Transforms are applied to both clip rectangle and position, so we need
to remove the transform from clip rectangle before checking if position
falls within the clip rectangle.
In this change, the removal of transform is moved into
`Paintable::clip_rect()` that is shared between hit-testing and
painting.
This change fixes hit-testing in Discord's multifactor authentication
form.
By moving scroll offset clamp from `PaintableBox::scroll_by()` to
`PaintableBox::set_scroll_offset()`, we ensure that updates from
`Element::set_scroll_top()` and `Element::set_scroll_left()` are
constrained to a valid range.
They currently assume the DOM node is an HTMLImageElement with respect
to handling the alt attribute. The HTMLInputElement will require the
same behavior.
Painting command executors are defined within the "Painting" namespace,
allowing us to remove this prefix from their names.
This commit performs the following renamings:
- Painting::PaintingCommandExecutor to Painting::CommandExecutor
- Painting::PaintingCommandExecutorCPU to Painting::CommandExecutorCPU
- Painting::PaintingCommandExecutorGPU to Painting::CommandExecutorGPU
Separating the recorder list from the painter will allow us to save it
for later execution without carrying along the painter's state. This
will be useful once we have a separate thread for executing painting
commands, to which we will have to transfer commands from the main
thread.
Preparation for https://github.com/SerenityOS/serenity/pull/23108
Previously, step 5 of the stacking context hit testing would just call
`hit_test()` on the stacking context's paintable box. Which (at least
for SVGs) is just indirect infinite recursion (it'll end up right back
where it started after going through a few functions).
This now explicitly hit tests the descendants, which seems more correct,
and avoids the crash for SVGs, but nothing really seems to depend on
this step. Another solution (which I've done for a while working on
SVGs) is just to delete step 5 entirely, and nothing seems to break.
Fixes#22305
This change modifies hit_test() to no longer return the first paintable
encountered at a specified position. Instead, this function accepts a
callback that is invoked for each paintable located at a position, in
hit-testing order.
This modification will allow us to reuse this call for
`Document.elementsFromPoint()` in upcoming changes.
Refactor to resolve paint-only properties before painting, aiming to
stop using layout nodes during recording of painting commands.
Also adds a test, as we have not had any for outlines yet.
With this change, instead of applying only the border-radius clipping
from the closest containing block with hidden overflow, we now collect
all boxes within the containing block chain and apply the clipping from
all of them.
In this commit we have optimized the handling of scroll offsets and
clip rectangles to improve performance. Previously, the process
involved multiple full traversals of the paintable tree before each
repaint, which was highly inefficient, especially on pages with a
large number of paintables. The steps were:
1. Traverse the paintable tree to identify all boxes with scrollable or
clipped overflow.
2. Gather the accumulated scroll offset or clip rectangle for each box.
3. Perform another traversal to apply the corresponding scroll offset
and clip rectangle to each paintable.
To address this, we've adopted a new strategy that separates the
assignment of the scroll/clip frame from the refresh of accumulated
scroll offsets and clip rectangles, thus reducing the workload:
1. Post-relayout: Identify all boxes with overflow and link each
paintable to the state of its containing scroll/clip frame.
2. Pre-repaint: Update the clip rectangle and scroll offset only in the
previously identified boxes.
This adjustment ensures that the costly tree traversals are only
necessary after a relayout, substantially decreasing the amount of work
required before each repaint.
Recently, we moved the resolution of CSS properties that do not affect
layout to occur within LayoutState::commit(). This decision was a
mistake as it breaks invalidation. With this change, we now re-resolve
all properties that do not affect layout before each repaint.
In cases where the stacking context painting requires a separate
bitmap, the destination position needs to be translated by the
scrolling offset to ensure it ends up in the correct position.
This change addresses an issue with overflow clipping in scenarios
where `overflow: hidden` is applied to boxes nested within elements
with `overflow: scroll`.
Fixes https://github.com/SerenityOS/serenity/issues/22733
This change makes hit-testing more consistent in the handling of hidden
overflow by reusing the same clip-rectangles.
Also, it fixes bugs where the box is visible for hit-testing even
though it is clipped by the hidden overflow of the containing block.
Hit-testing relies on updated clip rectangles and containing scroll
offsets, so it's necessary to ensure that paintables have these elements
updated.
This also removes the enclosing scroll offsets update from
`Internals::hit_test()`, as it is no longer needed.
Paintable boxes should not hold information stored in device pixels.
It should be converted from CSS pixels only by the time painting
command recording occurs.
The hit-testing position is now shifted by the scroll offsets before
performing any checks for containment. This is implemented by assigning
each PaintableBox/InlinePaintable an offset corresponding to the scroll
frame in which it is contained. The non-scroll-adjusted position is
still passed down when recursing to children because the assigned
offset accumulated for nested scroll frames.
With this change, hit testing works in the Inspector.
Fixes https://github.com/SerenityOS/serenity/issues/22068
This is a fix for regression introduced in
0bf82f748f
All CSS transforms need to be removed from the clip rectangle before
applying it. However, it is still necessary to calculate it with
applied transforms to find the correct intersection of all clip
rectangles in the containing block chain.
With this change, clip rectangles for boxes with hidden overflow or the
clip property are no longer calculated during the recording of painting
commands. Instead, it has moved to the "pre-paint" phase, along with
the assignment of scrolling offsets, and works in the following way:
1. The paintable tree is traversed to collect all paintable boxes that
have hidden overflow or use the CSS clip property. For each of these
boxes, the "final" clip rectangle is calculated by intersecting clip
rectangles in the containing block chain for a box.
2. The paintable tree is traversed another time, and a clip rectangle
is assigned for each paintable box contained by a node with hidden
overflow or the clip property.
This way, clipping becomes much easier during the painting commands
recording phase, as it only concerns the use of already assigned clip
rectangles. The same approach is applied to handle scrolling offsets.
Also, clip rectangle calculation is now implemented more correctly, as
we no longer stop at the stacking context boundary while intersecting
clip rectangles in the containing block chain.
Fixes:
https://github.com/SerenityOS/serenity/issues/22932https://github.com/SerenityOS/serenity/issues/22883https://github.com/SerenityOS/serenity/issues/22679https://github.com/SerenityOS/serenity/issues/22534
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 change introduces a method for direct access to the paintable of
a PaintableFragment. This method is intended to replace the usage of
the layout node pointer. Currently, we are only eliminating the use
of `layout_node()` in hit testing.
Additionally, we no longer check if a fragment's layout node is a
`BlockContainer` before recursing into its children during hit testing.
This check was likely relevant when all fragments were owned by
`PaintableWithLines`, but now it should be safe to remove this check.
In the upcoming changes, the AccelGfx context will be used for WebGL, so
we can no longer assume that the WebContent process has a single global
context.
Now, instead of resolving "transform" and "transform-origin" during the
construction of the stacking context tree, we do so during the layout
commit.
This is part of a refactoring effort to make the paintable tree
independent from the layout tree.
This refactoring makes WebContent less aware of LibWeb internals.
The code that initializes paint recording commands now resides in
`Navigable::paint()`. Additionally, we no longer need to reuse
PaintContext across iframes, allowing us to avoid saving and restoring
its state before recursing into an iframe.
The paintable tree structure more closely matches the painting order
when fragments are owned by corresponding inline paintables. This
change does not affect the layout tree, as it is more convenient for
layout purposes to have all fragments owned by a block container in
one place.
Additionally, this improves performance significantly on pages with
many fragments, as we no longer have to walk the ancestor chain up
to the closest block container to determine if a fragment belongs
to an inline paintable.
Rather than resolving the text-shadow each time painting commands are
recorded, we can resolve it once during the layout commit and save the
resolved values in paintable fragments. This is also step towards
getting rid of layout node pointer in paintable fragment.
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.
Previously, these were clipped by the RecordingPainter, which used the
path's bounding box (which in this case is zero width or height). The
fix is to expand the bounding box by the stroke width.
Fixes#22661
Note: This is unrelated to any recent LibGfx changes :^)
CSSPixels should not be wrapped into CSS::Length before being passed
to resolved() to end up resolving percentages without losing
precision.
Fixes thrashing layout when 33.3333% width is used together with
"box-sizing: border-box".