Before this change, LayoutState essentially had a Vector<UsedValues*>
resized to the exact number of layout nodes in the current document.
When a nested layout is performed (to calculate the intrinsic size of
something), we make a new LayoutState with its own Vector. If an entry
is missing in a nested LayoutState, we check the parent chain all the
way up to the root.
Because each nested LayoutState had to allocate a new Vector with space
for all layout nodes, this could get really nasty on very large pages
(such as the ECMA262 specification).
This patch replaces the Vector with a HashMap. There's now a small cost
to lookups, but what we get in return is the ability to handle huge
layout trees without spending eternity in page faults.
These functions no longer do anything interesting and just forward to
content_width/content_height. Let's make the callers use those directly
instead and remove this indirection layer.
When calculating the intrinsic width of a box, we now make its content
width & height indefinite before entering the intrinsic sizing layout.
This ensures that any geometry assigned to the box by its parent
formatting context is ignored.
For intrinsic heights, we only make the content height indefinite.
This is because used content width is a valid (but optional) input
to intrinsic height calculation.
As it turns out, we sometimes query the intrinsic height of a box before
having fully resolved and/or constrained its containing block. Because
of this, we may enter intrinsic sizing with different amounts of
available width for the same box.
To accommodate this scenario, we now allow caching of multiple intrinsic
heights, separated by the amount of available width provided as input.
This colors a bit outside the lines of the specification, but the spec
doesn't offer a proper explanation for how descendants of a flex item
are supposed to have access to the flex item's main size for purposes
of percentage resolution.
The approach I came up with here was to take the hypothetical main size
of each flex item, and assign it as a temporary main size. This allows
percentage resolution in descendants to work against the pre-flexing
main size of items. This seems to match how other engines behave,
although it feels somewhat dirty. If/when we learn more about this,
we can come up with something nicer.
Now that intrinsic heights (correctly) depend on the amount of available
width, we can't just cache the first calculated min-content and
max-content heights and reuse it without thinking.
Instead, we have to cache three pairs:
- min-content & max-content height with definite available width
- min-content & max-content height with min-content available width
- min-content & max-content height with max-content available width
There might be some more elegant way of solving this, but basically this
makes the cache work correctly when someone's containing block is being
sized under a width constraint.
This is a big and messy change, and here's the gist:
- AvaliableSpace is now 2x AvailableSize (width and height)
- Layout algorithms are redesigned around the idea of available space
- When doing layout across nested formatting contexts, the parent
context tells the child context how much space is available for the
child's root box in both axes.
- "Available space" replaces "containing block width" in most places.
- The width and height in a box's UsedValues are considered to be
definite after they're assigned to. Marking something as having
definite size is no longer a separate step,
This probably introduces various regressions, but the big win here is
that our layout system now works with available space, just like the
specs are written. Fixing issues will be much easier going forward,
since you don't need to do nearly as much conversion from "spec logic"
to "LibWeb logic" as you previously did.
This is rather subtle and points to our architecture around definite
sizes not being exactly right, but...
At some points during flexbox layout, the spec tells us that the sizes
of certain flex items are considered definite from this point on.
We implement this by marking each item's associated UsedValues as
"has-definite-width/height".
However, this breaks code that tries to resolve computed "auto" sizes
by taking the corresponding size from the containing block. The end
result was that the 1st sizing pass in flexbox would find the right size
for an "auto" sized item, but the 2nd pass would override the correct
size with the containing block's content size in that axis instead.
To work around the issue, FFC now remembers when it "definitizes" an
item, and future attempts to resolve an "auto" computed size for that
value will bypass the computed-auto-is-resolved-against-containing-block
step of the algorithm. It's not perfect, and we'll need to think more
about how to really represent these intermediate states relating to
box sizes being definite..
- Use the border box of the floated element when testing if something
needs to flow around it.
- Take the floated element's containing block size into account (instead
of the BFC root) when calculating available space on a line where a
right-side float intrudes.
This state is less static than we originally assumed, and there are
special formatting context-specific rules that say certain sizes are
definite in special circumstances.
To be able to support this, we move the has-definite-size flags from
the layout node to the UsedValues struct instead.