This allows us to avoid the need for costly traversals to gather
boxes that have been saved during the construction of the stacking
context tree.
No behavior change intended.
By storing a list of positioned and floating descendants within the
stacking context tree node, we can eliminate the need for costly
paintable tree traversals during hit-testing.
This optimization results in hit-testing being 2 to 2.5 times faster
on https://ziglang.org/documentation/master/
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.
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 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.
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
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.
With this change, a stacking context can be established by any
paintable, including inline paintables. The stacking context traversal
is updated to remove the assumption that the stacking context root is
paintable box.
Instead of implementing stacking context painting order exactly as it
is defined in CSS2.2 "Appendix E. Elaborate description of Stacking
Contexts" we need to account for changes in the latest standards where
a box can establish a stacking context without being positioned, for
example, by having an opacity different from 1.
Fixes https://github.com/SerenityOS/serenity/issues/21137
With this change, instead of applying scroll offsets during the
recording of the painting command list, we do the following:
1. Collect all boxes with scrollable overflow into a PaintContext,
each with an id and the total amount of scrolling offset accumulated
from ancestor scrollable boxes.
2. During the recording phase assign a corresponding scroll_frame_id to
each command that paints content within a scrollable box.
3. Before executing the recorded commands, translate each command that
has a scroll_frame_id by the accumulated scroll offset.
This approach has following advantages:
- Implementing nested scrollables becomes much simpler, as the
recording phase only requires the correct assignment of the nearest
scrollable's scroll_frame_id, while the accumulated offset from
ancestors is applied subsequently.
- The recording of painting commands is not tied to a specific offset
within scrollable boxes, which means in the future, it will be
possible to update the scrolling offset and repaint without the need
to re-record painting commands.
The current set of stacking context commands do not encode the
information needed to correctly paint the stacking context, instead,
they're based on the limitations of the current CPU renderer.
Stacking contexts should be able to be transformed by an arbitrary
3D transformation matrix, not just scaled from a source to a destination
rect. The `_with_mask()` stacking context also should not be separate
from the regular stacking context.
```c++
push_stacking_context(
bool semitransparent_or_has_non_identity_transform,
float opacity, Gfx::FloatRect const& source_rect,
Gfx::FloatRect const& transformed_destination_rect,
Gfx::IntPoint const& painter_location);
pop_stacking_context(
bool semitransparent_or_has_non_identity_transform,
Gfx::Painter::ScalingMode scaling_mode);
push_stacking_context_with_mask(
Gfx::IntRect const& paint_rect);
pop_stacking_context_with_mask(
Gfx::IntRect const& paint_rect,
RefPtr<Gfx::Bitmap> const& mask_bitmap,
Gfx::Bitmap::MaskKind mask_kind, float opacity);
```
This patch replaces this APIs with just:
```c++
push_stacking_context(
float opacity,
bool is_fixed_position,
Gfx::IntRect const& source_paintable_rect,
Gfx::IntPoint post_transform_translation,
CSS::ImageRendering image_rendering,
StackingContextTransform transform,
Optional<StackingContextMask> mask);
pop_stacking_context()
```
And moves the implementation details into the executor, this should
allow future backends to implement stacking contexts without these
limitations.
By storing painting command coordinates relative to the nearest
stacking context we can get rid of the Translate command.
Additionally, this allows us to easily check if the bounding
rectangles of the commands cover or intersect within a stacking
context. This should be useful if we decide to optimize by avoiding
the execution of commands that will be overpainted by the results of
subsequent commands.
This modification introduces a new layer to the painting process. The
stacking context traversal no longer immediately calls the
Gfx::Painter methods. Instead, it writes serialized painting commands
into newly introduced RecordingPainter. Created list of commands is
executed later to produce resulting bitmap.
Producing painting command list will make it easier to add new
optimizations:
- It's simpler to check if the painting result is not visible in the
viewport at the command level rather than during stacking context
traversal.
- Run painting in a separate thread. The painting thread can process
serialized painting commands, while the main thread can work on the
next paintable tree and safely invalidate the previous one.
- As we consider GPU-accelerated painting support, it would be easier
to back each painting command rather than constructing an alternative
for the entire Gfx::Painter API.
This allows applying SVG <mask>s to elements. It is only implemented for
the simplest (and default) case:
- mask-type = luminance
- maskContentUnits = maskContentUnits
- maskUnits = objectBoundingBox
- Default masking area
It should be possible to extend to cover more cases. Though the layout
for maskContentUnits = objectBoundingBox will be tricky to figure out.
d06d4eb made the `clip` property apply to children of an absolute-
positioned element, but caused it not to be applied to the element the
property was applied to directly.
To fix this, apply the clip in new `before_paint()` and `after_paint()`
functions. Doing so keeps painter state from leaking from `paint()`,
but still allows subclasses of `PaintableBox` clip their contents
correctly without repeating the application of the clip rectangle.
In general it is not safe to convert any arbitrary floating-point value
to CSSPixels. CSSPixels has a resolution of 0.015625, which for small
values (e.g. scale factors between 0 and 1), can produce bad results
if converted to CSSPixels then scaled back up. In the worst case values
can underflow to zero and produce incorrect results.
Grid specification https://www.w3.org/TR/css-grid-2/#z-order defines
special painting order for grid items which should be the same as for
defined for inline-blocks in CSS2.
Eventually we should not need the layout tree for anything when painting
and this code will only look at the paint tree. For now, this is just
another step in that direction.
Previously stacking contexts were only painted in steps 3, 8, and 9.
These steps are only meant to cover positioned elements (as per
https://www.w3.org/TR/CSS22/zindex.html). This meant that elements with
opacity (which forms a stacking context) could end up painted above
elements that actually occlude them.
Fixes painting of nested nodes in scrollable containers by moving
painter's scroll offset translation from paint_node() to
before_children_paint() and after_children_paint().
Build a grid snapped to device pixels and use it to construct the
rectangles for the cell edges, same as for collapsed borders. This is
especially important when border-spacing is set to 0 since it avoids
gaps between adjacent cells which have borders set.
...along with `outline-color`, `outline-style`, and `outline-width`.
This re-uses the existing border-painting code, which seems to work well
enough!
This replaces the previous code for drawing focus-outlines, with generic
outline painting for any elements that want it. Focus outlines are now
instead supported by this code in Default.css:
```css
:focus-visible {
outline: auto;
}
```
There are two parts to this fix:
- First, StyleProperties::transformations() would previously omit calc()
values entirely when returning the list of transformations. This was
very confusing to StackingContext which then tried to index into the
list based on faulty assumptions. Fix this by emitting calc values.
- Second, StackingContext::get_transformation_matrix() now always calls
resolve() on length-percentages. This takes care of actually resolving
calc() values. If no reference value for percentages is provided, we
default to 0px.
This stops LibWeb from asserting on websites with calc() in transform
values, such as https://qt.io/ :^)
Since we deliberately skip positioned elements in paint_descendants(),
we have to make sure we actually paint them in the subsequent
paint_internal() pass.
Before this change, we were only painting positioned elements whose
paintable was a PaintableBox, neglecting inline-level relpos elements.
Follow the specification in making the borders centered on the grid
lines. This avoids visual bugs due to double-rendering of borders on
either side of an edge and paves the way for a full implementation of
the harmonization algorithm for collapsed borders.
Currently, this still lacks complete handling of row and column spans.
Also, the box model for cells still considers the full width of the
internal borders instead of just half, as the specification requires.
Some additional handling of rounding issues will be needed to avoid very
subtle visual bugs.
Despite these limitations, this improves the appearance of all the
tables with collapsed borders I've tried while limiting the amount of
change to something reasonable.
Move painting of cell borders to a separated function since doing it
correctly has to consider the entire grid as a whole for the collapsed
borders case.
Previously, all StackingContexts which were scaled using CSS transforms
were hard-coded to use BilinearBlend. This fix maps specified
image-rendering properties to reasonable ScalingModes for painting.