When calculating e.g the min-content height of some box, we would only
set its containing block's height to 0 in the temporary formatting
state. The containing block width was not touched at all.
This patch aims to do a bit better by resolving indefinite containing
block sizes to INFINITY before performing intrinsic sizing. We should
probably also find a way to resolve definite containing block sizes,
but I'll leave that for our future selves.
Due to some yet-to-be-found bug(s) in intrinsic sizing, we can sometimes
end up deciding that some box has a non-finite intrinsic size.
This will create unpleasant results, and may lead to some layout
algorithms looping infinitely, so this patch adds a safeguard where
we simply turn non-finite intrinsic sizes into zero (and complain a
little bit in the debug log.)
We were prematurely resolving the computed size values, which meant
that `auto` values were swallowed before we could resolve them via
the intrinsic aspect ratio (if present)
This patch adds a separate entry point for this kind of layout.
We override it in BFC to set up initial width/height values for the
BFC root block.
Resulting dimensions are assigned as content_width and content_height
at the end of intrinsic sizing, for each axis, if it's either "auto"
or there's a min-content or max-content constraint in effect.
Previously, we had three layout modes:
- Normal:
- Everything uses the computed values from CSS.
- MinContent:
- Containing blocks act as if they have 0 width.
- All line breaking opportunities are taken.
- MaxContent:
- Containing blocks act as if they have infinite width.
- Only forced line breaks are accepted.
The above was based on a set of misunderstandings of CSS sizing.
A major problem with the above was that *all* containing blocks
behaved differently during intrinsic size layout, not just the
relevant one.
With this patch there are only two layout modes:
- Normal:
- Everything uses the computed values from CSS.
- IntrinsicSizeDetermination:
- One or more boxes have size constraints applied.
There are two size constraints per layout box, set here:
- FormattingState::NodeState::width_constraint
- FormattingState::NodeState::height_constraint
They are of type SizeConstraint and can be one of None, MinContent,
or MaxContent. The default is None.
When performing an IntrinsicSizeDetermination layout, we now assign
a size constraint to the box we're trying to determine the intrinsic
size of, which is then honored by using two new helpers to query
the dimensions of containing blocks:
- FormattingContext::containing_block_width_for(Box)
- FormattingContext::containing_block_height_for(Box)
If there's a relevant constraint in effect on the Box, the size of
its containing block is adjusted accordingly.
This is essentially an implementation of the "available space"
constraints from CSS-SIZING-3. I'm sure some things will break from
this, and we'll have to deal with that separately.
Spec: https://drafts.csswg.org/css-sizing-3/#available
Instead of allowing FormattingContext to instantiate an IFC for anything
that has inline children, move this logic to BFC.
This is fine, since only BFC deals with blocks having inline children
anyway.
Before, querying any of the four intrinsic sizes would cause us to
calculate all of them (the four being min-content width/height, and
max-content width/height).
Now, the helper functions only calculate the specific intrinsic size
requested. It's then cached at the root formatting context level,
so that it's never calculated twice within the same layout pass.
Instead of using Optional<LengthPercentage>, we now use LengthPercentage
for these values. The initial values are all `auto`.
This avoids having to check `has_value()` in a ton of places.
This is a hack that allows block-level replaced elements to be flex
items. Flexbox layout currently assumes (in many places) that it's
always possible to create an independent formatting context for each of
its items.
Change "compute" to "calculate" to make clearer that this is unrelated
to the CSS "computed height" concept.
Change "intrinsic" to "auto" to make clearer that this is not the same
as the intrinsic min-content and max-content sizing calculations.
When running the min-content and max-content sizing algorithms and the
target box creates a flex formatting context, we don't need to measure
its children.
FFC has already assigned the content_width and content_height values,
so we just need to pick those up from the container's formatting state.
For computing height in FormattingContext::calculate_intrinsic_sizes
we were calling into BlockFormattingContext::compute_theoretical_height
which will check if the CSS height property was defined and calculate
the height based on that instead of calculating the intrinsic height
This patch adds a new function calculate_intrinsic_height, which will
call into compute_auto_height_for_block_level_element for a block
element, or into compute_height_for_replaced_element for a replaced
element.
Unlike BFC root blocks with height:auto, when the block *isn't* a BFC
root, we don't have to look for the "bottommost" block-level child and
determine the width from that.
Instead, we should just look at the last in-flow block-level child.
This was already indicated in the spec comment next to the code, but
the code itself was wrong.
This makes the body element on Acid3 have the correct height. It also
introduces a small regression on Acid2 that we'll have to track down.
When the spec tells us to measure from the top content edge of a block,
that just means we should measure from Y=0. We don't need to go looking
for a child box with a negative top offset and measure from there.
This patch reimplements inset property resolution based on the new
CSS Positioned Layout specification. Nothing should change for
left/right insets, but we gain support for top/bottom. :^)
...but never allow the resulting height to become negative. This solves
an issue seen on Acid3 where elements with negative vertical margins
expanded the size of their height:auto container instead of shrinking
it, which is the correct behavior. This now works :^)
BFC roots with children_are_inline()==true can still have floating boxes
as well. children_are_inline() is only concerned with in-flow children.
For this reason, we have to always consider floats when calculating
height:auto for BFC roots.
Collect all the preceding block-level siblings whose vertical margins
are collapsible. Both margin-top and margin-bottom now (previously,
we only considered the margin-bottom of siblings.)
Use the right margin in part-negative and all-negative situations.
The old mode names, while mechanically accurate, didn't really reflect
their relationship to the CSS specifications.
This patch renames them as follows:
Default => Normal
AllPossibleLineBreaks => MinContent
OnlyRequiredLineBreaks => MaxContent
There's also now an explainer comment with the LayoutMode enum about the
specific implications of layout in each mode.
The previous implementation used relative X offsets for both left and
right-side floats. This made right-side floats super awkward, since we
could only determine their X position once the width of the BFC root was
known, and for BFC roots with automatic width, this was not even working
at all most of the time.
This patch changes the way we deal with floats so that BFC keeps track
of the offset-from-edge for each float. The offset is the distance from
the BFC root edge (left or right, depending on float direction) to the
"innermost" margin edge of the floating box.
Floating box are now laid out in two passes: while going through the
normal flow layout, we put floats in their *static* position (i.e the
position they would have occupied if they weren't floating) and then
update the Y position value to the final one.
The second pass occurs later on, when the BFC root has had its width
assigned by the parent context. Once we know the root width, we can
set the X position value of floating boxes. (Because the X position of
right-side floats is relative to the right edge of the BFC root.)
Using the intrinsic size cache means we only perform the nested layout
to determine intrinsic size *once* per root layout pass.
Furthermore, by using a throwaway FormattingState, details of the nested
layout can't leak into and mutate the outer layout.
Instead of caching them with the current state, we can cache them at the
root of the state tree. Since intrinsic sizes are immutable during the
same layout, this allows layout to take advantage of intrinsic sizes
discovered during nested layout (and avoids a *lot* of duplicate work.)
I'm a little confused about intrinsic heights *really* work, and I'm
struggling to extract that information from the spec. In the meantime,
let's ensure that min-content is always smaller than (or equal to)
max-content so that other math works as expected.
Previously, each NodeState in a FormattingState was shared with the
parent FormattingState, but the HashMap of NodeState had to be copied
when making FormattingState copies.
This patch makes copying instant by keeping a pointer to the parent
FormattingState instead. When fetching immutable state via get(), we may
now return a reference to a NodeState owned by a parent FormattingState.
get_mutable() will copy any NodeState found in the ancestor chain before
making a brand new one.
FormattingContext can now calculate the intrinsic sizes (min-content and
max-content in both axes) for a given Layout::Box.
This is a rather expensive operation, as it necessitates performing two
throwaway layouts of the subtree rooted at the box. Fortunately, we can
cache the results of these calculations, as intrinsic sizes don't change
based on other context around the box. They are intrinsic after all. :^)
I was wrong in 56df05ae44, there are
situations where floating children should not affect the auto height of
their parent.
It turns out we were using the "height:auto for BFC roots" algorithm for
all height:auto blocks. This patch fixes that by splitting it into two
separate functions, and implementing most of the two different variants.
Note that we don't support vertical margin collapsing here yet.
Thanks to Tim for noticing the error! :^)
If an element with height:auto has any floating descendants whose bottom
margin edge is below the element's bottom content edge, then the height
is increased to include those edges.
Before this patch, we were stopping at the bottom *content* edge of
floating descendants.
Previously we were computing the bottom edge of a line box by finding
the bottommost fragment on the line.
That method didn't give correct results for line boxes with no fragments
(which is exactly what you get when inserting a bunch of <br> elements.)
To cover all situations, we now keep track of the bottommost edge in the
LineBox object itself.
When encountering a box that claims to have block-level children, but
its CSS display type isn't actually "flow" inside, we would previously
crash due to a VERIFY() failure.
However, many sites choke on this due to freestanding table-related
boxes like those created by "table-row" and "table-row-group".
We're supposed to fix those up by wrapping them in a full set of table
boxes during layout tree construction, but that algorithm obviously
isn't working correctly in all cases. So let's work around the crashes
for now, allowing many more sites to load (even if visually incorrect.)
This is a rather monstrous hack, and we should get rid of it as soon as
it's not needed anymore.
This patch adds a map of Layout::Node to FormattingState::NodeState.
Instead of updating layout nodes incrementally as layout progresses
through the formatting contexts, all updates are now written to the
corresponding NodeState instead.
At the end of layout, FormattingState::commit() is called, which
transfers all the values from the NodeState objects to the Node.
This will soon allow us to perform completely non-destructive layouts
which don't affect the tree.
Note that there are many imperfections here, and still many places
where we assign to the NodeState, but later read directly from the Node
instead. I'm just committing at this stage to make subsequent diffs
easier to understand.
The purpose of this new object will be to keep track of various states
during an ongoing layout.
Until now, we've been updating layout tree nodes as we go during layout,
which adds an invisible layer of implicit serialization to the whole
layout system.
My idea with FormattingState is that running layout will produce a
result entirely contained within the FormattingState object. At the end
of layout, it can then be applied to the layout tree, or simply queried
for some metrics we were trying to determine.
When doing subtree layouts to determine intrinsic sizes, we will
eventually be able to clone the current FormattingState, and run the
subtree layout in isolation, opening up opportunities for parallelism.
This first patch doesn't go very far though, it merely adds the object
as a skeleton class, and makes sure the root BFC has one. :^)