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 […]