Quantum/DOM: Difference between revisions

4,312 bytes added ,  17 May 2017
m
(Request clarity in the TabGroup definition)
 
(21 intermediate revisions by 2 users not shown)
Line 7: Line 7:
= Concepts =
= Concepts =


To more precisely specify when one runnable can observe state from another runnable, we need to define some terminology.
First, a <b>runnable</b> in this context specifically refers to nsIRunnable. However, note that other common things such an nsITimer implementations use nsIRunnable under the hood. Any code that indirectly (and possibly without the knowledge of the author of the consuming code) uses nsIRunnable under the hood also needs to be modified so that the underlying runnable(s) are labeled.


First, a <b>TabGroup</b> 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}. <span style="color: red">(XXX Is this specifically about sharing the same opener, or about being reachable through an opener chain? That is, if T5 opens T6 which opens T7, are they all in the same TabGroup? And what if a tab is opened using rel=noopener? Does that start a new group?)</span> 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 TabGroup can run while a runnable from a different TabGroup is paused.
To more precisely specify when one runnable can observe state from another runnable, we need to define some terminology:
 
A <b>TabGroup</b> 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}. <span style="color: red">(XXX Is this specifically about sharing the same opener, or about being reachable through an opener chain? That is, if T5 opens T6 which opens T7, are they all in the same TabGroup? And what if a tab is opened using rel=noopener? Does that start a new group?)</span> 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 TabGroup can run while a runnable from a different TabGroup is paused.


A <b>DocGroup</b> 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 belong to 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 <b>DocGroup</b> 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 belong to 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.


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.
The <b>SystemGroup</b> is a group for runnables that do not affect web content.


Runnables that do not affect web content can be labeled with the <b>SystemGroup</b>. Only use the system group if you know that the runnable will not run any content JS code or do any content DOM or layout access.
= Labeling =
 
A major task for the Quantum DOM project is to label runnables with DocGroups, TabGroups, or the SystemGroup. Using a DocGroup should be preferred over TabGroup since TabGroup is less specific. But for some runnables the best we can do is to give it a TabGroup.


= Labeling =
A major task for the Quantum DOM project is to "label" runnables. Labeling refers to giving runnables names, and to associating them with a DocGroup, TabGroup, or the SystemGroup before they're dispatched. Associating runnables with a group is ultimately what will enable Quantum DOM to work, but at the very least runnables should be given a name (grouping can be more tricky than naming). If runnables are at least named then we can gather telemetry about which ungrouped runnables are the most common so that we can focus our efforts on grouping the most important runnables. (Ideally we'll name and group runnables at the same time though.)


Based on how it is dispatched, there are multiple ways to label a runnable. The simplest way is to provide the DocGroup or TabGroup when dispatching the runnable. You can help by taking one of the unowned bugs to label a runnable in the following list:
You can help by taking one of the unowned bugs to label runnables from the following list:


<bugzilla>
<bugzilla>
Line 31: Line 31:
   }
   }
</bugzilla>
</bugzilla>
== Runnable naming ==
Runnables should be named as follows:
* If the runnable is a class that you define, its name should be the name of the class. Namespaces can be left off unless they are necessary for disambiguation. For example, if the runnable is simply called <code>Runnable</code>, then a namespace should be included. But if the runnable is <code>mozilla::dom::OffThreadScriptRunnable</code>, then the namespaces can be omitted.
* If the runnable is a method (created via some variant of NewRunnableMethod), then the class name and method name should form the name. <code>"nsDocument::UpdateVisibilityState"</code> is a good example. Namespaces should be omitted as before unless they're necessary.
* If the runnable is a function created via NewRunnableFunction, follow the rule as if it were a class.
If the runnable name would be ambiguous <i>and</i> it lives in an anonymous namespace, then make up a namespace that seems right.
== Runnable grouping ==
Using a DocGroup is preferred over using a TabGroup since TabGroup is less specific, but for some runnables the best we can do is give it a TabGroup. Runnables that do not affect web content at all should be labeled using the <b>SystemGroup</b>. However, only use the system group if you are absolutely sure that the runnable will not run any content JS code or have any affect on content DOM or layout.
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 ==
== Dispatching ==
Based on how it is dispatched, there are multiple ways to label a runnable. The simplest way is to provide the DocGroup or TabGroup when dispatching the runnable.


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 "task category". For now, these are for debugging purposes, but the category may be used for scheduling purposes later on.
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 "task category". For now, these are for debugging purposes, but the category may be used for scheduling purposes later on.
Line 50: Line 70:
  +  Dispatch("nsDocument::UpdateVisibilityState", TaskCategory::Other, event.forget());
  +  Dispatch("nsDocument::UpdateVisibilityState", TaskCategory::Other, event.forget());
   }
   }
