Previously, the active document's lifecycle was bound to
SessionHistoryEntry via DocumentState. The ownership chain was:
Navigable → SessionHistoryEntry → DocumentState → Document
This made it impossible to move SessionHistoryEntry to the UI process
(which cannot own DOM::Document). This commit decouples the two by
giving Navigable a direct m_active_document field that serves as the
authoritative source for active_document().
- Navigable owns m_active_document directly; active_document() reads
from it instead of going through the active session history entry.
- DocumentState no longer holds a Document pointer. Instead, it stores
a document_id for "same document?" checks. Same-document navigations
share a DocumentState and thus the same document_id, while
cross-document navigations create a new DocumentState with a new ID.
- A pending_document parameter is threaded through
finalize_a_cross_document_navigation → apply_the_push_or_replace →
apply_the_history_step so the newly created document reaches
activation without being stored on DocumentState.
- For traversal, the population output delivers the document.
A resolved_document is computed per continuation from either the
pending document, the population output, or the current active
document (for same-document traversals).
HTMLScriptElement::execute_script() and SVGScriptElement had spin_until
calls waiting for ready_to_run_scripts to become true. The race exists
because load_html_document() resolves the session history signal and
starts the parser in the same deferred_invoke — so the parser can hit a
<script> before update_for_history_step_application() sets the flag.
Instead of spinning, defer parser->run() until the document is ready.
Document gains a m_deferred_parser_start callback that is invoked when
set_ready_to_run_scripts() is called. The callback is cleared before
invocation to avoid reentrancy issues (parser->run() can synchronously
execute scripts). All three document loading paths (HTML, XML, text)
now check ready_to_run_scripts before starting the parser and defer if
needed.
create_document_for_inline_content() (used for error pages) now calls
set_ready_to_run_scripts() before mutating the document, ensuring the
invariant holds for all parser paths.
The spin_until calls are replaced with VERIFY assertions.
When an SVG is loaded as image data (e.g. via <img>), the
XMLDocumentBuilder returns early for decoded SVGs without calling
completely_finish_loading(). This meant that <use> elements which
forward-reference elements parsed after them never got their shadow
trees populated, since:
1. During parsing, the referenced element doesn't exist yet, so
clone_element_tree_as_our_shadow_tree() receives null.
2. The document_completely_loaded callback never fires.
3. update_use_elements_that_reference_this() returns early because
is_completely_loaded() is false.
Fix this by calling completely_finish_loading() on the document after
parsing in SVGDecodedImageData::create().
Replace per-node heap-allocated AtomicRefCounted
AccumulatedVisualContext objects with a single contiguous Vector inside
AccumulatedVisualContextTree. All nodes for a frame are now stored in
one allocation, using type-safe VisualContextIndex instead of RefPtr
pointers.
This reduces allocation churn, improves cache locality, and opens the
door for future snapshotting of visual context state — similar to how
scroll offsets are snapshotted today.
The conversion of sqrt2 into CSSPixels caused the value to change from
1.41... to 1.42... This led to incorrect results. I moved the division
inside the sqrt to prevent this conversion step.
Introduce Document::update_layout_if_needed_for_node() which only calls
update_layout() when the node is connected. Use it at all call sites
that query layout metrics (offsets, client dimensions, image size, SVG
bounding box, etc.) so disconnected elements no longer trigger an
unnecessary layout.
With per-paintable display list command caching now in place, the
separate paint-only properties resolution phase is no longer needed.
Resolution now happens inline during painting and its cost is amortized
since it only runs on cache miss.
Move all property resolution to point of consumption:
- is_visible() and visible_for_hit_testing() compute on the fly
- Filter resolution moved to assign_accumulated_visual_contexts()
- Border radii, outlines computed on access
- Box shadows, backdrop filter resolved inline during painting
- Background resolution moved into paint_background()
- Mask resolution moved to StackingContext::paint()
- Text fragment and SVG stroke properties resolved during painting
Rename Document::set_needs_display() to set_needs_repaint() and make it
private. External callers must now go through Node/Paintable which
route the request to the document internally.
Fix one existing misuse in AnimationEffect that was calling
document-level set_needs_display() instead of routing through the
target element's paintable.
This is preparation for per-paintable display list command caching:
repaint requests must go through specific paintables so their cached
command lists can be invalidated.
Add unsafe_layout_node(), unsafe_paintable(), and unsafe_paintable_box()
accessors that skip layout-staleness verification. These are for use in
contexts where accessing layout/paintable data is legitimate despite
layout not being up to date: tree construction, style recalculation,
painting, animation interpolation, DOM mutation, and invalidation
propagation.
Also add wrapper APIs on Node to centralize common patterns:
- set_needs_display() wraps if (unsafe_paintable()) ...set_needs_display
- set_needs_paint_only_properties_update() wraps similar
- set_needs_layout_update() wraps if (unsafe_layout_node()) ...
And add Document::layout_is_up_to_date() which checks whether layout
tree update flags are all clear.
width_style_value_from_attribute() and
height_style_value_from_attribute() were showing up as hot functions
when profiling YouTube Music. They call parse_css_value() on every
invocation, but the underlying attributes rarely change. The call path
is SVGSVGBox::natural_size() → negotiate_natural_metrics() → these
functions, so they are invoked on every layout pass.
Use the existing bitmap() method which caches the ImmutableBitmap,
instead of creating a fresh snapshot from the painting surface on
every paint call.
This was regressed by d9e04ec9e8 which replaced draw_painting_surface
with a per-call snapshot to remove the DrawPaintingSurface dependency,
but didn't use the existing bitmap cache.
SVG elements (except the outermost <svg>) use SVG's coordinate system,
not the CSS box model, so CSS positioning doesn't apply to them.
This adds SVGElement::adjust_computed_style() to force position:static
on all SVG elements except the outermost <svg> element (which has no
owner_svg_element()). SVGSymbolElement's existing override now calls
Base::adjust_computed_style() to inherit this behavior.
With this in place, the FIXME in layout_absolutely_positioned_element()
for SVG boxes becomes unreachable and is replaced with
VERIFY_NOT_REACHED().
The last consumer of draw_painting_surface now snapshots the surface
into an ImmutableBitmap and uses draw_scaled_immutable_bitmap instead,
removing the final dependency on DrawPaintingSurface.
This is already fully absolutized as part of the computation process so
we can resolve it in `ComputedProperties::stroke_dasharray` instead of
persisting it as a `NumberOrCalculated`
Remove unused/redundant includes from Element.h:
- AK/IterationDecision.h (redundant)
- ARIA/AttributeNames.h (redundant via ARIAMixin.h)
- CSS/CascadedProperties.h (redundant via PseudoElement.h)
- CSS/StylePropertyMapReadOnly.h (pointer types only)
- HTML/LazyLoadingElement.h (unused in header)
Extract IntersectionObserverRegistration struct from
IntersectionObserver.h into its own lightweight header.
This breaks the heavy transitive include chain through
IntersectionObserverEntry.h and Geometry/DOMRect.h that
was pulled into every file including Element.h.
Indirect recompilation impact reductions:
- IntersectionObserver.h: ~1387 -> ~27 files
- LazyLoadingElement.h: ~1387 -> ~1002 files
Remove includes from Node.h that are only needed for forward
declarations (AccessibilityTreeNode.h, XMLSerializer.h,
JsonObjectSerializer.h). Extract StyleInvalidationReason and
FragmentSerializationMode enums into standalone lightweight
headers so downstream headers (CSSStyleSheet.h, CSSStyleProperties.h,
HTMLParser.h) can include just the enum they need instead of all of
Node.h. Replace Node.h with forward declarations in headers that only
use Node by pointer/reference.
This breaks the circular dependency between Node.h and
AccessibilityTreeNode.h, reducing AccessibilityTreeNode.h's
recompilation footprint from ~1399 to ~25 files.
These attributes are consumed during layout in SVGFormattingContext to
compute the viewbox transform. They don't affect the layout tree
structure, so a layout-only invalidation is sufficient instead of a
full layout tree rebuild.
...and SVGDecodedImageData.h
These headers only use Document via forward-declarable references and
smart pointers. Add explicit Document.h includes to .cpp files that
were relying on the transitive include.
This reduces the recompilation cascade when Document.h is modified,
cutting off the transitive path through ~30 SVG element headers.
Move the inline try_resolve_url_to() template body in
SVGGraphicsElement.h to a non-template helper in the .cpp file to
avoid needing Document.h and ShadowRoot.h in the header.
Add explicit includes to files that relied on the transitive dependency.
The SVG `transform` attribute is stored on the DOM element and read
directly during layout by
`SVGFormattingContext::layout_graphics_element()`. Since changing the
transform doesn't affect which DOM nodes produce layout boxes or how
they're structured, we only need to re-run layout on the existing tree
instead of rebuild it from scratch.
SVGPolylineElement::get_path() produces a path based on the points
attribute. During layout, this path is copied into paintables. If the
points attribute changes after layout, the path stored in the paintable
becomes stale. Fix by calling set_needs_layout_update() when it changes
so the path is recomputed.
SVGPolygonElement::get_path() produces a path based on the points
attribute. During layout, this path is copied into paintables. If the
points attribute changes after layout, the path stored in the paintable
becomes stale. Fix by calling set_needs_layout_update() when it changes
so the path is recomputed.
SVGPathElement::get_path() produces a path based on the d attribute.
During layout, this path is copied into paintables. If the d attribute
changes after layout, the path stored in the paintable becomes stale.
Fix by calling set_needs_layout_update() when it changes so the path
is recomputed.
SVGLineElement::get_path() produces a path based on x1, y1, x2, and
y2 attributes. During layout, this path is copied into paintables.
If any of these attributes change after layout, the path stored in the
paintable becomes stale. Fix by calling set_needs_layout_update() when
a geometry attribute changes so the path is recomputed.
Every user of this actually wants an ancestor in the flat tree - taking
things like `<slot>` into account. So rename it and adjust its behavior
to use that.
No observable behavior change — this is a refactoring of how SVG
transforms are computed for mask and clip content.
Previously, SVGGraphicsElement::get_transform() computed accumulated
transforms by walking the DOM tree. This didn't work correctly for masks
and clips because their DOM structure differs from layout: in the DOM,
mask/clip elements are siblings or ancestors of their targets, but in
the layout tree they become children of the target element. Walking the
DOM tree caused transforms from the target's ancestors to incorrectly
leak into mask/clip content.
The fix walks the layout tree instead of the DOM tree, which means
transform computation must happen during layout (where we have access to
the layout tree structure) rather than on-demand from the DOM element.
This moves the logic to SVGFormattingContext and removes get_transform()
since it can no longer serve its purpose — the DOM element only provides
element_transform() for its own transform now.
During layout, we walk up the layout tree and stop at mask/clip
boundaries, ensuring mask/clip content stays in its own coordinate
space. The target's accumulated transform is applied separately at paint
time.