Bug 215087 (LFC) - Next Generation Layout
Summary: Next Generation Layout
Status: NEW
Alias: LFC
Product: WebKit
Classification: Unclassified
Component: Layout and Rendering (show other bugs)
Version: WebKit Nightly Build
Hardware: Unspecified Unspecified
: P2 Normal
Assignee: zalan
URL:
Keywords: InRadar
Depends on: 188522 188775 192289 192756 194932 201378 205000 205601 206158 206168 206336 206489 206813 207447 213978 214406 214474 214520 214527 214582 214720 214728 214733 214779 214784 214785 214790 215016 215048 215068
Blocks:
  Show dependency treegraph
 
Reported: 2020-08-03 09:38 PDT by Sam Sneddon [:gsnedders]
Modified: 2020-09-06 20:29 PDT (History)
8 users (show)

See Also:


Attachments

Note You need to log in before you can comment on or make changes to this bug.
Description Sam Sneddon [:gsnedders] 2020-08-03 09:38:25 PDT
See the the "Layout!" section of https://trac.webkit.org/wiki/NextGenerationLayoutAndRendering
Comment 1 zalan 2020-08-08 21:52:25 PDT
- Layout rules are defined by various layout formatting contexts [LFC].
- Layout always runs within a formatting context.
- Block layout happens inside the Block Formatting Context [BFC], inline layout happens inside the Inline Formatting Context [IFC] etc.
- Layout operates on an immutable [Layout:Box] tree and generates a list of [Display::Box]es and [Display::Run]s
- Each node in the [Layout::Box] tree belongs to a formatting context (except the root of the [Layout::Box] tree, the initial containing block)
- Nodes in the [Layout::Box] tree may establish new formatting contexts.
- The initial containing block (ICB) establishes the Initial Block Formatting Context.
- Formatting contexts compute geometry only for nodes that live in that particular formatting context. 
- When a node establishes a new formatting context, this new formatting content will not run layout on the node itself, only on the descendant tree. 
- Layout runs iteratively within a formatting context and recursively across nested formatting contexts.

  simple example:
  <div id=container>
    <div id=first><span>text</span>content</div>
    <div id=second><img></div>
  </div

  - The ICB establishes the initial block formatting context (IBFC).
  - 'container', 'first' and 'second' live in the IBFC (ignore container's ancestors for now).
  - both 'first' and 'second' establish IFCs for their inline content.
  - <span> and the text both live in the IFC established by 'first'.
  - <img> lives in the IFC established by 'second'.

  Layout flow:
    IBFC runs layout on block content:
    [block layout starts]
      1. computes static position, border, padding and horizontal geometry for 'container'
      2. computes static position, border, padding and horizontal geometry for 'first'
      3. calls IFC::layout(horizontal constraints) on the IFC established by 'first'
      IFC runs layout on inline content
      [inline layout starts]
        1. creates runs and builds line box(es).
      [inline layout ends] 
      4. computes vertical geometry for 'first' by looking at the line box(es) generated by the IFC
      5. repeat #2 -> #4 for 'second'
      6. computes vertical geometry for 'container'
      ...
    [block layout ends]
    Note: while both 'first' and 'second' establish new formatting contexts, they are sized and positioned by the IBFC (the formatting context they live in, not the ones they create).

- Formatting contexts may have associated FloatingContexts.
- BFCs always have FloatingContexts, while IFCs inherit FloatingContexts from their parent BFCs. 
- FloatingContext is responsible for placing the floats (imagine it as a special formatting context for floats).
- Float boxes in FloatingContexts are scoped to the formatting context they belong to. 
  simple example:
  <div id=absolute style="position: absolute">
    <div id=float style="float: left"></div>
    <div id=inline>inline content</div>
    <div id=bfc style="overflow: hidden">some more inline content</div>
  </div>
  
  1. <div> 'absolute' establishes a BFC and it also establishes a new (empty) FloatingContext.
  2. the FloatingContext gains 'float' and computes its geometry (note that 'float' also establishes a BFC but it is empty so nothing to layou in there).
  3. <div> 'inline' establishes an IFC and this new IFC inherits the parent BFC's FloatingContext (with the 'float' box in it).
  4. IFC takes the float box in the FloatingContext into account while computing line constraints. 
  5. <div> 'bfc' establishes both a BFC (overflow: hidden) and an IFC (inline content). Because it establishes a BFC, it does not inherit the parent BFC's FloatingContext, but instead it creates a new one.
  6. while laying out the inline content of the <div> 'bfc', this IFC will not see any intrusive float from the parent BFC.
  (note that since <div> 'bfc' establishes a BFC, it becomes a float avoider and it is positioned accordingly)
  
- Formatting contexts use formatting states for subsequent layouts (e.g: BFC caches preferred widths, IFC caches inline items and TFC caches the table grid)
- Invalidation operates on the formatting states.
- Formatting contexts may specialize further to support certain special formatting behaviors (e.g tables and their captions are wrapped inside table wapper boxes. These tables wrapper boxes establish BFCs. However they require somewhat special inflow content handling, see TableWrapperBlockFormattingContext)
- Perpendicular writing modes establish new BFCs. Coordinate flipping happens on the formatting context boundary.
- Independent formatting contexts may be laid out in a concurrent fashion.
- Throw-away layouts may be performed in a concurrent fashion by creating dedicated layout contexts (see LayoutContext).
- [Display::Box] tree may be cloned and run hit-test, paint while performing subsequent layouts.
Comment 2 zalan 2020-08-09 10:43:00 PDT
                                         
                                         
                              Layout State (Block Formatting State, Inline Formatting State, Floating State etc)
                         <-------<----------<----------<----------<---
                        /                                              \
                       /                 LayoutContext                  \
                       \        __________________________________      /
                        \      |     _____                        |    /
                          ---> |    |     |              _____    | --   
                               |    | BFC |             |     |   |
immutable Layout::Box tree --> |    |_____|     _____   | IFC |   |
                               |     ^         |     |  |_____|   | -----> Display::Box tree
                               |     |         | TFC |    ^       |  
                               |     |         |_____|    |       |
                               |   ____                   |       |
                               |  |    |                  |       |
                               |  | FC |------------------        |
                               |  |____|                          |
                               |__________________________________|

BFC -> Block Formatting Context
IFC -> Inline Formatting Context
TFC -> Table Formatting Context
FC -> Floating Context

LayoutContext -> performs layout on the Layout::Box tree by constructing Formatting Context objects as required by the Layout::Box nodes.
Layout State -> collection of Block Formatting State, Inline Formatting State, Floating State objects to ensure performant incremental layouts.

Layout::Box tree:

  <div style="overflow: hidden">text1</div>
  <div>
    <div style="float: left"></div>
    text2
  </div>

ICB 
 |
  --> ContainerBox(html) 
            |
             --> ContainerBox(body)
                      |
                       --> ContainerBox(div style="overflow: hidden") 
                      |           |
                      |            --> Anonymous Inline Box (text1)
                      |
                       --> ContainerBox(div) 
                                  |
                                   --> ContainerBox(div style="float: left")
                                  |
                                  |
                                   --> Anonymous Block Box
                                             |
                                              --> Anonymous Inline Box (text2)


Initial Block Formatting Context(ICB)   <- Floating Context(new)
               |
                --> IFC (div style="overflow: hidden") <- Floating Context(new) 
               |
               |
                --> BFC (div style="float: left") <- Floating Context(new)
               |
               |
                --> IFC (Anonymous Block Box) <- Floating Context (inherited from Initial Block Formatting Context)
                
IBFC computes geometry for:
  - ContainerBox(html)
  - ContainerBox(body)
  - ContainerBox(div style="overflow: hidden")
  - ContainerBox(div)
  - ContainerBox(div style="float: left")
  - Anonymous Block Box
  
IFC established by ContainerBox(div style="overflow: hidden") computes geometry for:
  - Anonymous Inline Box(text1)

BFC established by ContainerBox(div style="float: left") computes geometry for:
  no descendant content -> no layout.

IFC established by Anonymous Block Box computes geometry for:
  - Anonymous Inline Box(text2)
Comment 3 Radar WebKit Bug Importer 2020-08-10 09:39:21 PDT
<rdar://problem/66784432>
Comment 4 zalan 2020-08-10 20:46:23 PDT
Floats:

Float boxes live in FloatingContexts.
Float boxes are added to the FloatingContext in document order.
FloatingContexts are assigned to one or many Formatting Contexts.
Float boxes in a particular FloatingContext can affect geometry on boxes that live in the assigned Formatting Context only (e.g FloatingContext(A) is assigned to Block Formatting Context(A), boxes in Block Formatting Context(B) will not be affected by any of the float boxes in FloatingContext(A)).
BFC always initiates a new, empty FloatingContext.
IFCs always inherit the FloatingContexts from their parent BFCs.
FloatingContexts are inherited by reference meaning that when an IFC adds a new float box to the inherited FloatingContext, this float box will be visible in the parent BFC and in any (next)sibling IFCs.  

<div style="float: left">FloatBox A</div>
<div style="overflow: hidden">
  <div style="float: right">FloatBox B</div>
  <div>FloatBox B is intrusive here but FloatBox A is not</div>
</div>
<div>FloatBox B has no power in here, but FloatBox A does.<div>

Formatting context tree:
IBFC(1) (Initial Containing Block) <- FloatingContext(1)
               |
                --> BFC/IFC(2) (div style="float: left") <- FloatingContext(2)
               |
               |
                --> BFC(3) (div style="overflow: hidden") <- FloatingContext(3)
               |      |
               |      |
               |       --> BFC/IFC(4) (div style="float: right") <- FloatingContext(4)
               |      |
               |      |
               |       --> IFC(5) (div 'FloatBox B is intrusive...') <- Inherits FloatingContext(3)
               |       
                --> IFC(6) (div 'FloatBox B has no...') < Inherits FloatingContext(1)
                
FloatingContext(1) has:
 FloatBox A

FloatingContext(3) has:
 FloatBox B
 
FloatingContext(2) and FloatingContext(4) are empty.

Layout flow:
IBFC(1) layout starts.
    Initiates FloatingContext(1). 
    FloatingContext(1) gains a new float box (FloatBox A).
    IFC(2) layout starts - ends.
    BFC(3) layout starts.
        Initiates FloatingContext(3).
        FloatingContext(3) gains a new float box (FloatBox B).
        IFC(4) layout starts - ends.
        IFC(5) layout starts.
            Inherits FloatingContext(3).
            Inline layout sees FloatBox B only and shrinks the available horizontal space for the line accordingly. 
        IFC(5) layout ends.
    BFC(3) layout ends.
    IFC(6) layout starts.
        Inherits FloatingContext(1).
        Inline layout sees FloatBox A only and shrinks the available horizontal space for the line accordingly. 
    IFC(6) layout ends.
IBFC(1) layout ends.

Scoping float boxes to formatting context helps when looking for intrusive floats. When a float is present in the FloatingContext it is intrusive, otherwise it's not.
FloatingContexts keep track of float boxes in the coordinate system of the root of the formatting context (as opposed to the coordinate system of the float box's containing block).
Intersecting float boxes with other boxes (e.g to figure out if line needs to shrink) is simply a matter of translating the box into the formatting context's coordinate system and intersect.
BFCs are float avoiders (they never overlap float boxes). This ensures that their descendant formatting contexts won't need to worry about float boxes outside the BFC (hence they can always initiate an empty FloatingContext)
(Note that float boxes are BFCs too, and they also avoid other float boxes by just being BFCs)

How BFCs avoid floats:
<div style="float: left">FloatBox A</div>
<div>FloatBox A is intrusive</div>

vs.

<div style="float: left">FloatBox A</div>
<div style="overflow: hidden">FloatBox A is _NOT_ intrusive</div>

In the first case, the second <div> establishes an IFC. This IFC inherits the parent FloatingContext with the FloatBox A in it. 
The <div> overlaps the float box and the inline content avoids FloatBox A by adjusting the line's horizontal constraints.
However when the second <div> gains "overflow: hidden", it starts establishing a BFC and it becomes a float avoider.
When the <div>'s position is computed (in the BFC it lives), we take this newly gained property into account and position the <div> in a way that it won't overlap FloatBox A anymore. 
It initiates an empty FloatingContext and the child IFC inherits it. The child IFC won't see the FloatBox A when laying out the inline content. It does not need to avoid any float boxes anymore.
Comment 5 zalan 2020-08-11 09:06:34 PDT
the last example begs for ascii art.
 ______________________________________
|            |                         |
| FloatBox A | FloatBox A is intrusive |
|____________|_________________________|

vs.
 ____________  _______________________________
|            ||                               |
| FloatBox A || FloatBox A is _NOT_ intrusive |
|____________||_______________________________|
Comment 6 zalan 2020-08-12 17:26:05 PDT
Layout::Box tree (let's call it [layout tree], though the name is somewhat misleading as this tree has no layout information, it's input to the layout process)

- The layout tree builder constructs the [layout tree] and mutates it as needed. Both the construction and the mutation operations are strictly outside of the context of layout.
- Nodes in the [layout tree] have very simple, mostly const interface.
- Nodes in the [layout tree] can't position, size, paint, select, drag, relocate themselves. They can't perform any of these operations on their subtrees or any random part of the [layout tree] (no, not even the mighty multicol spanner is able to mutate the [layout tree]).
- What nodes can pretty much only do is advise the FormattingContexts about the type of layout they need to perform on their descendants (see e.g. Box::establishesBlockFormattingContext())
- Node class hierarchy is as follows: (extremely lightly typed at this point, but not planning to extend it too much)

Box --> ContainerBox --> InitialContainingBlock(final)
   |
   |
    --> InlineTextBox(final)
   |
   |
    --> LineBreakBox(final)
   |
   |
    --> ReplacedBox(final)

- A Box object may have next, previous siblings and a parent.
- A ContainerBox object may have children.
- Every node in the [layout tree] has a parent, a containing block and a formatting context root (they may all be the same ContainerBox object), except the InitialContainingBlock.  
- InitialContainingBlock is the root of the [layout tree], it has no parent.
- However Box::parent(), containingBlock() and formattingContextRoot() all return 'const ContainerBox&'. Layout code should _never_ need to call these functions on the ICB (never ever).
- [Layout tree] is input to the layout process through the LayoutState/LayoutContext object pair. LayoutState has a weak reference to the ICB and LayoutContext::layout() operates on the LayoutState.
- Layout always starts at a formatting context root. Formatting context roots are always ContainerBox types.
- Multiple formatting context roots may be used as entry points to LayoutContext::layout().
- Layout may operate on multiple (independent)formatting context subtrees in a concurrent fashion.

Simple example:

<div>text <span>content</span></div>
<div>before<div></div>after</div>

generates the following [layout tree]

ICB --> ContainerBox(html) --> ContainerBox(body) --> ContainerBox(div) --> Anonymous InlineTextBox(text)
                                                 |                     |
                                                 |                     |
                                                 |                      --> ContainerBox(span) --> Anonymous InlineTextBox(content)
                                                 |
                                                  --> ContainerBox(div) --> Anonymous ContainerBox(pre-block) --> Anonymous InlineTextBox(before)
                                                                       |
                                                                       |
                                                                        --> ContainerBox(div)
                                                                       |
                                                                       |
                                                                        --> Anonymous ContainerBox(post-block) --> Anonymous InlineTextBox(after)
Comment 7 zalan 2020-08-14 21:48:11 PDT
Display::Box tree
- it's not a tree (it will be probably be at some point), display objects can currently be found in a HashMap of [Layout::Box, Display::Box] in LayoutState.
- Display::Box holds the classic CSS box model properties (margin, border, padding and content box).
- Display::Run and Display::LineBox objects are constructed for inline content.
- Display objects are being created during layout, mostly explicitly at the first geometry access.
- Layout code collects previously computed geometry information by looking at Display objects.   
- Layout code (FormattingContext) can access mutable Display objects through the associated FormattingState (Display::Box& FormattingState::displayBox()).

BlockFormattingContext::computeHeightAndMargin()
{
   ...
    auto& displayBox = formattingState().displayBox(layoutBox);
    displayBox.setContentBoxHeight(contentHeightAndMargin.contentHeight)
}

- FormattingContext code can access mutable Display objects only within the same formatting context subtree. Layout code should not need to mutate geometry outside of its own formatting context.
- Layout code (FormattingContext) can also access immutable Display objects through the FormattingContext::geometryForBox() call.

BlockFormattingContext::Geometry::staticVerticalPosition()
{
    ...
    if (auto* previousInFlowSibling = layoutBox.previousInFlowSibling()) {
        auto& previousInFlowBoxGeometry = formattingContext().geometryForBox(*previousInFlowSibling);
        return previousInFlowBoxGeometry.bottom() + previousInFlowBoxGeometry.marginAfter();
    }
}

- Normally layout code should only need to access geometry within the same formatting context.
- However in certain (valid) cases, it's okay for the layout code to access (immutable) geometry information outside of the current formatting context (quirks, floats etc). The caller needs to pass in a valid EscapeReason to do so (e.g FindFixedHeightAncestorQuirk, OutOfFlowBoxNeedsInFlowGeometry etc).
- Layout code should only access previously computed geometry information. Display::Box asserts when the layout code incorrectly gathers uninitialized geometry. It ensures that the layout code is not doing something silly, like trying to get the geometry off of the next sibling.
Comment 8 zalan 2020-08-30 17:08:36 PDT
Inline formatting contexts (IFC) are established by block container boxes (when they only contain inline content):

Each IFC has at least one line box.
Each line box has at least one inline box.
The inline box that every line box has to have is called root inline box.
Inline boxes are always initiated by inline content (e.g. inline replaced boxes, inline containers etc).

This is the simplest setup an IFC can have:

<div>FOO</div>

Block Container Box (div)
           |
            --> Anonymous Inline Box ("FOO")
 - The Block Container Box establishes an IFC.
 - The Anonymous Inline Box initiates an inline box (Root Inline Box)
 
   ___________________________________________ Line Box
 | ---------------------------------------- Root Inline Box
 ||   _____    ___      ___                    
 ||  |        /   \    /   \ 
 ||  |_____  |     |  |     |           ascent
 ||  |       |     |  |     |
 ||__|________\___/____\___/_____________ alignment_baseline
 ||                                    
 ||                                     descent
 ||___________________________________________________
 |_____________________________________________________ <- this is not an actual gap
          
Inline boxes may stretch the line box vertically (1).
Inline boxes never stretch the line box horizontally (instead, they overflow the line box).
The line box is always constrained by the IFC root (Block Container Box) horizontally. 
The line box may be constrained by the line-height property vertically, is such cases the inline boxes, including the root inline box may overflow the line box (glyphs in subsequent lines may overlap each other)
Inline boxes can be nested, they may have inline box parents. The root inline box has no parent.

<div>
  <span id=outer>
    <img>
    <span id=inner></span>
  </span>
</div>
inline boxes: [root inline box] [span id=outer] [img] [span id=inner]
root inline box
        |
         --> inline box [span id=outer]
                       |
                       |
                        --> inline box [img]
                       |
                        --> inline box [span id=inner]  

Root inline boxes are always baseline aligned, their vertical positions are computed using the half leading method.
Some vertical-align values make the inline box stretch its parent inline box, some others stretch the line box directly (e.g top/bottom).

In standards mode, the root inline box always starts with an imaginary strut.
This imaginary strut (invisible glyph) sets the initial vertical geometry for the root inline box.

<div style="font-size: 100px;">
  <img style="width: 50px; height: 50px;">
</div>

In standards mode the imaginary struct stretches the root inline box (let's assume actual height of 100px with 20px descent):
  ____________________________________________ Line Box
 | ---------------------------------------- Root Inline Box
 ||   ^            
 ||   |  <- imaginary strut (100px)    
 ||   |      ---------     
 ||   |     |         |    
 ||   |     |   img   |  50px
 ||   |     |         |                    
 ||   |     |         |    
 || -------  ---------  ----------------- alignment baseline
 ||   |        
 | ----------------------------------------     
  -------------------------------------------- <- no actual gaps in here

However in quirks mode this markup produces a 50px tall line box:
  ____________________________________________ Line Box
 | ---------------------------------------- Root Inline Box
 || ---------     ^
 |||         |    |
 |||   img   |    |
 |||         |   50px                 
 |||         |    |
 || ---------  ------------------------- alignment baseline
 | ----------------------------------------  
  -------------------------------------------- <- no actual gaps in here
 (note that the root inline box now does not even have descent space)

Every inline run belongs to one and only one inline box.

(1) In trunk/rendering: 
1. assume the line-height is set to normal -> while the line height is computed using the FontMetrics::lineSpacing(), the root inline box's height is FontMetrics:ascent() + descent().
With positive line gap value (font's property, rather common), this could lead to some mildly interesting off-by-one errors, where the line is slightly taller (e.g 1px) than the root inline box.
Comment 9 zalan 2020-08-31 08:00:33 PDT
> (1) In trunk/rendering: 
> 1. assume the line-height is set to normal -> while the line height is
> computed using the FontMetrics::lineSpacing(), the root inline box's height
> is FontMetrics:ascent() + descent().
> With positive line gap value (font's property, rather common), this could
> lead to some mildly interesting off-by-one errors, where the line is
> slightly taller (e.g 1px) than the root inline box.
Alternatively we could let the LineBox fully contain the inline boxes (this would be closer to the spec language) and move the concept of overflow and the line-height/font's line gap outside of LineBox to something like a Line (which we don't yet have, but Display::LineBox is the great candidate -need to rename it to Display::Line first).
It would eliminate the confusing FontMetrics::ascent() + descent() vs. FontMetrics::lineSpacing() setup and make the LineBox geometry easier to understand.
Comment 10 zalan 2020-09-06 20:29:52 PDT
> Alternatively we could let the LineBox fully contain the inline boxes (this
> would be closer to the spec language) and move the concept of overflow and
> the line-height/font's line gap outside of LineBox to something like a Line
r266682 implements this.
- Layout::LineBox fully contains the horizontally and vertically aligned inline boxes.
- Layout::LineBox has only width/height geometry. It does not position itself in the context of the current line.
- Line height and the LineBox vertical offset is computed and the Display::Runs are moved accordingly.

Simple case.
 <div>font went crazy with line spacing</div>
  ________________________________________________  line
 |                ^                   ^           |
 |                | line spacing      |           |
 |                v                   |           |
 | -----------------------------------|-----------|---------  LineBox
 ||    ^                              |           |         |
 ||    | line box height              |line height|         |      
 ||----v------------------------------|-----------|-------- | alignment baseline
 | -----------------------------------|-----------|---------
 |                ^                   |           |   ^
 |                | line spacing      |           |   |
 |________________v___________________v___________|  scrollable overflow


 When glyphs in subsequent lines overflow:
 <div style="line-height: 5px; font-size: 20px;">line box overflows the line</div>
   ____________________________________________  LineBox
  |                          ^                 |
  |                          | line box height |
  |                          |                 |
 -|--------------------------|-----------------|-  Line
| |     ^                    |                 | |
| |     | line height        |                 | |
 -|-----v--------------------|-----------------|-
  |                          |                 |
  |--------------------------|-----------------| alignment baseline
  |                          |                 |
  |__________________________v_________________|