Supporting per-window private browsing

Firefox 20 introduced per-window private browsing mode, in which private user data is stored and accessed concurrently with public user data from another window.

Detecting private browsing mode

Determining whether or not a given DOM window is private is simple: import resource://gre/modules/PrivateBrowsingUtils.jsm and use PrivateBrowsingUtils.isWindowPrivate(window). You can then take action based on this value, as any data or actions originating from this window should be considered private.

        try {
          // Firefox 20+
          Components.utils.import("resource://gre/modules/PrivateBrowsingUtils.jsm");
          if (!PrivateBrowsingUtils.isWindowPrivate(window)) {
            ...
          }
        } catch(e) {
          // pre Firefox 20 (if you do not have access to a doc.
          // might use doc.hasAttribute("privatebrowsingmode") then instead)
          try {
            var inPrivateBrowsing = Components.classes["@mozilla.org/privatebrowsing;1"].
                                    getService(Components.interfaces.nsIPrivateBrowsingService).
                                    privateBrowsingEnabled;
            if (!inPrivateBrowsing) {
              ...
            }
          } catch(e) {
            Components.utils.reportError(e);
            return;
          }
        }

Obtaining an nsILoadContext for privacy-sensitive APIs

Some APIs (such as nsITransferable and nsIWebBrowserPersist) take nsILoadContext arguments that are used to determine whether they should be classed as private or not (for example, whether the URI being persisted by saveURI should be added to the permanent download history). To do this, import resource://gre/modules/PrivateBrowsingUtils.jsm and use PrivateBrowsingUtils.getPrivacyContextFromWindow(win), passing a Window object that is related to the content in question.

As an example, if an add-on adds a context menu item that accesses an API that requires an nsILoadContext, the most relevant window is the one that owns the element being targeted by the context menu (element.ownerDocument.defaultView). If some action triggered by a chrome element (such as a button) requires an API that takes a privacy context, the most relevant window is the one that contains the chrome element.

In some cases, there is no logical window object to use (such as when data or an action originate from some other source than web content). In these cases, it is permissable to pass null as the argument instead of a real privacy context. However, this can lead to privacy leaks (such as cache and download entries) if not used carefully.

Clearing any temporarily-stored private data

It is permissable to store private data in non-persistent ways for the duration of a private browsing session. To be notified when such a session ends (i.e., when the last private window is closed), observe the last-pb-context-exited notification.

function pbObserver() { /* clear private data */ }
var os = Components.classes["@mozilla.org/observer-service;1"]
                   .getService(Components.interfaces.nsIObserverService);
os.addObserver(pbObserver, "last-pb-context-exited", false);

Preventing a private session from ending

If there are unfinished transactions involving private data, where the transactions will be terminated by the ending of a private session, an add-on can vote to prevent the session from ending (prompting the user is recommended). To do this, observe the last-pb-context-exiting notification and set the data field of the nsISupportsPRBool subject to true.

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) {
      /* you should display some user interface here */
      aSubject.data = true; // cancel the operation
   }
}, "last-pb-context-exiting", false);

Forcing a channel into private mode

Usually, network channels inherit the privacy status of the document that created them, which means that they work correctly most of the time. However, sometimes you need to adjust the privacy status on a channel manually, for example, if you have created the channel directly yourself. You can use the nsIPrivateBrowsingChannel interface for this purpose. The example below creates a channel to load a URL, and forces it to be in private mode.

var channel = Services.io.newChannel("http://example.org", null, null);
channel.QueryInterface(Components.interfaces.nsIPrivateBrowsingChannel);
channel.setPrivate(true); // force the channel to be loaded in private mode

Similarly, XMLHttpRequest objects created via createInstance(Ci.nsIXMLHttpRequest) will often require explicit adjustment, since they have no context from which to derive a privacy status.

var xhr = Components.classes["@mozilla.org/xmlextras/xmlhttprequest;1"].createInstance(Components.interfaces.nsIXMLHttpReqeust);
var channel = xhr.channel.QueryInterface(Components.interfaces.nsIPrivateBrowsingChannel);
channel.setPrivate(true);