Since handling overflow: hidden in PaintableBox::before_children_paint
while following paint traversal order can't result in correctly computed
clip rectangle for elements that create their own stacking context
(because before_children_paint is called only for parent but overflow:
hidden can be set somewhere deeper but not in direct ancestor), here
introduced new function PaintableBox::clip_rect() that computes clip
rectangle by looking into containing block.
should_clip_overflow flag that disables clip for absolutely positioned
elements in before_children_paint and after_children_paint is removed
because after changing clip rectangle to be computed from not parent
but containing block it is not needed anymore (absolutely positioned
item is clipped if it's containing block has hidden overflow)
We were doing a forward traversal in hit testing which led to sometimes
incorrect results when multiple boxes were occupying the same X and Y
coordinate.
Positioned descendants are now handled entirely by paint_internal()
so we can just skip over positioned children in paint_descendants().
This avoids drawing the same boxes multiple times.
This "worked" before because all positioned elements would create their
own stacking context. When we stopped doing this, there was nobody to
actually paint positioned descendants with `z-index: auto`.
This patch splits up steps 8 and 9 of the paint order algorithm and
implements step 8 as a paint tree traversal. There's more to step 8 than
I've implemented here, so I've left a FIXME for our future selves.
Since positioned elements no longer automatically create stacking
contexts, we can't rely on this assumption when painting descendants of
a stacking context.
In this commit, we fix an issue that manifested as a failure to
Gfx::Painter::restore() in the "Overlay" paint phase. What happened was
that a CSS clip was being applied in the "Background" paint phase, and
then unapplied in the "Overlay" phase. Due to bogus checks in
paint_descendants(), the "Background" phase never ran for positioned
elements, but the "Overlay" phase did.
The check for positioned elements was bogus in the first place and had
never actually worked before, since we would always skip over positioned
descendants due to them having stacking contexts.
We're supposed to hit test positive z-index stacking contexts first,
and negative z-index stacking contexts later. Instead, we were hit
testing all stacking contexts both times.
This made hit testing unbearably slow on some websites.
While we're here, also add an extra comment about why stacking contexts
are traversed in reverse order. It tripped me up while looking at this,
so I'm sure it could trip someone else up too.
Regressed in 44057c9482.
If the 2D transform in effect is just a simple translation, we don't
need to draw into a temporary bitmap and then transform it. We can
just translate the painter. :^)
This fixes an edge case, where the destination rect falls partly
outside the painter, so is clipped to a smaller size in
`get_region_bitmap()` (which needs to be accounted for with an extra
offset).
This now copies the area under the destination to a new bitmap, that
is then scaled to the size of the source. The element is then painted
into that bitmap, which is then scaled and painted back to
the destination. This is done as many effects such as shadows, border
radii, filters, etc require being able to read pixels from the painter.
This does work (and is not that noticeable in many cases), but it does
mean there may be a few scaling artifacts in the background
around transformed elements. Though that was already the case before
anyway for the elements (since it is just a bitmap scale).
What we really want is to (where possible) just scale the paintable
and its descendants, then paint things normally, which would give
much nicer results (but is much more tricky to achieve).
This also now makes it so only a bitmap of the size of the paintable is
copied/created, rather than the whole page.
When mousing over twitter, 17% of time was spent computing stacking
context transform origins. Since this never changes after the stacking
context is created, we can cache it and avoid all that work.
63c727a was meant to stop clipping absolutely positioned descendants,
but used `is_positioned()` rather than `is_absolutely_positioned()`,
which meant it disabled clipping in many more cases that it should
have.
This is mainly so we can easily read that matrix later, but also has the
benefit of only calculating the matrix once, instead of every time we
paint. :^)
Also, made the `reference_length` parameter optional for the lambda that
extracts transform-function parameters, since it is only needed to
resolve `LengthPercentage` parameters.
This now calls before/after_child_paint() on the parent paintable
of a positioned child. This allows the parent's overflow clipping
to apply to the child.
Previously, before/after_children_paint() was only called for the
"Foreground" paint phase, this meant the backgrounds and other
features of child nodes of a element with overflow: hidden were
not clipped.
Each of these strings would previously rely on StringView's char const*
constructor overload, which would call __builtin_strlen on the string.
Since we now have operator ""sv, we can replace these with much simpler
versions. This opens the door to being able to remove
StringView(char const*).
No functional changes.
When cloning the PaintContext we should be using the painter backed by
the bitmap created for this stacking context layer.
Fixes: 54c3053bc3 ("LibWeb: Preserve paint state when painting...")
For layers that require indirect painting (due to opacity, transform,
etc.) we create a nested PaintContext. Until now, that PaintContext
was created fresh without transferring all the state from the parent
PaintContext.
This replaces the usage of `rounded_int_rect`, whose name did not
accurately reflect the rounding operation happening. For example, the
position of the rect was not rounded but floored, and the size was
pulled through `roundf` before casting to `int` which could result in
inadvertent flooring if the resulting floating point could not exactly
represent the rounded value.
We already walk the entire paint tree within each stacking context in
the main hit testing function (StackingContext::hit_test()), so there's
no need for each individual paintable to walk its own children again.
By not doing that, we remove a source of O(n^2) traversal which made hit
testing on deeply nested web pages unbearably slow.
For stacking contexts that have opacity between 0 and 1, and also
contexts with a 2D transform, we first paint them into a temporary layer
buffer. Then we blend that buffer with the contents in one go.
Before this patch, we were only drawing the content box of the stacking
context into this layer buffer, which led to padding and borders missing
from elements painted this way.
Since there is currently no easy way to handle rotations and skews
with LibGfx this only implements translation and scaling by first
constructing a general 4x4 transformation matrix like outlined in
the css-transforms-1 specification. This is then downgraded to a
Gfx::AffineTransform in order to transform the destination rectangle
used with draw_scaled_bitmap()
While rotation would be nice this already looks pretty good :^)
By the time we're painting, we've already built the stacking context
tree. So instead of asking if a box establishes a stacking context, we
can ask if its paintable *has* a stacking context.
This was taking up ~6% of the profile when mousing around on the HTML
specification. With this change, it disappears completely. :^)
Instead of calling quick_sort() every time a StackingContext child
is added to a parent, we now do a single pass of sorting work after the
full StackingContext tree has been built.
Before this change, the quick_sort() was ~13.5% of the profile while
hovering links on GitHub in the Browser. After the change, it's down to
~0.6%. Pretty good! :^)
Everything related to hit testing is better off using the painting tree.
The thing being mousemoved over is a paintable, so let's hand that out
directly instead of the corresponding layout node.