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.
The flush() call at the end of execute_impl() was accidentally left
behind in 2d2af9cd3b. That commit moved flushing into execute(), but
didn't remove the old call from execute_impl(). This caused every
nested display list to trigger a redundant GPU flush.
On an M4 MacBook, this improves Discord from ~65 FPS to 120 FPS.
Add a pre-computed `has_empty_effective_clip` flag on
AccumulatedVisualContext that propagates from parent to child. When a
clip rect or clip path has zero area, all descendant commands are
skipped at display list recording time in `DisplayList::append()`,
so they are never stored or executed.
This allows skipping ~10% of display list commands in the Discord app.
Per the CSS Overflow spec, overflow properties apply only to block
containers, flex containers, and grid containers — not SVG graphics
elements. Add an `is<SVGPaintable>` check in
`overflow_property_applies()` to return false for SVG inner elements
like `<g>`, `<rect>`, `<path>`.
This doesn't affect `<svg>` elements (which use `SVGSVGPaintable`, a
direct `PaintableBox` subclass) or `<foreignObject>` (which uses
`SVGForeignObjectPaintable`, a `PaintableWithLines` subclass) — both
correctly keep their overflow clips.
All mask call sites now use saveLayer+DstIn compositing, so the AddMask
command, its SkSL runtime shaders, and CachedRuntimeEffects are no
longer needed.
This applies the same pattern used for background-clip: text (commit
f2e6f70fbb).
Results in visible performance improvement in Discord app where
previously, according to profiles, we spent lots of time allocating
surfaces for masks.
Instead of using a custom paintable to draw the controls for video and
audio elements, we build them out of plain old HTML elements within a
shadow root.
This required a few hacks in the previous commits in order to allow a
replaced element to host children within a shadow root, but it's
fairly self-contained.
A big benefit is that we can drive all the UI updates off of plain old
DOM events (except the play button overlay on videos, which uses the
video element representation), so we can test our media and input event
handling more thoroughly. :^)
The control bar visibility is now more similar to how other browsers
handle it. It will show upon hovering over the element, but if the
cursor is kept still for more than a second, it will hide again. While
dragging, the controls remain visible, and will then hide after the
mouse button is released.
The icons have been redesigned from scratch, and the mute icon now
visualizes the volume level along with indicating the mute state.
The chain is already in element→root order, which matches the linked
list's natural traversal via parent pointers. No need to collect into
a Vector first.
These rects are computed from immutable layout data but were
recomputed from scratch on every call, with the CSSPixels saturating
arithmetic showing up as a significant cost in profiles.
Cache them lazily alongside the existing m_absolute_rect cache.
This was 3.3% of CPU time while playing a YouTube video.
Paintable::display() was chasing through layout_node().display() which
goes through computed_values().display() on every call. Since display
never changes after the Paintable is constructed, cache it as a member
set in the constructor.
This was 1.6% of CPU time while playing a YouTube video.
Make CSSPixels::to_float(), to_double(), and to_int() constexpr inline
instead of out-of-line in the .cpp file. These are trivial one-liners
called in very hot painting paths.
Also optimize DevicePixelConverter::rounded_device_rect() to work
directly with CSSPixels values instead of constructing an intermediate
Rect<double> and scaling it.
Cache the result of the visibility+opacity check as a bit field
(m_visible) computed in resolve_paint_properties(), which already runs
before each paint when paint properties need updating.
This makes Paintable::is_visible() a simple inline bit field read
instead of chasing through layout_node->computed_values() every time.
This was 3.3% of CPU time while playing a YouTube video.
There are actually a couple of bugs here:
1. As of commit ebda8fcf11, editing hosts
are now excluded from Node::is_editable. Since this special hit test
handling is specifically for contenteditable nodes, we would not
enter this branch for these nodes.
2. We were not checking if the contenteditable node actually contained
the hit testing position. So if a page had multiple empty editable
nodes, we would just return whichever was hit test first.
These bugs were exposed by 7c9b3c08fa.
This commit resulted in the text cursor hit test node being set as the
document focus node. If we returned the wrong result, we would not set
the correct node.
This was seen on discord, where clicking the message box would result in
the search box being focused.
No callers of draw_painting_surface remain after the previous commits
migrated canvas, video, and SVG to use ExternalContentSource or
ImmutableBitmap snapshots.
present() now snapshots the PaintingSurface into an ImmutableBitmap
and publishes it to the ExternalContentSource, so the rendering thread
never touches the live GPU surface — eliminating the data race
described in the ExternalContentSource commit (problem 1).
Canvas elements are registered with Page and presented once per frame
from the event loop, rather than on every individual draw call in
CRC2D::did_draw(). A dirty flag on HTMLCanvasElement ensures the
snapshot is only taken when content has actually changed, and makes
the present() call in CanvasPaintable::paint() a no-op when the
surface has already been snapshotted for the current frame.
Publish new video frames to an ExternalContentSource, and switch
VideoPaintable from draw_scaled_immutable_bitmap to
draw_external_content.
Because DrawExternalContent reads the latest bitmap at replay time,
frame-only updates (no timeline or control change) now call
set_needs_display(InvalidateDisplayList::No) — skipping display list
rebuilds entirely. This addresses problem 2 from the previous commit.
Two related problems exist in the current display list architecture:
1. DrawPaintingSurface thread safety: CanvasPaintable::paint() records
the *same* PaintingSurface that the canvas rendering context draws
to. The rendering thread later reads from it, but the main thread
may be concurrently drawing — a data race.
2. Video frames force display list rebuilds: each new video frame
triggers set_needs_display() → full display list rebuild.
Both stem from display list commands holding direct references to
content (surface/bitmap) rather than going through an indirection
layer.
ExternalContentSource is a thread-safe, atomically-refcounted
container that holds an ImmutableBitmap snapshot. The accompanying
DrawExternalContent display list command reads from it during replay,
so producers can swap in new content without rebuilding the list.
Subsequent commits migrate canvas, video, and SVG painting to
ExternalContentSource and then remove DrawPaintingSurface.
By doing so, we attenuate the perspective transform on higher resolution
devices such as Retina displays (2x).
This fixes the perspective transform on sites such as
https://poke-holo.simey.me/.
Instead of rendering text glyphs into a separate mask surface and using
clipShader, paint the backgrounds first and then composite the text
glyphs via saveLayer with SkBlendMode::kDstIn. Skia's saveLayer
automatically sizes its backing at device resolution including CSS
transforms, so no manual scale computation is needed.
Fixes pixelation when zooming in on clipped backgrounds on e.g. the
title of https://modern-css.com/.
Chrome and Firefox inflate this rect to accommodate for the font's
ascenders and descenders, while the absolute rect for the fragment
remains unaffected. This fixes ascenders/descenders in text being
clipped when selecting text.
We should decide at a later stage, when we've calculated the final
background painting area, whether that area is empty and we can skip
painting the background.
Fixes#7973
This means we don't need to include `FilterValueListStyleValue.h` in as
many places - reducing the rebuild from editing that file from 717 files
to 19.
`none` isn't a supported value for `<counter-style-name>` and is only
supported directly by `list-style-type` (i.e. not within `counter{s}()`
functions)
Remove 11 heavy includes from Document.h that were only needed for
pointer/reference types (already forward-declared in Forward.h), and
extract the nested ViewportClient interface to a standalone header.
This reduces Document.h's recompilation cascade from ~1228 files to
~717 files (42% reduction). Headers like BrowsingContext.h that were
previously transitively included see even larger improvements (from
~1228 down to ~73 dependents).
We don't have a text node in these, so no fragments either. Maintaining
a special case for this situation seems much simpler than reworking
`contenteditable`s to always have a fragment.
Our change to generate spans in PaintableWithLines from fragments also
broke drawing the caret for empty <input>s and <textarea>s, since spans
was empty in that case.
Fix this by moving caret drawing to PaintableWithLines, and only
invoking it if we have a cursor position set.