== Runnable naming convention ==
Runnables should be named as follows:
* If the runnable is a class that you define, its name should be the name of the class. Namespaces can be left off unless they are necessary for disambiguation. For example, if the runnable is simply called <code>Runnable</code>, then a namespace should be included. But if the runnable is <code>mozilla::dom::OffThreadScriptRunnable</code>, then the namespaces can be omitted.
* If the runnable is a method (created via some variant of NewRunnableMethod), then the class name and method name should form the name. <code>"nsDocument::UpdateVisibilityState"</code> is a good example. Namespaces should be omitted as before unless they're necessary.
* If the runnable is a function created via NewRunnableFunction, follow the rule as if it were a class.
* If the runnable name would be ambiguous <i>and</i> it lives in an anonymous namespace, then make up a namespace that seems right.


== Event Targets ==
== Event Targets ==


A lot of existing Gecko code uses an nsIEventTarget to decide where to dispatch runnables. The DocGroup and TabGroup classes expose EventTargetFor(category) methods that return an event target. Using this event target is equivalent to calling Dispatch on the DocGroup or TabGroup. (The one difference is that no name is provided for the runnable.) {TabGroup,DocGroup}::EventTargetFor can be called on any thread. As a convenience, you can also use nsIDocument::EventTargetFor (also callable from any thread) or nsIGlobalObject::EventTargetFor (main thread only).
A lot of existing Gecko code currently uses an nsIEventTarget to decide where to dispatch runnables. The DocGroup and TabGroup classes expose EventTargetFor(category) methods that return an nsIEventTarget. Using this event target is equivalent to calling Dispatch on the DocGroup or TabGroup (except that unfortunately no name is provided for the runnable). {TabGroup,DocGroup}::EventTargetFor can be called on any thread. As a convenience, you can also use nsIDocument::EventTargetFor (also callable from any thread) or nsIGlobalObject::EventTargetFor (main thread only).


=== Runnable names ===
=== Runnable names ===


One disadvantage of using EventTargetFor is that any runnables dispatched this way are not given a name. However, there are other options for assigning a name to runnables. To have a name, a runnable needs to implement the GetName method of the nsINamed interface. mozilla::Runnable already does this. You can use the SetName method on an existing mozilla::Runnable to change its name.
One disadvantage of using EventTargetFor is that any runnables dispatched this way are not given a name. However, there are other options for assigning a name to runnables. To have a name, a runnable needs to inherit the nsINamed interface and implement its GetName method. mozilla::Runnable already does this, so you can use the SetName method on an existing mozilla::Runnable to set its name.


Usually, though, you'll be using EventTargetFor in cases where you don't have direct access to the runnable. Typically you'll be giving the event target to a sub-system that will dispatch multiple runnables. Timers, the IPC code, and workers are examples of this. In these cases it's best to modify the sub-system to set pass down an appropriate name for the runnable. The IPC code, for example, can set the runnable name to the name of the message being dispatched.
Usually, though, you'll be using EventTargetFor in cases where you don't have direct access to the runnable. Typically you'll be giving the event target to a sub-system that will dispatch multiple runnables. Timers, the IPC code, and workers are examples of this. In these cases it's best to modify the sub-system to set pass down an appropriate name for the runnable. The IPC code, for example, can set the runnable name to the name of the message being dispatched.
Line 205: Line 217:


