This function used layout node pointer to check if it's corresponding to
viewport. There is no need for that, since `is_viewport_paintable()`
does exactly the same check without going through layout node.
Before this change, you could only scroll the current hovered scroll
container, even if it was at the beginning or end and thus having no
effect.
Now, if it doesn't update, it will not be classed as handled and will
move onto the next scroll container.
The overlay shown for the node hovered in the inspector is painted as
part of the normal tree traversal of all paintables. This works well in
most cases, but falls short in specific scenarios:
* If the hovered node or one of its ancestors establishes a stacking
context and there is another element that establishes a stacking
context close by or overlapping it, the overlay and especially the
tooltip can become partially hidden behind the second element. Ditto
for elements that act as if they established a stacking context.
* If the hovered node or one of its ancestors involves clipping, the
clip is applied to the overlay and espicially the tooltip. This can
cause them to be partially invisible.
* Similarly, if the hovered node or one of its ancestors has a defined
mask, the mask is applied to the overlay, often making it mostly
invisible.
* No overlays are shown for SVG nodes because they are painted
differently from HTML documents.
Some of these problems may be fixable with the current system. But some
seem like they fundamentally cannot work fully when the overlays are
painted as part of the regular tree traversal.
Instead we pull out painting the overlay as a separate pass executed
after the tree traversal. This way we ensure that the overlays are
always painted last and therefore on top of everything else. This also
makes sure that the overlays are unaffected by clips and masks. And
since overlay painting is independent from painting the actual elements,
it just works as well.
However we need to be careful, because we still need to apply some of
the steps of the tree traversal to get the correct result. Namely we
need to apply scroll offsets and transforms. To do so, we collect all
ancestors of the hovered node and apply those as if we were in the
normal tree traversal.
The debug option 'Show Line Box Borders' and the inspector overlay for
text nodes are conceptually similar. However they use two different
code paths. This commits unifies both to use the same code.
Previously, we were collapsing whitespace in Layout::TextNode and then
passed the resulting string for further processing through ChunkIterator
-> InlineLevelIterator -> InlineFormattingContext -> LineBuilder ->
LineBoxFragment -> PaintableFragment. Our painting tree is where we deal
with things like range offsets into the underlying text nodes, but since
we modified the original string, the offsets were wrong.
This changes the way we generate fragments:
* Layout::TextNode no longer collapses whitespace as part of its
stored "text for rendering", but moves this logic to ChunkIterator
which splits up this text into separate views whenever whitespace
needs to be collapsed.
* Layout::LineBox now only extends the last fragment if its end offset
is equal to the new fragment's start offset. Otherwise, there's a
gap caused by collapsing whitespace and we need to generate a
separate fragment for that in order to have a correct start offset.
Some tests need new baselines because of the fixed start offsets.
Fixes#566.
PaintContext dates back to a time when display lists didn't exist and it
truly represented "paint context". Renaming it to better align with its
current role.
The faux position we created here is adjusted by the device pixel ratio
later on, which would invoke integer overflow on screens with a DPR
greater than 1.
Instead of creating special data for a mouse move event, let's just add
an explicit leave event handler.
Until now, every paint phase of every PaintableBox injected its own
clipping sequence into the display list:
```
before_paint: Save
AddClipRect (1)
...clip rectangles for each containing block with clip...
AddClipRect (N)
paint: ...paint phase items...
after_paint: Restore
```
Because we ran that sequence for every phase of every box, Skia had to
rebuild clip stack `paint_phases * paintable_boxes` times. Worse,
usually most paint phases contribute no visible drawing at all, yet we
still had to emit clipping items because `before_paint()` has no way to
know that in advance.
This change takes a different approach:
- Clip information is now attached as metadata `ClipFrame` to each
DisplayList item.
- `DisplayListPlayer` groups consecutive commands that share a
`ClipFrame`, applying the clip once at the start of the group and
restoring it once at the end.
Going from 10 ms to 5 ms in rasterization on Discord might not sound
like much, but keep in mind that for 60fps we have 16 ms per frame and
there is a lot more work besides display list rasterization we do in
each frame.
* https://discord.com/channels/1247070541085671459/1247090064480014443
- DisplayList items: 81844 -> 3671
- rasterize time: 10 ms -> 5 ms
- record time: 5 ms -> 3 ms
* https://github.com/LadybirdBrowser/ladybird
- DisplayList items: 7902 -> 1176
- rasterize time: 4 ms -> 4 ms
- record time: 3 ms -> 2 ms
Initially ClippableAndScrollable was introduced, because we had
PaintableBox and InlinePaintable and both wanted to share clipping and
scrolling logic. Now, when InlinePaintable is gone, we could inline
ClippableAndScrollable implementation into PaintableBox.
Previously, we always applied the enclosing clip rectangle for all paint
phases except overlays, and the own clip rectangle for the background
and foreground phases. The problem is that applying a clip rectangle
means emitting an AddClipRect display list item for each clip rectangle
in the containing block. With this change, we choose whether to include
the own clip based on the paint phase and this way avoid emitting
AddClipRect for enclosing clip rectangles twice.
This way we can reuse the logic between PaintableWithLines and
PaintableBox. It also introduces the .is_positioned() check for the
children of a PaintableWithLines, which makes sure to skip positioned
child nodes since those are handled by the StackingContext.
We were calculating the scroll delta based on the last known mouse
position, even if that position ventured far beyond the scroll bar's
rect. This caused weird behavior such as scrolling when the mouse was
clearly not on the scrollbar.
This reworks the scrollbar logic to use the mouse's absolute position
instead of a delta, and to always calculate and set the absolute scroll
offset. This makes it much easier to reason about the scrollbar's
position, plus we don't need the last mouse position anymore.
As far as I can tell, our scroll bar behavior now closely resembles
Firefox' behavior.
Currently, compute_scrollbar_data does not adjust the position of the
scrollbar thumb based on the actual scroll offset. This is because we
perform this offset in most cases inside the display list executor, in
order to allow us to avoid recomputing the display list.
However, there are cases where we do want the thumb rect with an offset
inside PaintableBox. We currently use scroll_thumb_rect to perform that
computation.
In an upcoming patch, we will need both this offset thumb rect and the
scrollbar gutter rect. So this patch moves the computation of the offset
to compute_scrollbar_data, performed behind an optional parameter.
When a scrollbar is not interacting with the mouse, we now draw the
scrollbar slightly slimmer. When the mouse enters the space occupied
by the scrollbar, we enlarge it for easier mouse interactivity.
Previously, all `GC::Cell` derived classes were Weakable. Marking only
those classes that require this functionality as Weakable allows us to
reduce the memory footprint of some frequently used classes.
This reduces the number of `.cpp` files that need to be recompiled when
one of the below header files changes as follows:
CSS/ComputedProperties.h: 1113 -> 49
CSS/ComputedValues.h: 1120 -> 209
Previous name for misleading because it checks if box could be scrolled
by user input event which is diffent from checking if box is scrollable.
For example box with `overflow: hidden` is scrollable but it can't be
scrolled by user input event.
My previous attempt at resolving the continuation chain tried to deal
with `pointer-events: none` by repeatedly falling back to the parent
paintable until one was found that _would_ want to handle pointer
events. But since we were no longer performing hit-tests on those
paintables, false positives could pop up. This could happen for
out-of-flow block elements that did not overlap with their parent rects,
for example.
This approach works much better since it only handles the continuation
case that's relevant (the "middle" anonymous box) and it does so during
hit-testing instead of after, allowing all the other relevant logic to
come into play.
Our layout tree requires that all containers either have inline or
non-inline children. In order to support the layout of non-inline
elements inside inline elements, we need to do a bit of tree
restructuring. It effectively simulates temporarily closing all inline
nodes, appending the block element, and resumes appending to the last
open inline node.
The acid1.txt expectation needed to be updated to reflect the fact that
we now hoist its <p> elements out of the inline <form> they were in.
Visually, the before and after situations for acid1.html are identical.
For a while we used the wider Paintable type for stacking context,
because it was allowed to be created by InlinePaintable and
PaintableBox. Now, when InlinePaintable type is gone, we can use more
specific PaintableBox type for a stacking context.
Resulting in a massive rename across almost everywhere! Alongside the
namespace change, we now have the following names:
* JS::NonnullGCPtr -> GC::Ref
* JS::GCPtr -> GC::Ptr
* JS::HeapFunction -> GC::Function
* JS::CellImpl -> GC::Cell
* JS::Handle -> GC::Root