Previously, when applying `text-overflow: ellipsis`, line box fragments
after the ellipsis point were removed from the line box. This
invalidated fragment indices stored in `containing_line_box_fragment`,
causing an out-of-bounds access in `LayoutState::commit()` when
resolving atomic inline positions.
Instead of removing fragments, mark them as hidden. This preserves
index stability while preventing hidden fragments from being displayed.
Scrollable overflow still assumed a top-left scroll origin and only
added trailing padding on the physical bottom edge. That broke
scrollWidth and scrollHeight for flex containers whose main or cross
axis was reversed by writing-mode, direction, flex-direction, or
wrap-reverse.
Teach flex layout to place wrapped lines using the computed cross-axis
direction and to measure scrollable overflow from the container's
actual scroll origin so reachable reversed overflow is preserved, the
unreachable side is clipped, and end padding is added on the correct
physical edge.
Keep per-item cross-axis placement using the existing behavior.
Applying full cross-axis reversal there regressed baseline alignment
tests, and zero-sized boxes exactly at the scroll origin must still
contribute descendant overflow, so the unreachable-overflow checks
need strict comparisons.
This makes negative-overflow-002 and negative-overflow-003 pass and
improves negative-overflow, align-content-wrap-003, and
overflow-with-padding.
Build a HashMap from each containing block to the boxes it contains
before measuring scrollable overflow. This allows
measure_scrollable_overflow() to iterate only the relevant boxes
directly, rather than walking the entire layout subtree and filtering
by containing_block() at each node.
Throwaway LayoutState instances used for intrinsic sizing should not
access nodes outside the laid-out subtree. Make this explicit by setting
a subtree root on each throwaway state, pre-populating only the
immediate containing block, and using try_get() for any ancestor
lookups — treating unavailable ancestors as indefinite rather than
silently populating them with incorrectly-resolved values.
Each NodeWithStyle is assigned a sequential layout index during the
pre-layout tree traversal. LayoutState stores UsedValues in a
PagedStore — a two-level page table indexed by layout_index that
gives O(1) lookup via two array accesses, with pages allocated
lazily on first write. UsedValues are stored directly in pages
(Optional<T>) rather than behind heap pointers, eliminating
per-entry malloc/free calls and improving cache locality.
This cuts ensure_used_values_for() from ~14% to ~7% in profiles
on https://www.nyan.cat/.
The CSS Overflow spec says scrollable overflow should include "the
scrollable overflow areas of all of the above boxes (including
zero-area boxes)", but we were skipping zero-area boxes entirely via
an early return. This meant elements like a position:relative container
that collapses to zero height (because its only child is absolutely
positioned) would never have their children's overflow counted.
During partial subtree relayout, `LayoutState` pre-populates
`used_values_per_layout_node` with nodes outside the relayout subtree
(e.g. ancestor SVG graphics boxes) so dependent data can be resolved.
`LayoutState::commit()` was still creating paintables for all entries,
including those pre-populated outside-subtree nodes.
That produced extra disconnected paintables on ancestors across repeated
SVG partial relayouts.
Guard the main paintable-creation loop with
`m_subtree_root->is_inclusive_ancestor_of(node)` so only subtree nodes
get new paintables, while outside-subtree nodes keep their existing
state.
Replace content_box_rect_in_static_position_ancestor_coordinate_space()
which walked the ancestor chain to compute the offset between an abspos
element's static position containing block and its actual containing
block.
Instead, add a cumulative_offset() method to UsedValues that computes
the absolute offset from the ICB to a box's content edge by walking the
containing block chain. For pre-populated nodes during partial relayout,
it returns a cached value from the paintable's absolute position.
The static-CB-to-actual-CB offset is now a simple subtraction of two
cumulative offsets, which also fixes a bug where table cells with
position:relative ancestors got incorrect static positions due to the
old function accumulating offsets in the wrong coordinate space.
Also route direct UsedValues::offset assignments in Flex, Grid, and
Table formatting contexts through set_content_offset().
Previously, any SVG geometry attribute change would mark the entire
document layout tree as dirty, triggering a full layout pass even though
only the SVG subtree was affected. This made SVG geometry animations
unnecessarily expensive.
Fix this by stopping `needs_layout_update` propagation at the SVGSVGBox
boundary and tracking dirty SVG roots separately on the Document. When
`update_layout()` finds that only SVG roots need relayout (and the
document layout root is clean), it runs SVGFormattingContext on each
dirty SVG root in a fresh LayoutState and commits the results directly,
bypassing the full document layout pass entirely.
This results in a substantial performance improvement on pages with
animated SVGs, such as https://www.cloudflare.com/,
https://www.duolingo.com/, and our GC graph explorer page.
Instead of walking the entire DOM document to clear paintable refs and
collect inline nodes, walk the layout subtree rooted at the commit root.
This removes an assumption that commit() is always called with the
layout tree root and serves as preparatory refactoring for partial SVG
layout.
Move the inline dom_node() method to Viewport.cpp so the header no
longer needs the full Document definition. Add explicit includes to
files that relied on the transitive dependency.
Previously `refresh_scroll_state()` reached back to PaintableBox for
sticky insets. Storing them on ScrollFrame makes it self-contained
for sticky offset computation.
Previously, has_scrollable_overflow was a purely geometric check, true
whenever content extended beyond the padding box regardless of the
overflow property. This caused unnecessary scroll frame allocation for
boxes with `overflow:visible`.
Per CSS Overflow 3, scrollable overflow is only defined for scroll
containers (overflow: auto/hidden/scroll). Gate the flag on
`is_scroll_container()` so that only actual scroll containers get scroll
frames assigned.
Reuse existing paintables during relayout to reduce GC allocation
pressure. Each paintable subclass implements reset_for_relayout()
to clear state before reuse.
An BlockContainer inside an InlineNode is called from the
`for each in inclusive_subtree_of_type` but is also a fragment
of that InlineNode. Don't count the the Node twice.
Before this change, you could only scroll the current hovered scroll
container, even if it was at the beginning or end and thus having no
effect.
Now, if it doesn't update, it will not be classed as handled and will
move onto the next scroll container.
Previously, we were collapsing whitespace in Layout::TextNode and then
passed the resulting string for further processing through ChunkIterator
-> InlineLevelIterator -> InlineFormattingContext -> LineBuilder ->
LineBoxFragment -> PaintableFragment. Our painting tree is where we deal
with things like range offsets into the underlying text nodes, but since
we modified the original string, the offsets were wrong.
This changes the way we generate fragments:
* Layout::TextNode no longer collapses whitespace as part of its
stored "text for rendering", but moves this logic to ChunkIterator
which splits up this text into separate views whenever whitespace
needs to be collapsed.
* Layout::LineBox now only extends the last fragment if its end offset
is equal to the new fragment's start offset. Otherwise, there's a
gap caused by collapsing whitespace and we need to generate a
separate fragment for that in order to have a correct start offset.
Some tests need new baselines because of the fixed start offsets.
Fixes#566.
Not every user of this requires an `auto` state, but most do.
This has quite a big diff but most of that is mechanical:
LengthPercentageOrAuto has `resolved_or_auto()` instead of `resolved()`,
and `to_px_or_zero()` instead of `to_px()`, to make their output
clearer.
We used to only walk the paintable root tree from the layout root and
detach paintables from there. In some cases, this could leave paintables
behind, so we added another loop that iterates over all layout nodes and
detaches their paintables, if any remained.
Instead of traversing two trees like this, just traverse the layout tree
once and detach the inclusive descendant's paintables, similar to how we
deal with the DOM tree immediately after that.
In LayoutState, used_values_per_layout_node should not be modified in
order to determine inline nodes' dimensions - all the required values
should already be in there. In 2585f2da0d
we did accidentally create new values, causing the code further down to
try and get a PaintableBox from an anonymous container and crashing.
Fixes#6015.
All fragments inside an atomic inline box should stay within that box,
otherwise we'll screw up the paint order and paint them behind things
that they're supposed to be on top of.
This fixes an issue with inline-block content not appearing on sites
like Google Docs and Reddit, among others.
As it turns out, we still have to let the formatting contexts do a bit
of layout work in order to correctly apply the aspect-ratio. Hence we
can't just implicitly transfer definiteness from one size to the other.
This is a revert of 1cfd8b3ac0.
Before committing a new layout (and thus building a new paint tree)
we now go through both the old paint tree and the layout tree and detach
them from each other.
This is a little extra work, but it ensures that there are no lingering
references across the trees, which we were apparently accumulating in
some cases on Discord, causing GC leaks.
The existing resolve methods are not to spec and we are working to
replace them with new ones based on the `simplify_a_calculation_tree`
method.
These are marked as deprecated rather than replaced outright as work
will need to be done on the caller side to be made compatible with the
new methods, for instance the new methods can fail to resolve (e.g.
if we are missing required context), where the existing methods will
always resolve (albeit sometimes with an incorrect value).
No functionality changes.
Whenever we end up with a scrollable overflow rect that goes beyond
either of its axes (i.e. the rect has a negative X or Y position
relative to its parent's absolute padding box position), we need to clip
that rect to prevent going into the "unreachable scrollable overflow".
This fixes the horizontal scrolling on https://ladybird.org (gets more
pronounced if you make the window very narrow).
...with inline children. This fixes an issue when we ignore abspos boxes
contained by PaintableWithLines while calculating overflow rect size.
Lots of layout tests are affected, because now PaintableWithLines has
overflow rect.
`Text/input/DOM/Element-set-scroll-left.html` is also affected and now
matches other browsers.
Sometimes fixed positioned boxes would extend the viewport's scrollable
overflow, which according to the spec should never happen. There are
some nuances to this, such as properly determining the fixed positioning
containing block for a fixed position box, but for now this prevents
some pages from being overly scrollable.
Fixes horizontal scrollability of https://tweakers.net.
It's safe to remove this pointer because intrinsic layout should never
look up a box's state beyond its containing block.
This change affects the expectations of two layout tests, but both
already differ slightly from other browsers, and the difference between
expectations is less than 5px.
Fixes underinvalidation caused by resolving text-decoration-thickness
during layout commit, while this property can be invalidated
independently of layout.
This was an old hack intended to make percentage sizes on flex items
before we had implemented the appropriate special behavior of definite
sizes in flex layout.
Removing it makes flex layout less magical and should not change
behavior in any observable way.
We've historically asserted that no "saturated" size values end up as
final metrics for boxes in layout. This always had a chance of producing
false positives, since you can trivially create extremely large boxes
with CSS.
The reason we had those assertions was to catch bugs in our own engine
code where we'd incorrectly end up with non-finite values in layout
algorithms. At this point, we've found and fixed all known bugs of that
nature, and what remains are a bunch of false positives on pages that
create very large scrollable areas, iframes etc.
So, let's change it! We now clamp content width and height of boxes to
17895700 pixels, apparently the same cap as Firefox uses.
There's also the issue of calc() being able to produce non-finite
values. Note that we don't clamp the result of calc() directly, but
instead just clamp values when assigning them to content sizes.
Fixes#645.
Fixes#1236.
Fixes#1249.
Fixes#1908.
Fixes#3057.
Initially I added this to the existing CalculationContext, but in
reality, we have some data at parse-time and different data at
resolve-time, so it made more sense to keep those separate.
Instead of needing a variety of methods for resolving a Foo, depending
on whether we have a Layout::Node available, or a percentage basis, or
a length resolution context... put those in a
CalculationResolutionContext, and just pass that one thing to these
methods. This also removes the need for separate resolve_*_percentage()
methods, because we can just pass the percentage basis in to the regular
resolve_foo() method.
This also corrects the issue that *any* calculation may need to resolve
lengths, but we previously only passed a length resolution context to
specific types in some situations. Now, they can all have one available,
though it's up to the caller to provide it.
I see no good reason to keep them out of line, and having the methods
named differently than the property they're updating caused me some
confusion initially.
Our layout tree requires that all containers either have inline or
non-inline children. In order to support the layout of non-inline
elements inside inline elements, we need to do a bit of tree
restructuring. It effectively simulates temporarily closing all inline
nodes, appending the block element, and resumes appending to the last
open inline node.
The acid1.txt expectation needed to be updated to reflect the fact that
we now hoist its <p> elements out of the inline <form> they were in.
Visually, the before and after situations for acid1.html are identical.
The existing `::unite_horizontally()` and `::unite_vertically()` tests
did not properly test the edge cases where left/top in the Rect were
updated, so they get re-arranged a bit.