Prepare your add-on for Private Browsing

Private Browsing is one of the new features of Firefox which extension developers should start to handle in their extensions.  The API for this new mode is quite straightforward, and easy to use.  In addition, theme developers may want to style Firefox differently inside the Private Browsing mode.  That is also insanely easy to do.  In this article, I’m going to give you an overview of how the API works, plus with some sample code.  Note that what I’m explaining here is based the latest features landed on Mozilla trunk, and I don’t expect any of them to break before the final release of Firefox 3.1, but we may land extra API support if add-on developers demand it, so make sure to leave your comments.

Notice: This post is targeted at Mozilla extension developers and theme designers.  See my previous post for a general overview of the Private Browsing mode.

Private Browsing mode for extension developers

First, a bit of background is in order.  Many extensions may store data which can be used to reveal the places that the user has visited.  Examples include a download manager/helper extension, an extension which manipulates Places information, an extension which stores or otherwise manipulates cookies, etc.  You may be wondering what it takes to ensure that your extension respects the user’s choice about the private mode.  The most important thing to note here is that the Private Browsing mode does not magically handle what your extension does in saving browsing history data; that is the job of each extension.

At the heart of our Private Browsing implementation lies the Private Browsing service.  This service, which can be accessed using the contract ID @mozilla.org/privatebrowsing;1, implements the nsIPrivateBrowsingService interface.  Here is the definition of this interface:

[scriptable, uuid(49d6f133-80c0-48c7-876d-0b70bbfd0289)]
interface nsIPrivateBrowsingService : nsISupports
{
    // When read, determines whether the private browsing mode is currently
    // active.  Setting to true enters the private browsing mode, and setting
    // to false leaves the private browsing mode.
    // Setting this value while handling one of the notifications generated
    // by the private browsing service throws NS_ERROR_FAILURE.
    attribute boolean privateBrowsingEnabled;

    // Determine whether the private browsing mode has been started
    // automatically at application startup.
    // This value will never be true if privateBrowsingEnabled is false.
    readonly attribute boolean autoStarted;
};

The privateBrowsingEnabled attribute is the most important one.  To get the current status of the Private Browsing service, it’s enough to get the value of this attribute.  To switch to the Private Browsing mode, this attribute should be set to true, and to exit this mode, it should be set to false.  The autoStarted attribute can be queried to determine whether the browser.privatebrowsing.autostart preference has triggered the private mode automatically at startup.

There are a number of notifications which the Private Browsing service sends out in order to notify extensions about the Private Browsing related events that happen at runtime.  When the Private Browsing mode is about to initiate, the service sends out the private-browsing-cancel-vote notification in order to ask all the observers if they are all OK with entering the private browsing mode.  The data parameter if this notification will be set to enter.  The subject parameter of this notification will be set to a nsISupportsPRBool object.  Extensions are supposed to set this object to true if they wish to prevent the browser from entering the Private Browsing mode (for example, if the extension is downloading a file which can’t be interrupted).  If none of the observers vote to cancel the mode transition, the Private Browsing mode will be activated.  This sends a private-browsing notification with the data parameter set to enter.

The reverse case happens when the Private Browsing service is requested to leave the private mode.  First, a private-browsing-cancel-vote notification is sent to check if all modules can handle the private mode switch.  The data parameter will be set to exit this time.  If no extension sets the subject parameter to true, then the private mode will be turned off, and a private-browsing notification will be sent with the data parameter set to exit.  One extra point to mention here is that the subject parameter of the private-browsing notification will be a nsISupportsPRBool which determines whether the mode is being terminated normally, or because of an application shutdown (true standing for the case of an application shutdown).

A few code samples will be useful here, to demonstrate the API.

Sample 1: check the status of the Private Browsing mode

This sample shows the most basic usage of the Private Browsing service: querying the current status of Private Browsing.

var pbs = Components.classes["@mozilla.org/privatebrowsing;1"]
                    .getService(Components.interfaces.nsIPrivateBrowsingService);

// are we currently in the Private Browsing mode?
var inPrivateBrowsingMode = pbs.privateBrowsingEnabled;

Sample 2: listen for Private Browsing notifications

This sample first defines a helper object to make the process of listening for Private Browsing mode changes easier, and then shows a small sample of how this can be used.

