зеркало из https://github.com/mozilla/gecko-dev.git
333 строки
20 KiB
ReStructuredText
333 строки
20 KiB
ReStructuredText
.. _docsplit:
|
|
|
|
Document Splitting in WebRender
|
|
===============================
|
|
|
|
Goals
|
|
-----
|
|
|
|
The fundamental goal of document splitting is to fix a specific performance issue.
|
|
The architecture of WebRender is such that any time a content process sends a new
|
|
display list to the compositor, WebRender needs to do a full scene build that
|
|
includes both the content and chrome areas. Likewise if the chrome process sends
|
|
a new display list to the compositor. This means that animations such as the
|
|
tab loading spinner or an animated gif will cause much more WR activity than
|
|
is really needed.
|
|
|
|
With document splitting, the WR scene is split into two (or more) documents
|
|
based on the visual location of the elements. Everything in the "chrome area"
|
|
of the window (including the tab bar, URL bar, navigation buttons, etc.) are
|
|
considered part of one document, and anything below that (including content,
|
|
devtools, sidebars, etc.) is in another document. Bug 1549976 introduces a
|
|
third "popover" document that encompasses elements that straddle both of these
|
|
visually, but that's a side-effect of the implementation rather than being
|
|
driven by the fundamental problem being addressed.
|
|
|
|
With the documents split like so, an animation in the UI (such as the tab loading
|
|
spinner) runs independently of the content (by virtue of being in a different
|
|
document) and vice-versa, which results in better user-perceived performance.
|
|
|
|
Naming
|
|
------
|
|
|
|
Document splitting is so called because inside the WR code and bindings, a
|
|
"document" is an independent pathway to the compositor that has its own scene,
|
|
render tasks, etc.
|
|
|
|
In most of the C++ code in gfx/layers and other parts of Firefox/Gecko, the term
|
|
"render root" is used instead. A "render root" is exactly equivalent to a WR
|
|
document; the two terms are used interchangeably. The naming is this way
|
|
because "document" has different pre-existing meanings in Gecko-land. In this
|
|
documentation those other meanings are irrelevant and so "document" always refers
|
|
to the same thing as "render root".
|
|
|
|
At various points there have been discussions to renaming things so that everything
|
|
is more consistent and less confusing, but that hasn't happened yet.
|
|
|
|
Fundamental data types
|
|
----------------------
|
|
|
|
The `wr::RenderRoot
|
|
<https://searchfox.org/mozilla-central/rev/da14c413ef663eb1ba246799e94a240f81c42488/gfx/webrender_bindings/WebRenderTypes.h#65>`_
|
|
data type is an enumeration of all the different documents that are expected
|
|
to be created. As of this writing, the enumeration contains two entries: Default
|
|
and Content. The Default document refers to the one that holds the "chrome" stuff
|
|
and the Content document refers to the one that holds the "content" area stuff.
|
|
|
|
If document splitting is disabled (gfx.webrender.split-render-roots=false) then
|
|
everything lives in the Default document and the Content document is always empty.
|
|
|
|
Additional data structures in the same file (e.g. wr::RenderRootArray<T>) facilitate
|
|
converting pre-document-splitting code into document-splitting-aware code, usually
|
|
by turning a single object into an array of objects, one per document.
|
|
|
|
High-level design
|
|
-----------------
|
|
|
|
The notion of having multiple documents has to be introduced at a fairly fundamental
|
|
level in order to be propagated through the entire rendering pipeline. It starts
|
|
in the front-end HTML/XUL code where certain elements are annotated as being the
|
|
transition point between documents. For example, `this code
|
|
<https://searchfox.org/mozilla-central/rev/8ed8474757695cdae047150a0eaf94a5f1c96dbe/browser/base/content/browser.xhtml#1304>`_
|
|
explicitly identifies an element and its descendants as being in the "content"
|
|
document instead of the "default" (or "chrome") document.
|
|
|
|
These attributes are `read during display list building
|
|
<https://searchfox.org/mozilla-central/rev/8ed8474757695cdae047150a0eaf94a5f1c96dbe/layout/xul/nsBoxFrame.cpp#1112>`_
|
|
to create the nsDisplayRenderRoot display item in the Gecko display list.
|
|
|
|
When the Gecko display list is processed to create a WebRender display list,
|
|
it actually ends up creating multiple WR display lists, one for each document. This
|
|
is necessary because the documents are handled independently inside WR, and so
|
|
each get their own WebRenderAPI object and separate display list. The way the
|
|
implementation manages this is by `creating a "sub builder"
|
|
<https://searchfox.org/mozilla-central/rev/8ed8474757695cdae047150a0eaf94a5f1c96dbe/layout/painting/nsDisplayList.cpp#7043,7065>`_
|
|
for the render root that is being descended into, and using that instead of the
|
|
main WR display list builder as the display list is recursed into.
|
|
|
|
Note also that clip chains and stacking contexts are per-document, so when
|
|
recursing past a nsDisplayRenderRoot item, the `ClipManager and StackingContextHelper
|
|
<https://searchfox.org/mozilla-central/rev/da14c413ef663eb1ba246799e94a240f81c42488/gfx/layers/wr/WebRenderCommandBuilder.h#236-237>`_
|
|
being used switches to one specific to the new document. For this to work there are
|
|
certain assumptions that must hold, which are described in the next section.
|
|
Other things that must now be managed on a per-document basis are generally
|
|
encapsulated into the RenderRootStateManager class, and the WebRenderLayerManager
|
|
holds an `array of these
|
|
<https://searchfox.org/mozilla-central/rev/8ed8474757695cdae047150a0eaf94a5f1c96dbe/gfx/layers/wr/WebRenderLayerManager.h#242>`_.
|
|
|
|
After the Gecko display list is converted to a set of WebRender display lists
|
|
(one per document), these are sent across IPC along with any associated resources
|
|
as part of the `WebRender transaction
|
|
<https://searchfox.org/mozilla-central/rev/8ed8474757695cdae047150a0eaf94a5f1c96dbe/gfx/layers/ipc/PWebRenderBridge.ipdl#50>`_.
|
|
Conceptually, the parent side simply demultiplexes the data for different documents,
|
|
and submits the data for each document to the corresponding WebRenderAPI instance.
|
|
|
|
Limitations/Assumptions
|
|
-----------------------
|
|
|
|
One of the fundamental issues with the document splitting implementation is that
|
|
we can have stuff in the UI process that's part of the "content" renderroot (e.g.
|
|
a sidebar that appears to the left of the content area). The expectation for
|
|
front-end authors would be that this would be affected by ancestor elements that
|
|
are also in the UI process. Consider this outline of a Gecko display list:
|
|
|
|
::
|
|
|
|
- Root display item R
|
|
- ... stuff here (call it Q) ...
|
|
- display item P
|
|
- display item A
|
|
- display item B (flagged as being in the content renderroot)
|
|
- display item C
|
|
|
|
If item P was a filter, for example, that would normally apply to all of items
|
|
A, B, and C. This would mean either sharing the filter between the "chrome" renderroot
|
|
and the "content" renderroot, or duplicating it such that it existed in both
|
|
renderroots. The sharing is not possible as it violates the independence of WR
|
|
documents. The duplication is technically possible, but could result in visual
|
|
glitches as the two documents would be processed and composited separately.
|
|
|
|
In order to avoid this problem, the design of document splitting explicitly assumes
|
|
that such a scenario will not happen. In particular, the only information that
|
|
gets carried across the render root boundary is the positioning offset. Any
|
|
filters, transforms that are not 2D axis-aligned, opacity, or mix blend mode
|
|
properties do NOT get carried across the render root boundary. Similarly, a
|
|
scrollframe may not contain content from multiple render roots, because that
|
|
would lead to a similar problem in APZ where it would have to update the scroll
|
|
position of scrollframes in multiple documents and they might get composited
|
|
at separate times, resulting in visual glitches.
|
|
|
|
Security Concerns
|
|
-----------------
|
|
|
|
On the content side, all of the document splitting work happens in the UI process.
|
|
In other words, content processes don't generally know what document they are part
|
|
of, and don't ever split their display lists into multiple documents. Only the UI
|
|
process ever sends multiple display lists to the compositor side.
|
|
|
|
There are a number of APIs on PWebRenderBridge where a wr::RenderRoot is passed
|
|
across from the content side to the compositor side. And since PWebRenderBridge
|
|
is a unified protocol that is used by both the UI process and content processes
|
|
to communicate with the compositor, the content processes must provide *some*
|
|
value for the wr::RenderRoot. But since it doesn't (or shouldn't) be aware of
|
|
what document it's in, it must always pass wr::RenderRoot::Default.
|
|
|
|
Compositor-side code in WebRenderBridgeParent is responsible for checking that
|
|
any wr::RenderRoot values provided from a content process are in fact wr::RenderRoot::Default.
|
|
If this is not the case, it is either a programmer error or a hijacked content
|
|
process, and appropriate handling should be used. In particular, the compositor
|
|
side code should *never* blindly use the wr::RenderRoot value provided over the IPC
|
|
channel as hijacked content processes could force the compositor into leaking
|
|
information or otherwise violate the security and integrity of the browser. Instead,
|
|
the compositor is responsible for determining where the content is attached in
|
|
the display list of the UI process, and determine the appropriate document for that
|
|
content process. This information is stored in the `WebRenderBridgeParent::mRenderRoot
|
|
<https://searchfox.org/mozilla-central/rev/8ed8474757695cdae047150a0eaf94a5f1c96dbe/gfx/layers/wr/WebRenderBridgeParent.h#495>`_
|
|
field.
|
|
|
|
Implementation details
|
|
----------------------
|
|
|
|
This section describes various knots of complexity in the document splitting
|
|
implementation. That is, these pieces are thought to introduce higher-than-normal
|
|
levels of complexity into the feature, and should be handled with care.
|
|
|
|
APZ interaction
|
|
~~~~~~~~~~~~~~~
|
|
|
|
When a display list transaction is sent from the content side to the compositor,
|
|
APZ is also notified of the update, so that it can internally update its own
|
|
data structures. One of these data structures is a tree representation of the
|
|
scrollable frames on the page. With document splitting, the scrollable frames
|
|
may now be split across multiple documents. APZ needs to record which document
|
|
each scrollable frame belongs to, so that when providing the async scroll offset
|
|
to WebRender, it can send the scroll offset for a given a scrollable frame to the
|
|
correct WebRender document. As one might expect, this is stored in the `mRenderRoot
|
|
<https://searchfox.org/mozilla-central/rev/06bd14ced96f25ff1dbd5352cb985fc0fa12a64e/gfx/layers/apz/src/AsyncPanZoomController.h#916>`_
|
|
field in the AsyncPanZoomController (there is one instance of this per scrollable
|
|
frame).
|
|
|
|
Additionally, when new display list transactions and other messages are received
|
|
in WebRenderBridgeParent, APZ cannot process these updates right away. Doing so
|
|
would cause APZ to respond to user input based on the new display list, while
|
|
the WebRender internal state still corresponds to the old display list. To ensure
|
|
that APZ and WR's internal state remain in sync, APZ puts these update messages
|
|
into an `"updater queue"
|
|
<https://searchfox.org/mozilla-central/rev/06bd14ced96f25ff1dbd5352cb985fc0fa12a64e/gfx/layers/apz/src/APZUpdater.cpp#340>`_
|
|
which is processed synchronously with the WebRender scene swap. This ensures that
|
|
APZ's internal state is updated at the same time that WebRender swaps in the new
|
|
scene, and everything stays in sync. Conceptually this is relatively simple,
|
|
until we add document splitting to the mix.
|
|
|
|
Now instead of one scene swap, we have multiple scene swaps happening, one for
|
|
each of the documents. In other words, even though WebRenderBridgeParent gets a
|
|
single "display list transaction", the display lists for the different documents
|
|
modify WR's internal state at different times. Consequently, to keep APZ in sync,
|
|
we must apply a similar "splitting" to the APZ updater queue, so that messages
|
|
pertaining to a particular document are applied synchronously with that
|
|
document's scene swap.
|
|
|
|
(As a relevant aside: there other messages that APZ receives over other IPC
|
|
channels (e.g. PAPZCTreeManager) that have ordering requirements with the
|
|
PWebRenderBridge messages, and so those also normally end up in the updater queue.
|
|
Consequently, these other messages are also now subjected to the splitting of
|
|
the updater queue.)
|
|
|
|
Again, conceptually this is relatively simple - we just need to keep a separate
|
|
queue for each document, and when an update message comes in, we decide which
|
|
document a given update message is associated with, and put the message into the
|
|
corresponding queue. The catch is that often these messages deal with a specific
|
|
element or scrollframe on the page, and so when the message is sent from the
|
|
UI process, we need to do a DOM or frame tree walk to determine which render root
|
|
that element is associated with. There are some `GetRenderRootForXXX
|
|
<https://searchfox.org/mozilla-central/rev/06bd14ced96f25ff1dbd5352cb985fc0fa12a64e/gfx/thebes/gfxUtils.h#317-322>`_
|
|
helpers in gfxUtils that assist with this task.
|
|
|
|
The other catch is that an APZ message may be associated with multiple documents.
|
|
A concrete example is if a user on a touch device does a multitouch action with
|
|
different fingers landing on different documents, which would trigger a call to
|
|
`RecvSetTargetAPZC
|
|
<https://searchfox.org/mozilla-central/rev/06bd14ced96f25ff1dbd5352cb985fc0fa12a64e/gfx/layers/ipc/APZCTreeManagerParent.cpp#76>`_
|
|
with multiple targets, each potentially belonging to a different render root.
|
|
In this case, we need to ensure that the message only gets processed after
|
|
the corresponding scene swaps for all the related documents. This is currently
|
|
implemented by having each message in the queue associated with a set of documents
|
|
rather than a single document, and only processing the message once all the
|
|
documents have done their scene swap. In the example above, this is indicated by
|
|
building the set of render roots `here
|
|
<https://searchfox.org/mozilla-central/rev/06bd14ced96f25ff1dbd5352cb985fc0fa12a64e/gfx/layers/ipc/APZCTreeManagerParent.cpp#83>`_
|
|
and passing that to the updater queue when queueing the message. This interaction
|
|
is a source of some complexity and may have latent bugs.
|
|
|
|
Deferred updates
|
|
~~~~~~~~~~~~~~~~
|
|
|
|
Bug 1547351 provided a new and tricky problem where a content process is rendering
|
|
stuff that needs to go into the "default" document because it's actually an
|
|
out-of-process addon content that renders in the chrome area. Prior to this bug,
|
|
the WebRenderBridgeParent instances that corresponded to content processes
|
|
(hereafter referred to as "sub-WRBPs", in contrast to the "root WRBP" that
|
|
corresponds to the UI process) simply assumed they were in the "Content" document,
|
|
but this bug proved that this simplistic assumption does not always hold.
|
|
|
|
The solution chosen to this problem was to have the root WebRenderLayerManager
|
|
(that lives in the trusted UI process) to annotate each out-of-process subpipeline
|
|
with the render root it belongs in, and send that information over to the
|
|
root WRBP as part of the display list transaction. The sub-WRBPs know their own
|
|
pipeline ids, and therefore can find their render root by querying the root WRBP.
|
|
The catch is that sub-WRBPs may receive display list transactions *before* the
|
|
root WRBP receives the display list update that contains the render root mapping
|
|
information. This happens in cases like during tab switch preload, where the
|
|
user mouses over a background tab, and we pre-render it (i.e. compute and send
|
|
the display list for that tab to the compositor) so that the tab switch is faster.
|
|
In this scenario, that display list/subpipeline is not actually rendered, is not
|
|
tied in to the display list of the UI process, and therefore doesn't get associated
|
|
with a render root.
|
|
|
|
When the sub-WRBP receives a transaction in a scenario like this, it cannot
|
|
actually process it (by sending it to WebRender) because it doesn't know which
|
|
WR document it associated with. So it has to hold on to it in a "deferred update"
|
|
queue until some later point where it does find out which WR document it is
|
|
associated with, and at that point it can process the deferred update queue.
|
|
|
|
Again, conceptually this is straightforward, but the implementation produces a
|
|
bunch of complexity because it needs to handle both orderings - the case where
|
|
the sub-WRBP knows its render root, and the case where it doesn't yet. And the
|
|
root WRBP, upon receiving a new transaction, would need to notify the sub-WRBPs
|
|
of their render roots and trigger processing of the deferred updates.
|
|
|
|
Further complicating matters is Fission, because with Fission there can be
|
|
pipelines nested to arbitrary depths. This results in a tree of sub-WRBPs, with
|
|
each WRBP knowing what its direct children are, and only the root WRBP knowing
|
|
which documents its immediate children are in. So there could be a chain of
|
|
sub-WRBPs with a "missing link" (i.e. one that doesn't yet know what its children
|
|
are, because it hasn't received a display list transaction yet) and upon filling
|
|
in that missing link, all the descendant WRBPs from that point suddenly also
|
|
know which WR document they are associated with and can process their deferred
|
|
updates.
|
|
|
|
Managing all this deferred state, ensuring it is processed as soon as possible,
|
|
and clearing it out when the content side is torn down (which may happen without
|
|
it ever being rendered) is a source of complexity and may have latent bugs.
|
|
|
|
Transaction completion
|
|
~~~~~~~~~~~~~~~~~~~~~~
|
|
|
|
Transactions between the content and compositor side are throttled such that
|
|
the content side doesn't go nuts pushing over display lists to the compositor
|
|
when the compositor has a backlog of pending display lists. The way the throttling
|
|
works is that each transaction sent has a transaction id, and after the compositor
|
|
is done processing a transaction, it reports the completed transaction id back
|
|
to the content side. The content side can use this information to track how many
|
|
transactions are inflight at any given time and apply throttling as necessary.
|
|
|
|
With document splitting, a transaction sent from the content side gets split up
|
|
and sent to multiple WR documents, each of which are operating independently of
|
|
each other. If we propagate the transaction id to each of those WR documents,
|
|
then the first document to complete its work would trigger the "transaction complete"
|
|
message back to the content, which would unthrottle the next transaction. In this
|
|
scenario, other documents may still be backlogged, so the unthrottling is
|
|
undesirable.
|
|
|
|
Instead, what we want is for all documents processing a particular transaction
|
|
id to finish their work and render before we send the completion message back
|
|
to content. In fact, there's a bunch of work that falls into the same category
|
|
as this completion message - stuff that should happen after all the WR documents
|
|
are done processing their pieces of the split transaction.
|
|
|
|
The way this is managed is via a conditional in `HandleFrame
|
|
<https://searchfox.org/mozilla-central/rev/06bd14ced96f25ff1dbd5352cb985fc0fa12a64e/gfx/webrender_bindings/RenderThread.cpp#988>`_.
|
|
This code is invoked once for each document as it advances to the rendering step,
|
|
and the code in `RenderThread::IncRenderingFrameCount
|
|
<https://searchfox.org/mozilla-central/rev/06bd14ced96f25ff1dbd5352cb985fc0fa12a64e/gfx/webrender_bindings/RenderThread.cpp#552-553>`_
|
|
acts as a barrier to ensure that the call chain only gets propagated once all
|
|
the documents have done their processing work.
|
|
|
|
I'm listing this piece as a potential source of complexity for document splitting
|
|
because it seems like a fairly important piece but the relevant code is
|
|
"buried" away in a place where one might not easily stumble upon it. It's also not
|
|
clear to me that the implications of this problem and solution have been fully
|
|
explored. In particular, I assume that there are latent bugs here because other
|
|
pieces of code were assuming a certain behaviour from the pre-document-splitting
|
|
code that the post-document-splitting code may not satisfy exactly.
|