== AbstractThread::MainThread ==
== AbstractThread::MainThread ==
AbstractThread::MainThread() is a singleton of the AbstractThread wrapper class for the main thread and is widely used with MozPromise and its Promise-chain. If you use AbstractThread::MainThread() in your code and you have access to a document or a window, you can replace it with '''AbstractMainThreadFor''' provided the document or window object (or by TabGroup or DocGroup directly).
AbstractThread::MainThread() is a singleton of the AbstractThread wrapper class for the main thread and is widely used with MozPromise and its Promise-chain. If you use AbstractThread::MainThread() in your code and you have access to a document or a window, you can replace it with '''AbstractMainThreadFor''' provided by the document or window object (or by TabGroup or DocGroup directly).


=== Example ===
=== Example ===
Line 254: Line 266:
<b>A:</b> For people who want to participate in these labeling tasks, there is a list of good labeling bugs is linked off [https://bugzilla.mozilla.org/show_bug.cgi?id=labeling bug 1321812]. We will use telemetry to decide which runnables are most important, and updates will be posted to that bug. (Note: the analysis of the telemetry is available in [https://bugzilla.mozilla.org/show_bug.cgi?id=1333984 bug 1333984].)
<b>A:</b> For people who want to participate in these labeling tasks, there is a list of good labeling bugs is linked off [https://bugzilla.mozilla.org/show_bug.cgi?id=labeling bug 1321812]. We will use telemetry to decide which runnables are most important, and updates will be posted to that bug. (Note: the analysis of the telemetry is available in [https://bugzilla.mozilla.org/show_bug.cgi?id=1333984 bug 1333984].)


For sub-system experts, besides clearing the list above related to your sub-system, we need your help to look for calls to NS_DispatchTo{Main,Current}Thread or nsIThread::Dispatch, and the use cases of TimerCallback and IPC actor childs in your sub-system to file more bugs to ensure that all runnables to be labeled are covered and will be fixed. In general, no matter how frequent the runnable appears in the analysis list, '''all main-thread runnables in the content process need to be lableled''' to prevent the impact explained in Q1 because the happened rate will be the accumulation of the happened rates of all these unlabeled runnables. Otherwise, the benefit we get from Quantum-DOM will be significantly reduced.
For sub-system experts, besides clearing the list above related to your sub-system, we need your help to look for the use cases mentioned in '''Q3'''. In general, no matter how frequent the runnable appears in the analysis list, '''all main-thread runnables in the content process need to be lableled''' to prevent the impact explained in Q1 because the happened rate will be the accumulation of the happened rates of all these unlabeled runnables. Otherwise, the benefit we get from Quantum-DOM will be significantly reduced.
 
=== Q3: What runnables shall be labeled? ===
<b>A:</b> All the runnables ​dispatched to '''the main thread of the content process''' shall be labeled.


=== Q3: How do I specify a label for an nsITimer TimerCallback? ===
In addition to '''NS_Dispatch(Main|Current)Thread''', there are several ways to dispatch a runnable to the main thread implicitly:
 
* Any calls to the '''Dispatch()''' method of the subclass of '''nsIEventTarget'''.
* The use of '''AbstractThread::MainThread()''' (Most of them shall be covered in [https://bugzilla.mozilla.org/show_bug.cgi?id=1314833 bug 1314833])
* The use of '''nsITimer'''. (The timer callback will be called by a runnable to the current thread according to nsTimer implementaiton).
* Handled the received messages in '''IPC actor childs'''. (A received message will be handled on main thread with a new runnable if its actor child is created on the main thread.)
* Subclass of '''nsExpirationTracker''' (The overriden method of NotifyExpired() will be triggered implicitly with a new runnable by the internal timer in nsExpirationTracker implementation. See the dependency tree of [https://bugzilla.mozilla.org/show_bug.cgi?id=1345464 bug 1345464] for the list of the sub-classes.)
* The use of '''AsyncWait()''' on any '''nsIAsync(In|Out)putStream''' or the use of '''NS_NewInputStreamPump()'''. (These use cases trigger new runnables named '''(In|Out)putStreamReadyEvent''' to the specified nsIEventTarget in the invocation of AsyncWait(). These runnables need to be labeled if the nsIEventTarget points to the main thread.)
* The use of '''NS_ProxyRelease''' on main thread, '''NS_ReleaseOnMainThread''', or '''nsMainThreadPtrHolder'''.
* All '''[[Quantum/DOM#IPC_Actors|IPC Actor Childs]]''' created on the main thread have to be labeled.
 
=== Q4: How do I specify a label for an nsITimer TimerCallback? ===


<b>A:</b> This can be done by <code>nsITimer::SetTarget(nsIEventTarget*)</code> combined with <code>EventTargetFor</code>. See the XMLHttpRequest example above.
<b>A:</b> This can be done by <code>nsITimer::SetTarget(nsIEventTarget*)</code> combined with <code>EventTargetFor</code>. See the XMLHttpRequest example above.


=== Q4: Will the <code>TaskCategory</code> be used for prioritizing in the future? ===
=== Q5: Will the <code>TaskCategory</code> be used for prioritizing in the future? ===


If it will, how can developers ensure that the ordering won't be a problem if tasks are labeled in different categories in current developing stage?
If it will, how can developers ensure that the ordering won't be a problem if tasks are labeled in different categories in current developing stage?
Line 266: Line 292:
<b>A:</b> It may be used for scheduling in the future. Generally, anything that's not marked as Other should come from some outside source (usually IPC, but also from a timer). In that case, we have a lot of freedom about when to schedule it. So there shouldn't be too many concerns about breaking things as long as people follow that rule.
<b>A:</b> It may be used for scheduling in the future. Generally, anything that's not marked as Other should come from some outside source (usually IPC, but also from a timer). In that case, we have a lot of freedom about when to schedule it. So there shouldn't be too many concerns about breaking things as long as people follow that rule.


=== Q5: Do runnables need to be labeled in the main process? ===
=== Q6: Do runnables need to be labeled in the main process? ===


<b>A:</b> No. Only content process runnables need to be labeled. But labeling other runnables won't break anything.
<b>A:</b> No. Only content process runnables need to be labeled. But labeling other runnables won't break anything.
=== Q7: How to name an annoymous runnable before supporting (Doc|Tab|System)Group labeling? ===
<b>A:</b> Name can be given according to the type of annoymous runnables
* Subclasses of '''(Cancelable)Runnable''' classes which is displayed as "anonymous runnable" in the runnable analysis of [https://bugzilla.mozilla.org/show_bug.cgi?id=1333984 bug 1333984].
** To rename these runnables, you can invoke base classes constructor of '''(Cancelable)Runnable(const char* aName)''' in the subclass constructor.
** If your runnable instance was created from '''NewRunnableFunction(aFunction, ...)''' or '''NewRunnableMethod(aMethod, ...)''', you can call the the overloaded functions of '''NewRunnableFunction(aName, ...)''' or '''NewRunnableMethod(aName, ...)''' to rename these runnables.
* Subclasses of '''nsIRunnable''' but not '''Runnable''' classes which is displayed as "non-nsINamed runnable" in the runnable analysis of [https://bugzilla.mozilla.org/show_bug.cgi?id=1333984 bug 1333984].
** To rename these runnables, you can either change the base class to '''Runnable''' or inherit '''nsINamed''' class and override its '''GetName''' method.
* The timer callbacks of '''nsITimer::Init()''' or '''nsITimer::InitWith(FuncCallback|Callback)''' which are displayed as "Anonymous_observer_timer", "Anonymous_callback_timer", and "Anonymous_interface_timer" in the runnable analysis of [https://bugzilla.mozilla.org/show_bug.cgi?id=1333984 bug 1333984].
** To rename this timercallbacks, you can either have your nsISupport/nsIObserver class inheriting  '''nsINamed''' class or call the named version of '''Timer::InitWith(NamedFuncCallback|initWithNameableFuncCallback)''' for your nsTimerCallbackFunc instance.
Confirmed users
205

edits