// Helper object to register listeners for Private Browsing mode changes
function PrivateBrowsingListener() {
  this.init();
}
PrivateBrowsingListener.prototype = {
  _os: null,
  _inPrivateBrowsing: false, // whether we are in private browsing mode
  _watcher: null, // the watcher object

  init : function () {
    this._inited = true;
    this._os = Components.classes["@mozilla.org/observer-service;1"]
                         .getService(Components.interfaces.nsIObserverService);
    this._os.addObserver(this, "private-browsing", false);
    this._os.addObserver(this, "quit-application", false);
    try {
      var pbs = Components.classes["@mozilla.org/privatebrowsing;1"]
                          .getService(Components.interfaces.nsIPrivateBrowsingService);
      this._inPrivateBrowsing = pbs.privateBrowsingEnabled;
    } catch(ex) {
      // ignore exceptions in older versions of Firefox
    }
  },

  observe : function (aSubject, aTopic, aData) {
    if (aTopic == "private-browsing") {
      if (aData == "enter") {
        this._inPrivateBrowsing = true;
        if (this.watcher &&
            "onEnterPrivateBrowsing" in this._watcher) {
          this.watcher.onEnterPrivateBrowsing();
        }
      } else if (aData == "exit") {
        this._inPrivateBrowsing = false;
        if (this.watcher &&
            "onExitPrivateBrowsing" in this._watcher) {
          this.watcher.onExitPrivateBrowsing();
        }
      }
    } else if (aTopic == "quit-application") {
      this._os.removeObserver(this, "quit-application");
      this._os.removeObserver(this, "private-browsing");
    }
  },

  get inPrivateBrowsing() {
    return this._inPrivateBrowsing;
  },

  get watcher() {
    return this._watcher;
  },

  set watcher(val) {
    this._watcher = val;
  }
};

// Here's how to use this helper

var listener = new PrivateBrowsingListener();

if (listener.inPrivateBrowsing) {
  // we are in the private mode!
} else {
  // we are not in the private mode!
}

listener.watcher = {
  onEnterPrivateBrowsing : function() {
    // we have just entered the private browsing mode!
  },

  onExitPrivateBrowsing : function() {
    // we have just left the private browsing mode!
  }
};

Please note that the onEnterPrivateBrowsing and onExitPrivateBrowsing functions are optional, and you can avoid declaring one if you don’t need it.  Also, note that the PrivateBrowsingListener object is safe to use in previous version of Firefox which did not have the Private Browsing service available.

Sample 3: turn Private Browsing on or off

This sample shows how extensions can switch to and from the Private Browsing mode.

var pbs = Components.classes["@mozilla.org/browser/privatebrowsing;1"]
                    .getService(Components.interfaces.nsIPrivateBrowsingService);

// enter the Private Browsing mode
pbs.privateBrowsingEnabled = true;

// now, whatever we do remains private!

// exit the Private Browsing mode
pbs.privateBrowsingEnabled = false;

Sample 4: prevent leaving the Private Browsing mode

This sample shows how an extension can prevent the browser from turning the Private Browsing mode off.

var os = Components.classes["@mozilla.org/observer-service;1"]
                   .getService(Components.interfaces.nsIObserverService);
os.addObserver(function (aSubject, aTopic, aData) {
    aSubject.QueryInterface(Components.interfaces.nsISupportsPRBool);
    // if another extension has not already canceled entering the private mode
    if (!aSubject.data) {
      if (aData == "exit") { // if we are leaving the private mode
        aSubject.data = true; // cancel the operation
      }
    }
  }, "private-browsing-cancel-vote", false);

Private Browsing mode for theme designers

Our goal here was to enable a CSS-only mechanism to allow theme designers to style the browser both outside and inside of the Private Browsing mode.  I simply added the browsingmode attribute to the window element in browser.xul.  The value of this attribute is normal when the user is outside the Private Browsing mode, and it’s switched to private when the user turns the Private Browsing mode on.  This allows having CSS rules to select any element inside the browser’s window inside the private browsing mode specifically, and styling them to your heart’s content.  For example, suppose that you would like the location bar to appear with a gray background while we’re inside the Private Browsing mode.  This can simply be done like below:

[browsingmode=private] #urlbar {
  background: #eee;
}

We have tried to keep everything as simple as we could, so if you feel there’s something which can be improved in the API, or need to ask a question, please do not hesitate to leave a comment here.  You’re feedback is appreciated.  Yes, really!

Update:  Modified Sample 2 to work in previous versions of Firefox.

