Traditional Interview questions

We’ve probably seen this question in all kinds of interview questions and interviews. What process does the browser go through from getting the data to presenting it on the page?

This is not a browser “brush” to render the page, experienced a very complex process, we bit by bit from shallow to deep to peep into the browser render the whole process.

The traditional answer

First, we can take a look at the more traditional way of answering the question. We don’t care about the impact of JAVASCRIPT files on page parsing. In the simplest pages, the browser just takes the HTML and CSS files and renders a page. In the browser engine, an HTML parser and a CSS parser are used to convert the received binary stream data into a DOM tree and a CSS rule tree that the browser can recognize, and then the two are combined to generate a Render(Layout Object) tree. The browser takes the Layout Object tree and then goes through the layering, drawing, etc., before we see the final page on the screen.

Chrome multi-process mechanism

We can also explore the multi-process aspect of Chrome, which is known for its multi-process architecture. Refer to modern browser architectures for details. For example, the browser process is responsible for the main framework of the browser and provides some general capabilities, while the GPU process is responsible for uploading the rendering process to the BITmap texture in the GPU for processing and rendering to the screen, etc. The renderer process associated with browser rendering is, of course, the most important thing we care about, which threads it is composed of, and how each thread communicates and collaborates to complete the rendering?

Render process composition

The rendering process is mainly composed of the following threads:

  1. GUI rendering main thread: parsing HTML, CSS, building DOM trees, and LayoutObject.
  2. JS engine thread: Executes and parses JS code.
  3. Synthesizer thread: performs block operations and is also responsible for receiving user scrolling, input, sending callback events, etc.
  4. Rasterize threads: Convert draw commands to bitmaps or textures that the GPU can recognize.

With Chrome :// Tracing tracing, we can trace the communication between threads during a page rendering process:

As shown above, CRFRenderMain represents the render main thread, which performs some computation. Compositior represents a synthesizer thread, which performs composition operations. Compositior Tile Workder represents rasterized threads. Modern browsers tend to have 2-4 rasterized threads, and browsers allocate rasterized thread resources based on resource availability.

Communication between threads

Let’s start with a frame rendering and look at the communication between threads as follows:

At the same time, in order to clearly distinguish each stage, the Blink kernel also defines a class DocumentLifeCycle to ensure that there will be no back and forth jump in each stage. Similar to the life cycle in React, rendering a frame is an atomic operation that starts rendering and is executed all the way through without rollback.

The overall rendering process is as follows:

  1. The synthesizer thread receives the Vsync signal and begins a new frame drawing.
  2. We know that the synthesizer thread can process user input. If there are callback events in some input events, such as scrolling callback events, the synthesizer thread will pass these events to the render main thread for processing in the current frame after collecting them in the previous frame.
  3. Perform animation operations related to requestAnimationFrame.
  4. Parsing HTML data to form A DOM tree is the main work of HTML parser. The HTML data received by the browser is also a byte stream, so to convert it into token tags that the browser can recognize and convert, the steps are as follows:
  • 4.1. Decoding: The browser will parse the received Bytes into characters based on the encoding method.

  • 4.2. Word segmentation: Characters are converted into tokens through word segmentation (lexical analysis), which can be divided into Tag tokens and text tokens. See the template parsing process in the vue source code for details, most of which are the same.

  • Convert tokens tags to Nodes and then add nodes to the DOM tree. These two steps are performed in parallel. During this period, maintenance is performed mainly through the data structure of the stack. Push the corresponding node onto the stack and add it to the DOM tree. When a text label is encountered, the text node can be directly added to the DOM tree. When a closed label is encountered, the stack operation is carried out. In addition, HTML is a friendly language, for the open and closed tags do not match the scene, or custom tags, have their own way to deal with, here will not do specific expansion.

  1. Once we have the DOM tree, we need to evaluate styles. The main process of evaluating styles is left undeveloped. We will focus on the CSS parser product, which is styleSheets, which can be seen in the console using Document. styleSheets:

​​

For details about stylesheets attributes, refer to stylesheets

The main ways we introduce CSS are inline styles, inline stylesheets, and external stylesheets (most commonly used). Here stylesheets is the final parsing product of each of the styles introduced.

After parsing the various import methods, we apply these styles to our DOM nodes, and the browser combines CSS inheritance, priority cascade, and other rules to form a CSS rule tree that can be Computed on a DOM node using Element-> computations in the browser.

​​

  1. Layout, the calculation of the Layout, here is mainly the page really need to render elements in the Layout Object tree display.
  2. Update the Layer Tree. For example, update the Paint Layer Tree and Graphic Layer Tree, which will be expanded in detail in the following sections.
  3. Paint, which generates Paint instructions and records which Paint calls need to be executed and in what order, serialized into the SkPicture data structure.
  4. Composite, calculate the data required by each Composite layer during synthesis, including the parameters of operations such as Translation, Scale, and Rotation mixing.

These are mainly in the browser main thread operation, it can be seen that the main are some of the complexity is not very high calculation operations, and JS parsing execution will be placed in the special JS parsing thread to execute.

