Per-window Private Browsing
|Per-window Private Browsing|
|Release target||Firefox 20|
|Status note||Landed in Firefox 20|
|Product manager||Sid Stamm|
|Directly Responsible Individual||Josh Matthews|
|Lead engineer||Josh Matthews|
|Security lead||Dan Veditz|
|QA lead||Ioana Budnar|
|Product marketing lead||`|
|Additional members||Asa Dotzler, Ehsan Akhgari|
The proposed design will break every addon that uses the existing privite browsing implementation in any way (even just attempting to play nicely with entering/existing).
Stage 1: Definition
1. Feature overview
We want to move away from a global flag model of PB being enabled to a per-window model more in line with other browsers. The implementation should support per-tab private browsing as an implementation detail, but that functionality will not be exposed by default.
2. Users & use cases
Users should be able to open up a new private browsing window without interacting with their existing browsing session in any way (especially through leakage of data from one context to the other).
We do not want to expose per-tab UI for enabling/disabling private browsing.
Stage 2: Design
5. Functional specification
6. User experience design
Stage 3: Planning
7. Implementation plan
https://wiki.mozilla.org/Per-window_Private_Browsing has a discussion of various consumers of the existing service and the ways they will be modified.
Unresolved design issues:
- API design for consumers that don't have access to a channel or docshell - duplicate relevant Add/RemoveFoo APIs with Add/RemovePrivateFoo? Something else?
Resolved design issues:
- Open a new private mode window from command line:
Can't we use a temporary profile in a separate Firefox instance instead?
- It's assumed that we want things like extensions, history, bookmarks, etc. from the user's profile to continue to exist in the private window. A separate profile won't provide these, so this is not a feasible quick and dirty solution.
Quality Assurance review
Stage 4: Development
Implementation is under way by volunteers and paid contributors working in their spare time. Some patches have landed on mozilla-central.
The metabug for this work is https://bugzilla.mozilla.org/show_bug.cgi?id=pbngen with individual bugs filed for existing consumers of the global service. Please see this bug to get a sense of the remaining items.
Stage 5: Release
10. Landing criteria
|Theme / Goal||Contextual Identity|
|Secondary roadmap||Firefox Desktop|
Team status notes
|Quality assurance||Signed off||Test Plan|
= $all-$resolved ?> Open; = $resolved ?> Resolved; = $all ?> Total (84.15% complete)
The current Private Browsing (PB) implementation in Firefox is very disruptive, because it closes all of your open tabs and windows. Although we do a relatively good job at restoring everything back to the state before initiating the PB session, we should really not require the user to give up their existing session.
This page summarizes the design which can lead to a per-window Private Browsing implementation for Desktop Firefox.
Global Private Browsing Mode Design
The global mode PB implementation is basically an application wide boolean flag which designates whether the PB mode is on or off. The private browsing service manages this global flag, and sends a bunch of notifications when the global mode is changing (for example, when the user invokes the Private Browsing flag). Each module which needs to store data which can identify the websites that a user has previously visited needs to handle these notifications in order to adjust its behavior depending on the PB mode flag, and refrain from storing such data inside this mode. For more information on how the existing API works, see this document.
Per-window Private Browsing Mode Design
In order to move towards a per-window PB design, we can't represent the PB state as a global flag any more. At a birds-eye view, we need to store a per-window boolean flag and each module which needs to handle its storage needs based on the PB status should somehow be able to know which window is ultimately responsible for the request at hand.
The per-window flag is going to be handled by a Gecko object called a docshell. A docshell is simply an object which stores the information associated which every document that Gecko loads which has a presentation. Each docshell can either be a content or a chrome docshell. A chrome docshell represents a document which has chrome privileges, such as browser.xul which renders Firefox's main window, or about:addons. A content docshell represents a document which does not have chrome privileges, which is most often used to load a website inside the browser.
The docshells are arranged in a tree. For example, in a simple browser window rendering Google's homepage, there is a chrome docshell representing the browser window, and a content docshell rendering the google.com homepage (plus another content docshell as its child reprenting an iframe inside Google's home page).
We will leverage this structure in order to provide a per-tab Private Browsing API. Firefox is probably not going to expose per-tab PB mode, as it will be too confusing to users, but we can have a more flexible API which add-ons can leverage.
Here is a per-module specification for how the per-window Private Browsing mode will be implemented. This specification is borrowing from the specification for the global private browsing mode.
Each docshell maintains a privateBrowsing boolean flag.
- Upon docshell creation:
- For chrome docshells, the flag is initialized to false.
- For content docshells, the flag is initialized to the value of the parent docshell's privateBrowsing flag.
- On getting, the docshell returns the last value set for that flag.
- On setting, the following happens:
- If the docshell is a content docshell, the set operation fails, returning NS_ERROR_FAILURE, unless the set operation is being invoked by a parent docshell.
- If the docshell is a chrome docshell, the set operation succeeds. The docshell will look into all of its child subtree, and set the privateBrowsing flag on each element in its subtree with its own flag.
Since nav history occurs through the docshell, we can block history (and related, eg. pushState) in private windows using available information. The History/nsNavHistory services currently have various checks about private browsing mode to avoid API callers from using them "incorrectly" in PB mode. The proposal is to remove these checks - any consumer that is explicitly using the API is assumed to know what it is doing (addons included).
Like above, we can remove any checks for private browsing - we already allow bookmark modifications in the existing PB mode.
HTTP connections from private browsing contexts go into a separate, in-memory cache session that can be evicted when all private browsing contexts are closed.
The Cookie Service will maintain an in-memory hashtable for the cookies set in PB mode. This table will be completely separate from the normal cookie storage that it uses, and it is possible for cookies with the same hostname and the same name to be stored in both tables.
- On setting a cookie, the Cookie Service tries to get a docshell for the channel setting the cookie (hint: we may be able to borrow some code from nsCookiePermission::GetOriginatingURI). If there is a docshell associated with the channel, and its privateBrowsing flag is set to true, the Cookie Service will store the cookie in its in-memory PB mode hashtable. Otherwise, it will use its normal storage.
- On getting a cookie, the Cookie Service tries to get a docshell for the channel getting the cookie. If there is a docshell associated with the channel, and its privateBrowsing flag is set to true, the Cookie Service will use its in-memory PB hashtable. Otherwise, it will use its normal storage.
Proposed changes: the download manager is given an optional nsILoadContext argument when adding downloads which is used to determine whether the download is private. All download databases (private and non-private) get an extra GUID column, and methods that refer to downloads by ID change to accept GUIDs instead. The download manager window is left alone and only displays non-private downloads; the forthcoming download panel is modified to display private downloads in private windows.
Various places need to make decisions about how much data to delete when asked to do so (ie. clear cookies, etc.), but we might be able to get away with always deleting private and public info. We need to ensure that any key/vakue caching mechanism that exists doesn't get confused if a site is using localStorage/sessionStorage in both PB and non-PB modes concurrently. We also need to ensure that a docshell that transitions into/out of PB mode drops references to any current cached storage object.
The design which requires access to docshells is fundamentally broken for e10s, where networking occurs in the parent process and the relevant docshell is in a content process. This patch demonstrates a correct implementation for HTTP channels, whereby a concrete nsHttpChannel object can be reliably queried as to its PB usage, regardless of process. Possible solutions:
- add a new interface (or modify nsIChannel) to add private browsing status to every channel type, and propagate the information from the child to the parent as in the previously-linked HTTP patch
- make docshells implement IPDL actors, so that querying the docshell on the parent gives something useful. probably a non-starter.
I've chosen to make a clean break and create an nsIPrivateBrowserConsumer interface. This patch demonstrates the application of this to FTP and HTTP channels in a very elegant manner.
The cookie service already has a non-PB DB and a PB DB. However, functions like GetEnumerator assume that they can enumerate all the cookies of the currently active DB - how should this interact with the cookie list, which call this to list all cookies?
Ehsan and I agreed that there should be more of a separation than just PB vs. non-PB for some concepts like cache sessions, downloads, HTTP connectinos, etc. These should be grouped based on root docshell, so that when a root docshell in PB is closed, the corresponding session data should be destroyed.