Posted in Blog Tagged with: , , ,
20 comments on “Prepare your add-on for Private Browsing
  1. Panic Away says:

    […] adapted Eshan Akhgari’s recent blog post on private browsing into an article on MDC with some added details. In addition, I’ve […]

  2. […] should also respect the user moving in and out of private browsing mode.  Ehsan Akhgari has made a post in his weblog about what’s required of add-ons developers in order to support private bro….  If you’re an add-on developer it’s worth […]

  3. Ed Hume says:

    I will be using #FFD5EA as the privatemode urlbar background color in my themes. It comes out as a nice pink. If many of us use it, the color could become a de facto standard.

  4. Nathaniel says:

    Surely there should be some mechanism by which components that cancel user requests to change mode can provide a reason, ideally for the UI (though that might create i18n issues) but at a minimum for debugging purposes? A bool seems like a very narrow interface there, and one that’s likely to lead to mysterious and frustrating behavior (“It’s not working and I can’t tell why and no-one can help me either”).

  5. Ehsan Akhgari says:

    The Private Browsing service does not handle any user interface issues, so extensions are supposed to provide their own UI if they are canceling the Private Browsing mode transition.  For example, in the download manager, we use this notification to see if there is currently any non-resumable download in progress (which would be canceled by changing the private mode) and prompt the user whether she would like to cancel the download and enter/leave the Private Browsing mode or not.  Then, we provide the boolean cancel vote value to the Private Browsing service to either proceed with the mode transition, or abort.

  6. Thanks for the detailed instructions! Adblock Plus now disables hit statistics in private browsing mode: http://hg.mozdev.org/adblockplus/rev/1208d5106147

  7. PS: I noticed that “Private Browsing” isn’t shown in the title bar if autostart preference is turned on – is this intentional?

  8. Ehsan Akhgari says:

    Thank you for the quick adoption.  Adblock Plus may now be the first add-on to support the Private Browsing mode, which is very cool!

    A small suggestions regarding your implementation:  This check if ("nsIPrivateBrowsingService" in Components.interfaces) here is not really required, because if the extension is installed in an application without Private Browsing support, the code inside the try block throws when accessing Components.interfaces.nsIPrivateBrowsingService and it’s correctly ignored with the empty cache block.

  9. From all I know, “[browsingmode=private] #urlbar” isn’t the most efficient CSS selector because the browser will have to walk up and look for attribute “browsingmode” on all parent nodes of #urlbar. I wonder whether “[browsingmode=private]:root #urlbar” would be more efficient – depends of whether the CSS implementation can recognize that checking document element only is sufficient. Not that this really matters for one selector…

  10. Ehsan Akhgari says:

    Yes.  We don’t want to constantly remind the user that they’re in private mode if they have explicitly set Firefox to always run in that mode.

  11. I know – but I still prefer to catch exceptions only in unexpected cases, and private browsing not being supported is everything but unexpected. Actually, I probably shouldn’t even swallow the exception but print it to console like in similar cases.

  12. Ehsan Akhgari says:

    Good point.  Just note that until bug 462832 is resolved, nsIPrivateBrowsingService will be available on anything based on Gecko 1.9.1, but the Private Browsing service will only be available in Firefox 3.1.  Therefore, I think checking the existence of the contract ID may better serve your purpose.

  13. Thanks for the tip. This isn’t really a problem assuming bug 462832 gets resolved before release but I changed the check anyway.

  14. Ehsan Akhgari says:

    Hmm, reading through this guide, I couldn’t find any mention of :root pseudo-element, but your conclusion seems logical.  Another more efficient selector may be [browsingmode=private] > #navigator-toolbox > #nav-bar > #urlbar-container > #urlbar.

  15. […] Fixed: 248970 – Private Browsing mode. (Ehsan’s blog posts: screenshots, information for extension developers and theme designers.) […]

  16. Great post Ehsan. Thanks for taking the time to make the new interface clear for developers, as well as end users. Throughout the process, your attention to this kind of detail has been really fantastic.

  17. Ehsan Akhgari says:

    Thanks!  Yeah, I think this is a very important part of the job…

  18. I’ve adapted this post into the following article on MDC:

    https://developer.mozilla.org/En/Supporting_private_browsing_mode

    There’s also reference material for nsIPrivateBrowsingService:

    https://developer.mozilla.org/En/nsIPrivateBrowsingService

  19. Ehsan Akhgari says:

    Great job Eric, thanks!

    One thing I noted though is that the private-browsing-cancel-vote notification gets sent for both entering and leaving the private browsing mode, whereas the docs only mention the exiting part.  The entering part has exactly the same API, albeit with a different data parameter provided to the observers.

  20. […] adapted Eshan Akhgari’s recent blog post on private browsing into an article on MDC with some added details.  In addition, I’ve […]