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("https://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);