HTML and SVG link elements both encoded the same pseudo-class list for
hyperlink state changes. Move that CSS policy into LinkInvalidator and
have both call sites delegate to it.
This keeps element-specific code focused on detecting hyperlink state
changes, while the helper owns the affected selector features.
Replace the spin_until in SVGScriptElement::process_the_script_element
with an async fetch that mirrors HTMLScriptElement's mark_as_ready
pattern. External SVG scripts now fetch and execute asynchronously,
matching Chromium's behavior.
For HTML-embedded SVG scripts, the parser pauses via the existing
schedule_resume_check infrastructure, extended to support SVG scripts
through a new pending_parsing_blocking_svg_script slot on Document.
For top-level XML/SVG documents, scripts execute when their fetch
completes; the load event is delayed via DocumentLoadEventDelayer which
the existing XMLDocumentBuilder::document_end already waits on.
Previously, the LibWeb bindings generator would output multiple per
interface files like Prototype/Constructor/Namespace/GlobalMixin
depending on the contents of that IDL file.
This complicates the build system as it means that it does not know
what files will be generated without knowledge of the contents of that
IDL file.
Instead, for each IDL file only generate a single Bindings/<IDLFile>.h
and Bindings/<IDLFile>.cpp.
Previously this walked up the parent chain on every call, which shows
up as a 2.5% item in the profile while watching YouTube videos.
Cache an m_is_connected bit on Node instead, maintained by the DOM
insertion and removal steps.
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.