Quantum/DOM

< Quantum
Revision as of 05:06, 18 November 2016 by Wmccloskey (talk | contribs) (first draft)
(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)

Goals

The goal of the Quantum DOM project is to eliminate jank from background tabs. One of the main ways we intend to do this is to run each tab in its own cooperatively scheduled thread. If a runnable on a background thread takes too long to run, then we will pause its execution and switch to a different thread. To do this correctly, we need to guarantee that web pages never observe a change in behavior. For example, it would be bad if we paused a runnable R1 and then allowed another runnable R2 from the same page to see that R1 had started but not yet finished.

Concepts

To more precisely specify when one runnable can observe state from another runnable, we need to define some terminology.

First, a TabGroup is the set of tabs that are related by window.opener. In a session with four tabs, where T1 opens T2 and T3 opens T4, the TabGroups are {T1, T2} and {T3, T4}. Once a tab joins a TabGroup, it never leaves it. TabGroups have the property that two tabs from different TabGroups can never observe each other's state. So a runnable from one of these tabs can run while a runnable from the other tab is paused.

A DocGroup is the collection of documents from a given TabGroup that share the same eTLD+1 part of their origins. So if a TabGroup contains tabs with documents {x.a.com, y.a.com, x.b.com, y.b.com}, then these documents will for two DocGroups: {x.a.com, y.a.com}, {x.b.com, y.b.com}. DocGroups are essentially a refinement of TabGroups to account for the fact that only same-origin documents can synchronously communicate. (The eTLD+1 part is to account for pages changing their origin by modifying document.domain.) So a runnable from one DocGroup can run while a runnable from a different DocGroup is paused.

A major task for the Quantum DOM project is to classify runnables into DocGroups and TabGroups. Ideally we would assign all runnables a DocGroup. But in some cases the best we can do is to give it a TabGroup.

Implementation

Given a document, you can find its DocGroup via nsIDocument::GetDocGroup. Given an inner or outer window, you can find its TabGroup and DocGroup via nsPIDOMWindow::{TabGroup,GetDocGroup}. These methods should only be called on the main thread.

Dispatching

Both the TabGroup and DocGroup classes have Dispatch methods to dispatch runnables. Runnables dispatched in this way will always run on the main thread. You can call Dispatch from any thread. Both TabGroup and DocGroup are threadsafe refcounted. The Dispatch method requires you to name the runnable and provide a category. For now, these are for debugging purposes, but the category may be used for scheduling purposes later on.

As a convenience, nsIDocument and nsIGlobalObject have a Dispatch method that will dispatch to their DocGroup. The nsIDocument::Dispatch method can be used on any thread (although be careful because nsIDocument is not threadsafe refcounted). The nsIGlobalObject::Dispatch method is main thread only.

Event Targets

A lot of existing Gecko code uses an nsIEventTarget to decide where to dispatch runnables. As a convenience, it's possible to bundle a DocGroup or TabGroup along with an event name and category into a new nsIEventTarget. Any runnable dispatched to this target will act as if the Dispatch method were called on the corresponding DocGroup or TabGroup.

To create a new event target, you can use {TabGroup,DocGroup}::CreateEventTarget. This can be called from any thread. As a convenience, you can also use nsIDocument::CreateEventTarget (also callable from any thread) or nsIGlobalObject::CreateEventTarget (main thread only).

IPC Actors

Many content process runnables are dispatched from IPC. The IPC code allow you to specify an event target for each actor. Any messages received by that actor or its sub-actors will be dispatched to the given event target. You need to specify the event target after the actor is created but before sending the constructor method to the parent process. To do so, call the SetEventTargetForActor on the manager for the new actor. Pass in the new actor and the event target as parameters. All this must happen only on the main thread (or whichever thread the actor is bound to).

If the new actor is created on the parent side, then you must override the GetMessageEventTarget method on the top-level protocol. Any constructor message is passed to this method. It can return an event target for the new actor or null if no special event target should be used. Be careful, because this method is called on the Gecko I/O thread.