This fixes an issue where we'd make an absolute mess from nested SVG
roots with display:block. Before this fix, the inner SVG root would
trigger the inline continuation logic and try to split the tree.
This ensures that percentages resolve against the foreignObject's size
instead of the size of its containing block.
This makes user profile pictures clip correctly in the "Friends" view
of the Discord app.
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).
This is according to the default user-agent style from the SVG2 spec.
In order for this to work correctly, we also have to assign width and
height to foreignObject boxes during SVG layout, since they are handled
manually by SVGFormattingContext.
This patch does two things:
1. Makes TreeBuilder never cross the foreignObject boundary when looking
for an appropriate insertion parent. Before this change, we would
sometimes make things inside the foreignObject DOM subtree have
layout nodes outside the foreignObject.
2. Makes foreignObject boxes participate in the anonymous wrapping of
inline-level boxes. This is particularly imporant for absolutely
positioned elements inside foreignObject, which were previously
getting incorrectly wrapped if there was any text (even empty)
preceding the abspos element.
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.
...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.
Some SVG presentation attributes are only supported on certain
elements. We now support these special cases for attributes and
elements that we currently have implemented.
We were only dumping a PaintableBox' dimensions if its layout node was a
Layout::Box as well, causing us to not dump the dimensions of paintables
for inline nodes in the paintable tree.
Browsers such as Chrome and Firefox apply an arbitrary scale to the
current font size if `normal` is used for `line-height`. Firefox uses
1.2 while Chrome uses 1.15. Let's go with the latter for now, it's
relatively easy to change if we ever want to go back on that decision.
This also requires updating the expectations for a lot of layout tests.
The upside of this is that it's a bit easier to compare our layout
results to other browsers', especially Chrome.
We were previously crashing when invoking 'scroll to the fragment' on
such documents as it was unable to find the active document. This is
as a result of our AD-HOC implementation not setting up the document
fully to mark it is active before running the parser.
Fixes a crash on https://tweakers.net.
Tests with different combinations of missing width, height
and viewBox.
All tests confirmed to work on Ladybird:
- exactly the same as Chromium (131.0.6778.85)
- almost the same as Firefox (129.0.2)
- only difference: standalone-w.svg: same size, different alignment
This condition was included to implement flex containers with auto
height, but it actually can reset the definitive height to 0 for inline
blocks with only replaced elements such as an SVG. Removing the
condition does not break any in-tree test, so let's improve the
situation on the SVG side of things for now.
InlinePaintable was an ad-hoc paintable type required to support the
fragmentation of inline nodes across multiple lines. It existed because
there was no way to associate multiple paintables with a single layout
node. This resulted in a lot of duplicated code between PaintableBox and
InlinePaintable. For example, most of the CSS properties like
background, border, shadows, etc. and hit-testing are almost identical
for both of them. However, the code had to be duplicated to account for
the fact that InlinePaintable creates a box for each line. And we had
quite many places that operate on paintables with a code like:
```
if (box.is_paintable_box()) {
// do something
} else (box.is_inline_paintable()) {
// do exactly the same as for paintable box but using InlinePaintable
}
```
This change replaces the usage of `InlinePaintable` with
`PaintableWithLines` created for each line, which is now possible
because we support having multiple paintables per layout node. By doing
that, we remove lots of duplicated code and bring our implementation
closer to the spec.
This is what the spec tells us to do:
The root element’s display type is always blockified,
and its principal box always establishes an independent
formatting context.
Additionally, a display of contents computes to block
on the root element.
Spec link: https://drafts.csswg.org/css-display/#rootFixes#1562
We were incorrectly looking at the CSS computed values for width and
height to determine the natural size of <svg> root elements.
This meant that elements where the attribute and computed value were
different values would end up with incorrect natural size.
Previously, `SVGSVGBox` would have a natural aspect ratio of 0 if it
had a viewbox with zero width. This led to a division by zero, causing
a crash.
Found by Domato.
Doing multiple `for_each_in_subtree()` passes was kind of a hack. We
can resolve everything in a single pass with a little more control over
the layout process. This also fixes a few minor issues like the sizing
of nested `<g>` elements.
More work is needed here though as this is still fairly ad-hoc.
Note: This does regress `css-namespace-tag-name-selector.html`,
previously SVG text within `<a>` elements would appear. However, this
was only because `for_each_in_subtree()` would blindly look through the
InlineNodes from the unimplemented `SVGAElement`s.
Rather than try to lay out masks normally, this updates the TreeBuilder
to create layout nodes for masks as a child of their user (i.e. the
masked element). This allows each use of a mask to be laid out
differently, which makes supporting `maskContentUnits=objectBoundingBox`
fairly easy.
The `SVGFormattingContext` is then updated to lay out masks last (as
their sizing may depend on their parent), and treats them like
viewports.
This is pretty ad-hoc, but the SVG specification does not give any
guidance on how to actually implement this.
With this the `<circle>` element now correctly parses percentage sizes,
and resolves them relative to the viewport.
The rest of the geometry elements are still left TODO.
Previously, the check for `.html` meant that `.svg` tests were excluded.
This led to a few `.svg` with missing or bit-rotted expectations, which
have now been added/updated.
This allows positioning a child SVG relative to its parent SVG.
Note: These have been implemented as CSS properties as in SVG 2, these
are geometry properties that can be used in CSS (see
https://www.w3.org/TR/SVG/geometry.html), but there is not much browser
support for this. It is nicer to implement than the ad-hoc SVG
attribute parsing though, so I feel it may make sense to port the rest
of the attributes specified here (which should fix some issues with
viewport relative sizes).
Previously, we were handling viewBoxes/viewports in a slightly hacky
way, asking graphics elements to figure out what viewBox to use during
layout. This does not work in all cases, and can't allow for more
complex SVGs where it is possible to have nested viewports.
This commit makes the SVGFormattingContext keep track of the
viewport/boxes, and it now lays out each viewport recursively, where
each nested `<svg>` or `<symbol>` can establish a new viewport.
This fixes some previous edge cases, and starts to allow nested
viewports (there's still some issues to resolve there).
Fixes#22931
This is a part of refactoring towards making the paintable tree
independent of the layout tree. Now, instead of transferring text
fragments from the layout tree to the paintable tree during the layout
commit phase, we allocate separate PaintableFragments that contain only
the information necessary for painting. Doing this also allows us to
get rid LineBoxes, as they are used only during layout.
This patch makes a few changes to the way we calculate line-height:
- `line-height: normal` is now resolved using metrics from the used
font (specifically, round(A + D + lineGap)).
- `line-height: calc(...)` is now resolved at style compute time.
- `line-height` values are now absolutized at style compute time.
As a consequence of the above, we no longer need to walk the DOM
ancestor chain looking for line-heights during style computation.
Instead, values are inherited, resolved and absolutized locally.
This is not only much faster, but also makes our line-height metrics
match those of other engines like Gecko and Blink.
The elements this hack was being used for were grouping elements, and
can be properly sized: https://svgwg.org/svg2-draft/struct.html#Groups.
Note: Other than one test change the elements here are already covered
by layout tests.
This makes use of the new Gfx::Path::text() to handle SVG text elements,
with this text is just a regular path, and can be manipulated like any
other graphics element.
This removes the SVGTextPaintable and makes both <text> and geometry
elements use a new (shared) SVGPathPaintable. This is identical to the
old SVGGeometryPaintable. This simplifies painting as once something is
resolved to a Gfx::Path, the painting logic is the same.
Before this change, we were doing it after every layout, which meant
that already-propagated overflow could be propagated again, which led to
incorrect scrolling behavior.
Previously, all SVG <text> elements were zero-sized boxes, that were
only actually positioned and sized during painting. This led to a number
of problems, the most visible of which being that text could not be
scaled based on the viewBox.
Which this patch, <text> elements get a correctly sized layout box,
that can be hit-tested and respects the SVG viewBox.
To share code with SVGGeometryElement's the PathData (from the prior
commit) has been split into a computed path and computed transforms.
The computed path is specific to geometry elements, but the computed
transforms are shared between all SVG graphics elements.
For now, part of this is commented-out. Our current implementations of
`<mask>` and `<symbol>` rely on creating layout nodes, so they can't be
`display: none`.
This allows SVG mask elements to have layout computed, but not connected
to the main paint tree. They should only be reachable if (and painted)
if referenced by the "mask" attribute of another element.
This is controlled by the forms_unconnected_subtree() function on the
paintable, which (if it returns true) prevents the paintable from being
added as a child to what would be its parent.
I'm about to make StackingContext traverse the paintable tree instead of
actually traversing the layout tree, and it turns out we were not
creating paintables for these SVG elements.
Also switch them to Layout::Box instead of the default InlineNode to
make the trees look a bit less weird. Ultimately, we should do something
specialized for these subtrees, but for now this'll do.