The CSS spec says the baseline of an inline-block should be the bottom
margin when either the overflow property is not 'visible' or there are
no in-flow line boxes. Previously, only the latter case was checked.
This fixes 1 WPT test:
https://wpt.live/css/css-align/baseline-of-scrollable-1a.html
These functions are trivial, and we were actually bleeding a lot of time
in profiles to just function entry/exit.
By marking Length::make_px() as [[nodiscard]], we also exposed some
places that were creating a Length and not using it for anything.
This means that we now calculate the inner width correctly for `display:
inline-block` nodes when we have `box-sizing: border-box` and
`min-width`, as we would previously assume these dimensions were all `0`
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.
For button layouts, we were overriding the computed `width` value with
`fit-content` in `TreeBuilder::wrap_in_button_layout_if_needed()`. But
the spec asks us to set the _used value_ instead, so we now actually
calculate the fit-content width and set the box' content width to it.
Fixes#2516.
CSS grid specification states that for grid items with a replaced
element and a percentage preferred size or maximum size, the percentage
should be resolved against 0 during content-based minimum size
calculation. This makes sense, as it prevents replaced items from
overshooting their grid track while intrinsic track sizes are
calculated, and allows later track size resolution steps to scale
replaced items to fit their grid track.
This behavior is part of the cyclic percentage contribution logic from
CSS-SIZING-3 which explicitly only applies to non-replaced boxes.
This fixes an issue on Discord where buttons in the settings UI were
cropped to a narrower width than intended.
Fixes#3572
The specification [1] indicates that the tentative used width and height
should be computed first, and if they exceed the `max-width` or
`max-height`, the rules should be applied again using the computed
values of `max-width` and `max-height`.
The only required change to follow the spec is to remove the early
`return` statements, in both `compute_width_for_replaced_element`
and `compute_height_for_replaced_element`.
Fixes#5100.
[1] https://www.w3.org/TR/CSS22/visudet.html#min-max-widths
For `vertical-align: middle` and `vertical-align: text-bottom`, we used
just the content height of the inline box to determine its alignment
position. This caused incorrect positioning when padding is applied.
This fixes the button alignment on our GitHub page.
Fixes#290.
We were already always doing this, but through an unusual mechanism:
SVG layout creates a BFC on the stack when laying out foreignObject
subtrees.
This change makes the rest of the layout system aware of this, and
also allows it to be reflected in layout dumps.
By the time we calculate the min-content height, the width is already
known, so we can use it to calculate the height based on the natural
aspect ratio.
Instead, use the generic create_independent_formatting_context_if_needed
so that unusual situations like image-as-table-caption don't crash.
This logic clearly needs more work, but let's at least do better than
crashing. This gives us 26 new subtest passes on WPT.
We were incorrectly deciding that abspos elements shouldn't treat many
max-width and max-height values as `none`. My best understanding is that
this was a hack in 2023 for an issue that has been solved since then.
By removing the incorrect short-circuit, we stop at least one WPT test
from crashing due to infinite recursion and get ourselves +34 passes.
When determining the content/margin box rects within their ancestor's
coordinate space, we were returning early if the passed in values
already belonged to the requested ancestor. Unfortunately, we had
already applied the used values' offset to the rect, which is the offset
to the ancestor's ancestor.
This simplifies the logic to always apply the rect offset after checking
if we've reached the ancestor. Fixes determining float intrusions inside
block elements with `margin: auto` set.
Fixes#4083.
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.
...boxes with non-auto height.
We know for sure that by the time we layout abspos boxes, their
containing block has definite height, so it's possible to resolve
non-auto heights and mark it as definite before doing inner layout.
Big step towards having reasonable performance on
https://demo.immich.app/photos because now we avoid a bunch of work
initiated by mistakenly invoked intersection observer callbacks.
Co-Authored-By: Andreas Kling <andreas@ladybird.org>
positioned element is a descendant of inline-block
Sets inline block offsets in InlineFormattingContext.cpp, but this is
not enough. When static position rect is calculated during layout,
not all ancestors of abspos box may have their offsets calculated yet
(more info here: https://github.com/LadybirdBrowser/ladybird/pull/2583#issuecomment-2507140272).
So now static position rect is calculated relative to static containing
block during layout and calculation relative to actual containing block
is done later in
FormattingContext::layout_absolutely_positioned_element.
Fixes wpt/css/CSS2/abspos/static-inside-inline-block.html
12c6ac78e2 with fixed mistake when cache
slot is copied instead of being referenced:
```cpp
auto cache =
box.cached_intrinsic_sizes().min_content_height.ensure(width);
```
while it should've been:
```cpp
auto& cache =
box.cached_intrinsic_sizes().min_content_height.ensure(width);
```
This change moves intrinsic sizes cache from
LayoutState, which is local to current layout run,
to layout nodes, so it could be reused between
layout runs. This optimization is possible because
we can guarantee that these measurements will
remain unchanged unless the style of the element
or any of its descendants changes.
For now, invalidation is implemented simply by
resetting cache on whole ancestors chain once we
figured that element needs layout update.
The case when layout is invalidated by DOM's
structural changes is covered by layout tree
invalidation that drops intrinsic sizes cache
along with layout nodes.
I measured improvement on couple websites:
- Mail list on GMail 28ms -> 6ms
- GitHub large code page 47ms -> 36ms
- Discord chat history 15ms -> 8ms
(Time does not include `commit()`)
By the time we're measuring the height of a BFC root, we've already
collapsed all relevant margins for the root and its descendants.
Given this, we should simply use 0 (relative to the BFC root) as the
lowest block axis coordinate (i.e Y value) for the margin edges.
This fixes a long-standing issue where BFC roots were sometimes not tall
enough to contain their children due to margins.
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.
BFC roots behave differently in that their height is computed twice,
before and after inside layout, since automatic height depends on the
results of inside layout. Other formatting contexts only require the
"before" pass, and so we can treat their content sizes as definite
before proceeding with inside layout.
This makes https://play.tailwind.com/ look beautiful. :^)
In the case where we had a preferred aspect ratio and a natural height
but no natural width, we'd get into ping-ponging infinite recursion by
trying to find the width to resolve the height to resolve the width to
resolve the height...
This commit introduces proper handling of three intrinsic size keywords
when used for CSS heights:
- min-content
- max-content
- fit-content
This necessitated a few plumbing changes, since we can't resolve these
values without having access to containing block widths.
This fixes some visual glitches on https://www.supabase.com/ as well
as a number of WPT tests. It also improves the appearance of dialogs.
compute_inset() was incorrectly retrieving the containing block size
because containing_block() is unaware of grid areas that form a
containing block for grid items but do not exist in the layout tree.
With this change, we explicitly pass the containing block into
compute_inset(), allowing it to correctly provide the containing block
sizes for grid items.
If available space is definite it should always match the size of the
containing block. Therefore, there is no need to do containing block
node lookup.