​​

  1. Synthesis submitted to synthesizer threads, thread is mainly doing is a blocking operation, as everybody knows, if we draw on all the elements in a page’s words are very consumption performance (because rendering there is no need of the viewing area), so we will first region, harvest and draw the visual elements of the page. This can save a lot of resources.
  2. Rasterization, the main operation of rasterization is to convert the drawing instructions generated above into bitmaps or textures that can be recognized by THE GPU. There are mainly two ways as follows.
  • A. Based on CPU, using Software Rasterization of Skia library, first draw the carry map, and then upload it to GPU as a texture.
  • B. Based on GPU, Hardware Rasterization is adopted. This process is to draw and Raster directly in GPU texture with the help of OpenGL and fill pixels, namely GPU Raster.
  1. Submit to the GPU process for rendering and fore-and-aft buffer exchange, and display the results to the screen.

Layered phase

In the thread communication described above, we mainly ignored the process of layering, so we will focus on the purpose of the three trees generated in the process of layering.

Layout Object Tree

Function: DOM nodes can be divided into visual nodes (div, P), non-visual nodes (script,meta,head), etc. The function of the Render tree is to display the elements that actually need to be rendered on the page, ignore invisible elements such as (display: None), and add content that doesn’t exist in the DOM tree but needs to be displayed (such as pseudo-elements).

The layout tree forms an overview

The process shown above is to combine the DOM tree with the CSS rule tree, ignore invisible elements, and add visible elements to calculate the position of each element in the page.

Chrominum source code is also relatively simple, by judging display to generate different Layout objects.

​​

The mapping relationship

The mapping between each HTML node and LayoutObject is shown below. They all inherit from the same base class LayoutObject and derive different box models from it, such as the familiar block-level elements, inline elements and inline block elements. Each of these different classes defines the layout of its children and sibling elements, without elaboration.

​​

example

We can also take a simple layout code example to see what a Render tree would look like.

<! DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" /> <meta http-equiv="X-UA-Compatible" content="IE=edge" /> <meta name="viewport" content="width=device-width, Initial scale = 1.0 "/ > < title > Document < / title > < / head > < body > < div > < p > 123 < / p > < / div > < div > 1 < p > 456 < / p > < / div > < / body > </html>Copy the code
Content Shell

We use the Content Shell command line tool officially provided by Chromium, which has Chrome kernel but no UI. For details, please refer to it. After running the following command, you can see the Render tree generated by the above code.

Out/mychromium/Content \ Shell app/Contents/MacOS/Content \ Shell - run - web - layer tests - Desktop/demo/Layout/index. The HTMLCopy the code

In general, based on the Render tree, Layout Objects with the same Z coordinate space will be grouped into the same Paint Layer. The Paint Layer was originally used to stacking the context, similar to painting a blue sky with white clouds, to decide whether to Paint the blue sky first or the white clouds. If you draw white clouds first and then blue sky, there will be an error that white clouds are not visible. The same is true of the cascading context, which ensures that the page elements are composited in the correct order.

Rendering layer classification

Rendering layers can also be divided into the following three categories, and the main reasons for the formation of each rendering layer are as follows:

  1. kNormalPaintLayer
  • Root element (HTML)

  • An element whose position value is absolute or relative and z-index is not auto

  • An element whose position value is fixed or sticky

  • Child element of the Flex container, and z-index is not auto

  • A child element of the grid container, and z-index is not auto

  • Elements whose mix-blending-mode attribute value is not normal

  • Any of the following elements whose attribute value is not None:

    • transform
    • filter
    • perspective
    • clip-path
    • mask/mask-image/mask-border
    • Isolation elements with an attribute value of ISOLATE
  1. kOverflowClipPaintLayer
  • The overflow is not visible
  1. KNoPaintLayer
  • A PaintLayer that does not require paint, such as an empty div with no visual attributes (background, color, shadow, etc.)

For example,

<! DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" /> <meta http-equiv="X-UA-Compatible" content="IE=edge" /> <meta name="viewport" content="width=device-width, /> <title>Document</title> </head> <body> <div style="position: absolute; background-color: yellow; Z-index :1"> <div style="opacity: 0.1"> </div> <div style="filter: blur(5px)">filter</div> <div style="transform: translateX(20px)">tranform</div> <div style="mix-blend-mode: multiply">mix-blend-mode</div> <div style="overflow: hidden">overflow</div> </div> </body> </html>Copy the code

We can also use the Content Shell to see the Paint Layer layering generated by the code above.

Out/mychromium/Content \ Shell app/Contents/MacOS/Content \ Shell - run - web - layer tests - Desktop/demo/Paint/index. The HTMLCopy the code

​​

You can see that a render layer is formed for each HTML element that meets the criteria for generating a render layer.

The whole process

