Stop converting between CSS and device pixels as part of rendering - the
display list should be as simple as possible, so convert to DevicePixels
once when constructing the display list.
Move the inline dom_node() method to Viewport.cpp so the header no
longer needs the full Document definition. Add explicit includes to
files that relied on the transitive dependency.
Previously, absolutely positioned elements jumped directly to their
containing block's accumulated visual context, skipping effects
(opacity, mix-blend-mode, isolation) from intermediate ancestors. Per
CSS spec, these properties create stacking contexts that abspos elements
cannot escape — they only escape scroll containers and overflow clips.
The scrollport size was previously fetched at refresh time from the
scroll ancestor paintable. Since it doesn't change between layouts,
precompute it alongside the other sticky constraints.
Previously `refresh_scroll_state()` reached back to PaintableBox for
sticky insets. Storing them on ScrollFrame makes it self-contained
for sticky offset computation.
Previously, sticky elements were excluded from propagating their
scroll frame to descendants' accumulated visual context. This meant
that when a sticky element also had scrollable overflow, the scroll
offset was never visually applied to its children during painting.
Compute the perspective matrix on the fly during visual context
assignment instead of caching it in PaintableBox. This reduces
PaintableBox's size and keeps the logic closer to where it's used.
Instead of computing and caching the transform matrix and transform
origin on PaintableBox during resolve_paint_properties(), compute them
inline during assign_accumulated_visual_contexts() where they are
actually consumed. This makes PaintableBox smaller by not wasting space
for properties that are only consumed during AccumulatedVisualContext
tree construction.
Precompute the geometric data needed for sticky positioning during
scroll frame assignment. This avoids walking the paintable tree to query
containing blocks and ancestor geometry during scroll state refresh,
which runs on every scroll event.
Move the visual viewport (pinch-to-zoom) transform from a reserved slot
in DisplayList to the AccumulatedVisualContext tree as a root transform
node. Fixed position elements now correctly inherit from this context.
This requires rebuilding the context tree and display list on each zoom
change, but this overhead will be eliminated by future partial context
tree rebuilds.
Previously, both mask and clip-path were rendered to separate mutable
Gfx::Bitmap objects which forced CPU rasterization. They were then
combined using a CPU pixel-by-pixel operation before being returned
as an ImmutableBitmap.
Instead of including mask in the final bitmap as already rasterized
images, we now use display lists which opens opportunity to utilize
GPU if available.
Bitmap::apply_mask() and ApplyMaskBitmap display list command are no
longer used and have been removed.
Effects (opacity, blend mode, filters) must be applied in the parent's
coordinate space, before the element's transform. Previously this was
handled by manually switching to the parent's visual context when
applying effects at paint time.
By adding EffectsData to AccumulatedVisualContext and positioning it
before TransformData in the chain, effects are now naturally applied in
the correct order during display list replay, eliminating the special
case in StackingContext::paint().
For SVG filters that can generate content from empty elements (feFlood,
feImage, feTurbulence), a transparent FillRect command is emitted to
trigger the filter through the same AVC pipeline.
Reuse existing paintables during relayout to reduce GC allocation
pressure. Each paintable subclass implements reset_for_relayout()
to clear state before reuse.
For position:relative/static elements, use visual parent's state
directly instead of containing block's state + intermediate walk.
This reuses existing context nodes, avoids duplicate allocations,
and eliminates the intermediate ancestor vector construction.
Previously, clip-path was applied only during painting in
StackingContext::paint(), which meant hit testing did not respect
clip-path boundaries. Clicks outside the visible clipped region but
inside the element's bounding box would incorrectly register as hits.
By moving clip-path into AccumulatedVisualContext, it becomes part of
the same system that handles transforms, clips, and scroll offsets for
both painting and hit testing, ensuring consistent behavior.
Remove the now-obsolete ClipFrame infrastructure:
- Delete ClipFrame.h and ClipFrame.cpp
- Remove assign_clip_frames() from ViewportPaintable
- Remove enclosing_clip_frame and own_clip_frame from PaintableBox
- Remove m_clip_state HashMap from ViewportPaintable
Clip handling is now fully managed through AccumulatedVisualContext
nodes with ClipData.
Integrate AccumulatedVisualContext with display list recording and
playback. This is the main commit of the refactoring that delivers the
architectural improvements enabled by AccumulatedVisualContext.
Recording changes:
Each display list command now stores a single
RefPtr<AccumulatedVisualContext> instead of separate scroll_frame_id
and ClipFrame. The recorder simply captures the current accumulated
context when appending commands.
The before_paint()/after_paint() hooks that pushed/popped scroll frame
IDs are replaced by directly setting accumulated_visual_context on the
recorder before painting each element.
Playback changes:
The display list player now uses LCA (Lowest Common Ancestor) based
traversal to switch between visual contexts efficiently. When
transitioning from context A to context B:
1. Find the LCA of A and B in the context tree
2. Pop (restore) states back to the LCA depth
3. Push (save + apply) states from LCA down to B
This approach minimizes redundant save/restore operations. For example,
when rendering siblings that share a common scroll container, the
player keeps that scroll state applied and only switches the divergent
parts of their context chains.
Key deletions:
- Remove translate_by() from all 45 display list commands - commands
are now immutable
- Remove transform/perspective fields from PushStackingContext -
transforms are tracked via AccumulatedVisualContext
- Remove push_scroll_frame_id()/pop_scroll_frame_id() from
DisplayListRecorder
- Remove before_paint()/after_paint() hooks from Paintable
- Merge ApplyOpacity, ApplyCompositeAndBlendingOperator, ApplyFilter
into single ApplyEffects command
Stacking context painting changes:
The StackingContext::paint() method is significantly simplified.
Instead of building a PushStackingContextParams struct with transform
matrices and pushing/popping stacking contexts, it now:
1. Sets the accumulated visual context (which already contains
transforms)
2. Applies effects (opacity, blend mode, filters) if needed
3. Applies clip path if needed
4. Paints the content
5. Restores state
The visual state management that was interleaved throughout the
painting code is now handled uniformly by the context tree.
Cache the scroll state snapshot in ViewportPaintable when
refresh_scroll_state() is called.
The upcoming AccumulatedVisualContext integration requires access to
the scroll state snapshot during hit testing to transform screen
coordinates through scroll frames. Without caching, each hit test would
allocate a new snapshot (a Vector<Entry>), causing many temporary
allocations during mouse movement. Caching the snapshot eliminates this
overhead.
Introduce AccumulatedVisualContext, a tree structure that tracks the
cumulative visual state (scroll offsets, clip regions, transforms,
perspective) for each paintable box.
Motivation:
Before this change, visual state was fragmented across multiple
mechanisms:
- ClipFrame: Tracked clip rectangles, each storing its own
enclosing_scroll_frame_id to handle scroll offset adjustments
- scroll_frame_id: Passed separately to each display list command
- PushStackingContext: Stored transform matrices directly in the command
- Every display list command implemented translate_by() (45 methods
total) to allow scroll offset adjustment during playback
This fragmentation led to:
- Complex, error-prone coordinate transformation logic scattered
throughout the codebase
- Commands being mutated during playback to apply scroll offsets
- Duplicate logic between painting and hit testing for coordinate
transformations
Solution:
AccumulatedVisualContext builds a tree where each node represents a
single visual operation:
- ScrollData: A scroll frame with its ID
- ClipData: A clip rectangle with optional border radii
- TransformData: A 4x4 transform matrix with its origin
- PerspectiveData: A perspective projection matrix
Each PaintableBox stores a reference to its accumulated context node.
The tree structure naturally captures the parent-child relationships,
so traversing from any node to the root gives the complete chain of
visual transformations.
Benefits this enables (in subsequent commits):
- Display list commands become immutable - no more translate_by()
- Single RefPtr<AccumulatedVisualContext> replaces separate
scroll_frame_id and ClipFrame on commands
- LCA-based tree traversal during playback for efficient save/restore
- transform_point_for_hit_test() provides coordinate transformation for
hit testing using the same structure
This adds visit_edges(Cell::Visitor&) methods to various helper structs
that contain GC pointers, and makes sure they are called from owning
GC-heap-allocated objects as needed.
These were found by our Clang plugin after expanding its capabilities.
The added rules will be enforced by CI going forward.
Clip frames for overflow were applied based on whether the box in
question had a non-identity matrix transformation associated with it.
That however is not correct, since specifying a no-op transform like
`scale(1)` still needs to apply clip overflow rectangles. So instead we
need to check whether the element associated with the box in question
has any CSS transforms.
This appears to have been a regression from
9bbc1cd618 and effectively reverts that
commit, but keeps its effect by unifying on the check for CSS transforms
instead.
This fixes some background boxes being rendered for the invisible items
of the carousels on https://computerbase.de/.
Fixes a bug where we would clip `box-shadow` when `overflow: hidden`
was set, which is not supposed to happen since `overflow` only affects
clipping of an element's content.
Steps 4 and 5 were swapped since marking all the nodes between the start
and end of the selection now also marks the end node as full, even if it
should be marked as End.
There could be extra logic to avoid marking it if it is a text node, but
this seems easier.
As a whole, this fixes partially selected non-text nodes. In such cases,
where the selection starts or ends inside a node with descendants, it is
impossible to just select from the start node to the end node since that
would select all descendants of the start node and none of the end node.
Previously, this was only half considered and only if the start node was
a descendant of the end node.
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, we always updated paint-only properties for every
single paintable after layout or style changes.
This could get very expensive in large documents, so this patch makes
it something we can do partially based on "repaint" invalidations.
This cuts down time spent in paint-only property update when scrolling
https://imdb.com/ from 19% to 5%.
...in clip and scroll frames calculation algorithm.
Fix a regression from 719a50c where display-list recording disagreed
with the clipping logic about whether a stacking context is transformed.
`has_css_transform()` returns true whenever the computed transform is
not `none`, which differs from an identity-matrix check. These yield
different results for cases like `translate(0, 0)`.
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.
Instead, collect a list of all the elements with content-visibility:auto
after layout.
This way we can skip the tree traversal when updating the rendering.
This was previously eating up ~300 µs of the 60fps frame budget on
our GitHub repo pages (and even more on large pages).
The contain-paint-stacking-context-001a.html test has been removed
for now because it has a 1px tall blue line at the top that should
not be there. With paint containment, this line is removed only in
the actual test case, but not in the reference. This is because of
the font that we use in testing and happens in Chromium as well if
the test is run with that font.
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.