The call process for generating a rendering layer in the overall source code is as follows:

  1. The main thing we care about is the LayerTypeRequired function, where we get the actual type of rendering layer to produce.

    void LayoutBoxModelObject::StyleDidChange(StyleDifference diff, Const ComputedStyle* old_style) {·· code omission ·· if (old_style &&isoutofflowtoy () &&parent () && (StyleRef().GetPosition() == old_style->GetPosition()) && (StyleRef().IsOriginalDisplayInlineType() ! = old_style->IsOriginalDisplayInlineType())) Parent()->SetNeedsLayout(layout_invalidation_reason::kChildChanged, kMarkContainerChain); PaintLayerType type = LayerTypeRequired(); // No kNoPaintLayer if (type! = kNoPaintLayer) { if (! Layer()) { // In order to update this object properly, we need to lay it out again. // However, if we have never laid it out, don't mark it for layout. If // this is a new object, it may not yet have been inserted into the tree, // and if we mark it for layout then, we risk upsetting the tree // insertion machinery. if (EverHadLayout()) SetChildNeedsLayout(); / / create PainterLayer and insert CreateLayerAfterStyleChange (); }}}Copy the code
  2. LayerTypeRequired judge function

The main purpose of this function is to determine what type of PaintLayer is generated, and IsStacked, as its name suggests, is used to determine whether a cascading context will occur.

PaintLayerType LayoutBox::LayerTypeRequired() const { NOT_DESTROYED(); if (IsStacked() || HasHiddenBackface() || (StyleRef().SpecifiesColumns() && ! IsLayoutNGObject()) || IsEffectiveRootScroller()) return kNormalPaintLayer; if (HasNonVisibleOverflow()) return kOverflowClipPaintLayer; return kNoPaintLayer; }Copy the code

IsStacked function is the core of the process is as follows, we mainly is to judge the IsStackingContextWithoutContainment attributes, there will be many places in the style the parsing process to set this property.

inline bool IsStacked(const ComputedStyle& style) const { NOT_DESTROYED(); Return style.getPosition ()! Return style.getPosition ()! = EPosition::kStatic && IsStackingContext(style); } inline bool IsStackingContext(const ComputedStyle& style) const { NOT_DESTROYED(); // This is an inlined version of the following: // `IsStackingContextWithoutContainment() || // ShouldApplyLayoutContainment() || // ShouldApplyPaintContainment()` // The reason it is inlined is that the containment checks share // common logic, Which is extracted here to get repeated computation. / / judgment style IsStackingContextWithoutContainment attributes to return style.IsStackingContextWithoutContainment() || ((style.ContainsLayout() || style.ContainsPaint()) && (! IsInline() || IsAtomicInlineLevel()) && ! IsRubyText() && (! IsTablePart() || IsLayoutBlockFlow())); }Copy the code

The overall call link is:

​​

There are a lot of set IsStackingContextWithoutContainment properties, such as the transform3D property, will set the IsStackingContextWithoutContainment to true, We can go to watch the HasStackingGroupingProperty this function.

void ComputedStyle::UpdateIsStackingContextWithoutContainment( bool is_document_element, bool is_in_top_layer, bool is_svg_stacking) { if (IsStackingContextWithoutContainment()) return; // Force a stacking context for transform-style: preserve-3d. This happens // even if preserves-3d is ignored due to a 'grouping property' being present // which requires flattening. See: // ComputedStyle::HasGroupingPropertyForUsedTransformStyle3D(). // This is legacy behavior that is left ambiguous in the Official specs. // See https://crbug.com/663650 for more details. // Transform 3D style set to true if (TransformStyle3D() == ETransformStyle3D: : kPreserve3d) {/ / set IsStackingContextWithoutContainment SetIsStackingContextWithoutContainment(true); return; } / / document or root element contains StackingGroupingProperty attribute the if (is_document_element | | is_in_top_layer | | is_svg_stacking | | StyleType() == kPseudoIdBackdrop || HasTransformRelatedProperty() || HasStackingGroupingProperty(BoxReflect()) || HasViewportConstrainedPosition() || GetPosition() == EPosition::kSticky || HasPropertyThatCreatesStackingContext(WillChangeProperties()) || /* TODO(882625): This becomes unnecessary when will-change correctly takes into account active animations. */ IsStackingContextWithoutContainment ShouldCompositeForCurrentAnimations ()) {/ / Settings SetIsStackingContextWithoutContainment(true); }}Copy the code

HasStackingGroupingProperty function, every element with special style for ascension for rendering layer.

Bool HasStackingGroupingProperty (bool has_box_reflection const) {/ / opcaity attribute set to true if (HasNonInitialOpacity ()) return true; If (HasNonInitialFilter()) return true; if (has_box_reflection) return true; if (HasClipPath()) return true; if (HasIsolation()) return true; If (HasMask()) return true; // Mix-blendmode if (HasBlendMode()) return true; if (HasNonInitialBackdropFilter()) return true; return false; }Copy the code
  1. Paint Layer insert function

Once the render layer is created, we need to insert it into the existing render layer tree. The core process is as follows:

void PaintLayer::InsertOnlyThisLayerAfterStyleChange() { if (! parent_ && GetLayoutObject().Parent()) { // We need to connect ourselves when our layoutObject() has a parent. // Find EnclosingLayer and add ourselves. // Call Enclosing Layer PaintLayer* parent_layer = GetLayoutObject().Parent()->EnclosingLayer(); DCHECK(parent_layer); Call FindNextLayer PaintLayer* before_child = GetLayoutObject().parent ()->FindNextLayer(parent_layer, &GetLayoutObject()); Parent_layer ->AddChild(this, before_child); } code omission}Copy the code
  • 4. Call EnclosingLayer to find the PaintLayer corresponding to the parent of the LayoutObject associated with the newly created PaintLayer.
  • Call FindNext Layer to find the position of the Paint Layer to be inserted in the Child List of the parent Paint Layer. Before_child
  • Inserts the created Paint Layer into the Child List using header inserts.

For details, see Paint Layer Creation and Insertion

GraphicLayer

Based on the render layer tree, the browser will promote some special render layers to the Composite layer (G raphicLayers). The composite layer has a separate GraphicsLayer, and other render layers that are not composite layers share the same parent layer with their first GraphicsLayer, as shown below:

​​

advantage

Each GraphicsLayer actually has a GraphicsContext, which you can think of as a cache. The GraphicsContext caches the bitmap of the layer, and the next time you draw, the composition layer can draw directly from this cache without having to generate the drawing instructions. Bitmaps are stored in shared memory and uploaded to the GPU as textures.

Examples of synthetic layer formation

Note that these only work in chorme94, for reasons explained later.

Direct reason
  • 3dTranForm (translateZ, Rotate)
  • For iframe elements in different domains, hardware acceleration is used, using a composition layer.

The primary and subdomains of an iframe have different renderers, and the renderer has a root compositing layer by default, so the iframe element forms a separate compositing layer.

<! DOCTYPE html> <html> <head> <title>Composited frame test</title> <style type="text/css" media="screen"> #iframe { position: absolute; left: 100px; top: 100px; padding: 0; height: 300px; width: 400px; background-color: red; } </style> </head> <iframe id="iframe" src="composited-subframe.html" frameborder="0"></iframe> </html>Copy the code
  • Video element, 2D or 3D canvas layer
  • Backface – Visibility is hidden, so that the back of the element is visible to the observer. Details refer to
  • Animation or transition is applied to opacity, transform, fliter, and backdropfilter (animation or transition must be active, that is, the element in the animation, The animation or Transition layer fails before the animation or Transition effect starts or ends.
<! DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" /> <title>Test Animation</title> <style> .target { width: 80px; height: 80px; background-color: green; -webkit-animation: swing 5s linear 1; } @-webkit-keyframes swing { from { transform: rotate(0deg); } to { transform: rotate(90deg); } } </style> </head> <body> <div class="target"></div> </body> </html>Copy the code
  • Will-change is set to opacity, transform, top, left, bottom, and right (top, left, etc., need to set clear location properties, such as relative, etc., default location static will not generate a composite layer.
<! DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" /> <title>Test Will-change</title> <style> div { width: 150px; height: 30px; margin: 20px; padding: 10px; background-color: red; color: #fff; } .left { position: relative; will-change: left; } .top { position: relative; will-change: top; } .bottom { position: relative; will-change: bottom; } .right { position: relative; will-change: right; } .opacity { position: relative; will-change: opacity; } .transform { position: relative; will-change: transform; } </style> </head> <body> <div class="left">will-change: left</div> <div class="top">will-change: top</div> <div class="bottom">will-change: bottom</div> <div class="right">will-change: right</div> <div class="opacity">will-change: opacity</div> <div class="transform">will-change: transform</div> </body> </html>Copy the code
Descendant elements cause (kComboCompositedDescendants)
  • There is a composite layer descendant with opactiy (less than 1), mask, Fliter, Reflection and other attributes set.
<! DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" /> <title>Test Descendant</title> <style> .outer { width: 100px; height: 100px; border: 1px solid #000; margin: 20px; } .inner { width: 50px; height: 50px; margin: 20px; background-color: green; } .composited { will-change: transform; } .mask { -webkit-mask: linear-gradient(transparent, black); }.opacity {opacity: 0.5; } .reflection { -webkit-box-reflect: right 10px; } .filter { -webkit-filter: drop-shadow(-25px -25px 0 gray); } </style> </head> <body> <div class="outer filter"> filter <div class="inner composited">inner</div> </div> <div class="outer reflection"> reflection <div class="inner composited">inner</div> </div> <div class="outer opacity"> opacity <div class="inner composited">inner</div> </div> <div class="outer mask"> mask <div class="inner composited">inner</div> </div> </body> </html>Copy the code
  • The synthetic layer descendants of 3d Transfrom also have their own Poly3d properties.
<! DOCTYPE html> <html> <head> <style> .box { position: relative; height: 100px; width: 100px; margin: 10px; left: 0; top: 0; background-color: silver; } .preserve3d { width: 300px; border: 1px solid black; padding: 20px; margin: 10px; -webkit-transform-style: preserve-3d; } </style> </head> <body> <div class="preserve3d"> This layer should not be composited. <div class="box"></div> </div> </div> <div class="preserve3d"> This layer should not be composited. <div class="box" style="transform: rotate(10deg)"></div> </div> </div> <div class="preserve3d"> This layer should be composited. <div class="box" style="transform: rotateY(10deg)"></div> </div> </body> </html>Copy the code
  • Synthetic layer descendants that have 3DTransfrom also have perspective attributes themselves
Cause overlap

Let’s focus on why a composition layer is created when an element overlaps with the composition layer. In the original view, the top and bottom elements share the compositing layer resources of the parent element.

When the bottom layer for some reason forms a composite layer, the top element and the parent element are rendered first, followed by the bottom element, thus breaking the rule of cascading context as follows:

Therefore, the top elements must also be promoted to the composition layer to ensure that they are rendered in the correct order.

<! DOCTYPE html> <html> <head> <title>Test Overlap Filter</title> <! -- If the test passes, the light green drop shadow should appear over the the black div where they intersect. --> <style> #software { background-color: green; -webkit-filter: drop-shadow(25px 25px 0 lightgreen); position: absolute; top: 0; left: 0; width: 100px; height: 100px; color: #fff; } #composited { background-color: black; position: absolute; top: 105px; left: 105px; width: 100px; height: 100px; transform: translate3d(0, 0, 0); color: #fff; } </style> </head> <body> <div id="composited">composited</div> <div id="software">overlap</div> </body> </html>Copy the code
  • The transform overlaps with the composite layer
  • Overflow Scroll overlaps the composition layer. That is, if an element with overflow scroll (whether overflow:auto or overflow: Scroll, as long as it can) overlaps with the composition layer, then its visible child element overlaps with the composition layer
  • Assumed to be superimposed on a synthetic layer (assumet Lap)

For example, the CSS animation of an element may overlap with other elements during the animation. In this case, there is a reason for the synthesis layer of Assumet Lap. In this demo, the animation element does not visually overlap with its sibling element, but because of assumet Lap, its sibling element is still promoted to the synthesis layer.

<! DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" /> <title>Test Overlap Animation</title> <style> @-webkit-keyframes slide { from { transform: none; } to { transform: translateX(100px); } } .animating { width: 100px; height: 100px; background-color: orange; color: #fff; -webkit-animation: slide 10s alternate linear infinite; } .overlap { width: 100px; height: 100px; color: #fff; position: relative; margin: 10px; background-color: blue; } </style> </head> <body> <div class="animating">composited animating</div> <div class="overlap">overlap</div> </body> </html>Copy the code
Layer compression

Browsers also compress layers when certain conditions are met to prevent “too many composite layers”, as shown below:

<! DOCTYPE html> <head> <style> .composited { transform: translateZ(0); } .box { width: 100px; height: 100px; } .behind { position: absolute; z-index: 1; top: 100px; left: 100px; background-color: blue; } .middle { position: absolute; z-index: 1; top: 180px; left: 180px; background-color: lime; } .middle2 { position: absolute; z-index: 1; top: 260px; left: 260px; background-color: magenta; } .top { position: absolute; z-index: 1; top: 340px; left: 340px; background-color: cyan; } div:hover { background-color: green; transform:translatez(0); } </style> </head> <body> <div class="composited box behind"></div> <div class="box middle"></div> <div class="box middle2"></div> <div class="box top"></div> </body>Copy the code

When hover to the element of blue, blue elements will be promoted to composite layer, and the rest of the three elements, if according to the overlap, will produce three composite layer, but the browser will synthesize them into a layer, avoid the excessive waste of resources (because create a composite layer, will create a context to record, This increases the resource overhead of the browser.

​​

Unable to layer compress

However, there are cases in which browsers cannot layer compress and must have different compositing layers. There are several cases:

  • When rendering with composite layer with different cutting container (clippingcontainer), unable to compress the render layer (squashingClippingContainerMismatch)
<! DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" /> <title>Test</title> <style> .clipping-container { overflow: hidden; height: 10px; background-color: blue; } .composited { transform: translateZ(0); height: 10px; background-color: red; } .box1 { position: absolute; top: 0px; height: 100px; width: 100px; background-color: green; color: #fff; } .box2 { overflow: hidden; position: relative; top: -10px; } </style> </head> <body> <div class="clipping-container"> <div class="composited"></div> </div> <div Class ="box1"> The first will not be compressed on the composited div </div> <div class="box2"> The second will not be compressed on the composited div </div> </body> </ HTML >Copy the code

In the code above, composited elements have different clipped containers from Box1 and Box2, so when box1, box2 and Composited overlap, they are forced to create a separate composited layer and cannot be shared.

  • Unable to break rendering sequential compression (ksquashingWouldBreakPaintOrder)

  • Cannot be compressed video elements of rendering layer and at the same time other rendering layer cannot be compressed to synthetic video level (ksquashingVideoIsDisallowed)

  • The iframe, the plugin rendering layer cannot be compressed and at the same time other rendering layer cannot be compressed into its composite layer (ksquashingLayoutPartIsDisallowed)

  • Unable to compress a reflection properties of rendering layer (ksquashingReflectionDisallowed)

  • Unable to compress a blendmode property of rendering layer (ksquashingBlendingDisallowed)

The source code,

The whole process

The judgment and creation process of the overall synthetic layer is as follows:

​​

  1. UpdateAssignmentsIfNeeded entry function
Void PaintLayerCompositor: : UpdateAssignmentsIfNeeded (DocumentLifecycle: : LifecycleState target_state) {/ / update the dom life-cycle function  DCHECK(target_state >= DocumentLifecycle::kCompositingAssignmentsClean); Omit...... code if (update_type > = kCompositingUpdateAfterCompositingInputChange) { CompositingRequirementsUpdater(*layout_view_).Update(update_root); CompositingLayerAssigner layer_assigner(this); Create CompositingLayerMapping (Paint Layer to Graphic Layer mapping) layer_assigner.Assign(update_root, layers_needing_paint_invalidation); CHECK_EQ(compositing_, (bool)RootGraphicsLayer()); if (layer_assigner.LayersChanged()) update_type = std::max(update_type, kCompositingUpdateRebuildTree); } #if DCHECK_IS_ON() if (update_root->GetCompositingState() ! = kPaintsIntoOwnBacking) { AssertWholeTreeNotComposited(*update_root); } #endif GraphicsLayerUpdater updater; Update Graphic Layer properties upupdater. Update(*update_root, layers_needing_paint_invalidation); NeedsRebuildTree() update_type = STD :: Max (update_type,  kCompositingUpdateRebuildTree); if (update_type >= kCompositingUpdateRebuildTree) { GraphicsLayerVector child_list; { TRACE_EVENT0("blink", "GraphicsLayerTreeBuilder::rebuild"); GraphicsLayerTreeBuilder().Rebuild(*update_root, child_list); }Copy the code

One of the core is ComputeCompositedLayerUpdate, the function is the main function of multiple render mapping as a composite layer.

  1. ComputeCompositedLayerUpdate function

CompositingLayerAssigner::ComputeCompositedLayerUpdate(PaintLayer* layer) { CompositingStateTransitionType update = kNoCompositingStateChange; if (NeedsOwnBacking(layer)) { if (! layer->HasCompositedLayerMapping()) { // 1. You can create a Graphic the Layers update = kAllocateOwnCompositedLayerMapping; } } else { if (layer->HasCompositedLayerMapping()) // 2. Delete the Graphic the Layers update = kRemoveOwnCompositedLayerMapping; if (! layer->SubtreeIsInvisible() && layer->CanBeComposited() && RequiresSquashing(layer->GetCompositingReasons())) { // We can't compute at this time whether the squashing layer update is a // no-op, since that requires walking the paint layer tree. update = kPutInSquashingLayer; } else if (layer->GroupedMapping() || layer->LostGroupedMapping()) { // 3. Can be compressed layer update = kRemoveFromSquashingLayer; } } return update; }Copy the code

In ComputeCompositedLayerUpdate needsOwnBacking function, carry out synthetic layer is main reason of judgment.

Why GraphicLayers are formed

The reasons for the formation of compositing_reason are described in detail in Chromium’s Compositing_reason, which is similar to the classification above, mainly including direct reasons, layer stacking reasons and descendant element reasons.

Compositing_reason.h (Compositing_reason)

namespace blink { using CompositingReasons = uint64_t; #define FOR_EACH_COMPOSITING_REASON(V) \ /* Intrinsic reasons that can be known right away by the layer. */ //1. Direct Cause \ V(3DTransform) \ V(Trivial3DTransform) \ V(Video) \ V(Canvas) \ V(Plugin) \ V(IFrame) \ V(DocumentTransitionContentElement) \ /* This is used for pre-CompositAfterPaint + CompositeSVG only. */ \ V(SVGRoot) \ V(BackfaceVisibilityHidden) \ V(ActiveTransformAnimation) \ V(ActiveOpacityAnimation) \ V(ActiveFilterAnimation) \ V(ActiveBackdropFilterAnimation) \ V(AffectedByOuterViewportBoundsDelta) \ V(FixedPosition) \ V(StickyPosition) \ \ V(OutOfFlowClipping) \ V(VideoOverlay) \ V(WillChangeTransform) \ V(WillChangeOpacity) \ V(WillChangeFilter) \ V(WillChangeBackdropFilter) \ \ /* Reasons that depend on ancestor properties */ \ V(BackfaceInvisibility3DAncestor) \ /* TODO(crbug.com/1256990): Transform3DSceneLeaf today depends only on the \ element and its properties, but in the future it could be optimized \ to consider descendants and moved to the subtree group below. */ \ V(Transform3DSceneLeaf) \ /* This flag is needed only when none of the explicit kWillChange* reasons \ are set. */ \ V(WillChangeOther) \ V(BackdropFilter) \ V(BackdropFilterMask) \ V(RootScroller) \ V(XrOverlay) \ V(Viewport) \ // 2. \ /* Overlap reasons that require knowing what's behind you in paint-order \ before knowing the answer. */ \ V(AssumedOverlap) \ V(Overlap) \ V(NegativeZIndexChildren) \ V(SquashingDisallowed) \ \ /* Subtree reasons that require Knowing what the status of your subtree is \ before knowing the answer. */ 3 V(OpacityWithCompositedDescendants) \ V(MaskWithCompositedDescendants) \ V(ReflectionWithCompositedDescendants) \ V(FilterWithCompositedDescendants) \ V(BlendingWithCompositedDescendants) \ V(PerspectiveWith3DDescendants) \ V(Preserve3DWith3DDescendants) \ V(IsolateCompositedDescendants) \ V(FullscreenVideoWithCompositedDescendants) \Copy the code

Compositing_reason.cc (mapping)

The reasons for forming compositing layers are also described in the compositing_reason.cc, which corresponds to the compositing reasons we see in the Layer module of the console.

​​

Drawing stage

Current algorithm flow

from layout | v +------------------------------+ | LayoutObject/PaintLayer tree |-----------+ +------------------------------+ | | | | PaintLayerCompositor::UpdateIfNeeded() | | CompositingInputsUpdater::Update() |  | CompositingLayerAssigner::Assign() | | GraphicsLayerUpdater::Update() | PrePaintTreeWalk::Walk() | GraphicsLayerTreeBuilder::Rebuild() | PaintPropertyTreeBuider::UpdatePropertiesForSelf() v | +--------------------+ +------------------+ | GraphicsLayer tree |<------------------| Property trees | +--------------------+ +------------------+ | | | |<-----------------------------------+ | | LocalFrameView::PaintTree() | | LocalFrameView::PaintGraphicsLayerRecursively() | | GraphicsLayer::Paint() | | CompositedLayerMapping::PaintContents() |  | PaintLayerPainter::PaintLayerContents() | | ObjectPainter::Paint() | v | +---------------------------------+ | | DisplayItemList/PaintChunk list | | +---------------------------------+ | | | |<--------------------------------------------------+ | PaintChunksToCcLayer::Convert() | v | +--------------------------------------------------+ | | GraphicsLayerDisplayItem/ForeignLayerDisplayItem | | +--------------------------------------------------+ | | | | LocalFrameView::PushPaintArtifactToCompositor() | | PaintArtifactCompositor::Update() | +--------------------+ +--------------------------+ | | v v +----------------+ +-----------------------+ | cc::Layer list | | cc property trees | +----------------+ +-----------------------+ | | +-------------+--------------+ | to compositorCopy the code

The current rendering process of the chorme looks something like this. After layering, we operate on a compositing layer to generate a compositing layer of draw instructions. The order between these draw instructions tells us what to draw first and then in the current compositing layer. To ensure that multiple render layers (i.e., multiple cascading contexts) on a composite layer are rendered sequentially according to established rules, as shown below:

​​

We render the root context first, then the negative Z-index context, then some layout elements such as float, block-level elements, then the positive Z-index and inline elements (because the content is the most important and needs to be seen by the user).

For example,

<! DOCTYPE html> <html lang="en"> <meta http-equiv="Content-Type" content="text/html; Charset=UTF-8" /> <head> <style type="text/css"> * { margin: 0; padding: 0; } div { width: 200px; height: 100px; text-align: center; line-height: 100px; } p { height: 40px; line-height: 40px; font-size: 20px; margin-bottom: 30px; } .level-default { position: absolute; background: #f5cec7; top: 60px; } .level1 { background: #ffb284; position: absolute; z-index: 2; top: 160px; } .level2 { background: #e79796; position: absolute; z-index: 1; top: 260px; } .composite-1 { position: relative; transform: translateZ(0); width: 300px; height: 400px; background: #ddd; margin-bottom: 20px; } </style> </head> <body> <div class="composite-1"> <p> <div class="level-default"> default layer </div> <div Class ="level1"> Render layer 1: z-index:2</div> <div class="level2"> Render layer 2: z-index:1</div> </div>Copy the code

Let’s use the above code as an example to see if the render layer on composition-1 is drawn in the order mentioned above. The final drawing product is as follows:

Check the process

The paint instructions can be viewed in the Layers tool with PaintProfiler.

Draw the instruction

Each instruction reference can also refer to the instructions of flutter analysis, API. Flutter. Dev/flutter/dar… UI /Canvas/restore. HTML is roughly the same as the definition of a draw directive in a browser.

Performance optimization

The above mentioned reasons for the formation of so many synthetic layers, as well as the advantages of the synthetic layer, we should of course make reasonable use of it to improve our page performance, which can be divided into the following two levels:

The code level

  1. Different CSS properties will trigger the process csstriggers.com/, from the website can be seen that the modification of the value of each property will trigger what process, for example, we are familiar with the top, once modified, it will go through the layout, drawing, synthesis, etc., equivalent to the hierarchical stage of the process will go through again, so it is extremely performance loss.

​​

To modify the transform attribute, we only need to perform some transformation operations on the composite layer, such as the transformation of displacement or transparency. The layout and drawing commands can use the data in the cache, so the performance is greatly improved.

​​

The position of animation:

<! DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" /> <meta http-equiv="X-UA-Compatible" content="IE=edge" /> <meta name="viewport" content="width=device-width, Word-wrap: break-word! Important; "/> <title>Document</ style>. height: 100px; animation: run-around 2s linear alternate 100; background: red; position: absolute; border-radius: 50%; } @keyframes run-around { 0% { top: 0; left: 0; } 25% { top: 0; left: 200px; } 50% { top: 200px; left: 200px; } 75% { top: 200px; left: 0; } 100% { top: 0px; left: 0px; } } </style> </head> <body> <div class="ball-running"></div> </body> </html>Copy the code

Transform the animation:

<! DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" /> <meta http-equiv="X-UA-Compatible" content="IE=edge" /> <meta name="viewport" content="width=device-width, Word-wrap: break-word! Important; "/> <title>Document</ style>. height: 100px; animation: run-around 2s linear alternate 100; background: red; position: absolute; border-radius: 50%; } .ball-running { width: 100px; height: 100px; animation: run-around 2s linear alternate 100; background: red; border-radius: 50%; } @keyframes run-around { 0% { transform: translate(0, 0); } 25% { transform: translate(200px, 0); } 50% { transform: translate(200px, 200px); } 75% { transform: translate(0, 200px); } } </style> </head> <body> <div class="ball-running"></div> </body> </html>Copy the code
The performance comparison

We can also compare the left animation with the Transform animation using the same CPU power. As can be seen below, the second half of the left animation will lose frames, and the FPS of the entire page will be greatly reduced, which is only one element. If a bunch of elements are rearranged, The page is bound to get very staid, whereas the Transform animation will maintain a high F-PS and will not lose frames, ensuring a smooth page.

left:

transform:

Create a composition layer

The process of creating a composite layer is also relatively simple, just for the reasons we described above.

#target {
  transform: translateZ(0);
}
#target {
  will-change: transform;
}
Copy the code
Synthetic layer explosion

In some cases, due to improper operation, too many composite layers are generated, which greatly consumes the resources of the page, resulting in page lag and other problems, as shown below.

<! DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" /> <title>Layer Explosion</title> <style> @-webkit-keyframes  slide { from { transform: none; } to { transform: translateX(100px); } } .animating { width: 300px; height: 30px; background-color: orange; color: #fff; -webkit-animation: slide 5s alternate linear infinite; } ul { padding: 5px; border: 1px solid #000; } .box { width: 600px; height: 30px; margin-bottom: 5px; background-color: blue; color: #fff; position: relative; / * will lead to can't compress: squashingClippingContainerMismatch * / overflow: hidden; } .inner { position: absolute; top: 2px; left: 2px; font-size: 16px; line-height: 16px; padding: 2px; margin: 0; background-color: green; } </style> </head> <body> <div class="animating">composited animating</div> <ul id="list"></ul> <script> var template = function (i) { return [ '<li class="box">', '<p class="inner">asume overlap, Because squashingClippingContainerMismatch cannot be compressed < / p > ', "< / li >",]. Join (" "); }; var size = 200; var html = ""; for (var i = 0; i < size; i++) { html += template(i); } document.getElementById("list").innerHTML = html; </script> </body> </html>Copy the code

Each li element above is forced to be promoted to a composition layer due to assumed overlap, and they have different clipping containers between them, so layer compression cannot be carried out. Therefore, each Li element generates a composition layer. We have a floor explosion.

So we had to break the layer explosion, and the first way to do that was to increase the Z-index of the animated element and make sure it was overlaid on top of the Li element, so that the Li elements wouldn’t be promoted to composite layers because of the assumption of overlap.

  • Generate a cascading context to promote the z-index property:
.animating { ... /* Let other elements not overlap the composition layer */ position: relative; z-index: 1; }Copy the code
  • Remove squashingClippingContainerMismatch:

The second method is to remove overflow so that each LI element has the same clipping container, so that the condition of layer compression is satisfied and the layer explosion is avoided.

.box { width: 600px; height: 30px; margin-bottom: 5px; background-color: blue; color: #fff; position: relative; / * will lead to can't compress: squashingClippingContainerMismatch * / overflow: hidden; }Copy the code

Browser level

At the browser level, also have to help us do a lot of optimization, for example, in chrome94 and above version, the new browser rendering algorithm has been used by the full amount, different from the original rendering algorithm, the new algorithm can after drawing instruction generation, for layering, this can greatly reduce because the probability of overlap and forced to ascend for synthetic layer, Here’s how the new browser rendering algorithm flows:

from layout | v +------------------------------+ | LayoutObject/PaintLayer tree | +------------------------------+ | | |  | PrePaintTreeWalk::Walk() | | PaintPropertyTreeBuider::UpdatePropertiesForSelf() | v | +--------------------------------+ |<--| Property trees | | +--------------------------------+ | | | LocalFrameView::PaintTree() | | FramePainter::Paint() | | PaintLayerPainter::Paint() | | ObjectPainter::Paint() | v | +---------------------------------+ | | DisplayItemList/PaintChunk list | | +---------------------------------+ | | | |<---------------------------------+ | LocalFrameView::PushPaintArtifactToCompositor() | PaintArtifactCompositor::Update() | +---+---------------------------------+ | v | | +----------------------+ | | | Chunk  list for layer | | | +----------------------+ | | | | | | PaintChunksToCcLayer::Convert() | v v v +----------------+ +-----------------------+ | cc::Layer list | | cc property trees | +----------------+ +-----------------------+ | | +------------------+ | to compositor vCopy the code

The above layer explosion example appears in the chorme94 version, where li elements share a composite layer. Thank you very much, Google Dad.

​​

There are also a number of new features in chrome94, such as the SchedulerAPI, which allows developers to control the priorities of tasks. In the React framework, the SchedulerAPI allows developers to schedule tasks. Blog.chromium.org/2021/10/ren…

The resources

In the process of writing this article, I also referred to a lot of information, as shown below. I recommend the second, third and sixth articles, which are very detailed about overlapping fine lines, synthetic layers and rendering. I would like to thank the original author of each article ~

  1. Debugging chromium:zhuanlan.zhihu.com/p/260645423
  2. Cascading context: www.zhangxinxu.com/wordpress/2…
  3. Example: synthetic layer fed.taobao.org/blog/2016/0…
  4. RenderNG data structures: developer.chrome.com/blog/render…
  5. PaintLayer and create new: GraphicLayer zhuanlan.zhihu.com/p/48515392 >…
  6. Pixel docs.google.com/presentatio my life…