IntersectionObserver updates already iterate over each observer and its
observation targets. We then looked the same target and observer pair up
again through Element's registered observer list just to read and write
previousThresholdIndex and previousIsIntersecting.
Store that mutable state with the observer-side observation target
instead. The element-side list now only keeps strong observer
references for lifetime management and unobserve/disconnect.
This deviates from the spec's storage model, so document the difference
next to the preserved spec comments.
Pre-compute per-observer values (root node, root paintable, root
bounds, is_implicit_root, root_is_element) once before the per-target
loop instead of recomputing them for each target.
Add intersection_root_node() which returns GC::Ref<DOM::Node> without
creating GC::Root wrappers. Previously, intersection_root() was called
multiple times per target (in the skip condition check, in
compute_intersection, and again in root_intersection_rectangle inside
compute_intersection), each call creating GC::Root objects that require
heap allocation via new RootImpl.
Pass the pre-computed root bounds and root paintable into
compute_intersection instead of having it call
root_intersection_rectangle() and intersection_root() internally.
For an observer watching N targets, this eliminates roughly 4N heap
allocations per frame.
Per the spec, the observer's [[scrollMargin]] should be applied to
each scroll container's scrollport when walking the containing block
chain. This expands the effective clip rect, allowing targets to be
detected as intersecting before they actually enter the visible area
of the scroll container.
Add a scroll_margin_values() accessor to IntersectionObserver so the
raw LengthPercentage values can be used during intersection computation.
The scroll margin is applied by inflating the scroll container's
padding box rect before clipping the intersection rect against it.
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 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).
Before this change, we were going through the chain of base classes for
each IDL interface object and having them set the prototype to their
prototype.
Instead of doing that, reorder things so that we set the right prototype
immediately in Foo::initialize(), and then don't bother in all the base
class overrides.
This knocks off a ~1% profile item on Speedometer 3.
Instead of bothering the GC heap with a bunch of DOMRect allocations,
we can just pass around CSSPixelRect internally in many cases.
Before this change, we were generating so much DOMRect garbage that
we had to do a garbage collection *every frame* on the Immich demo.
This was due to the large number of intersection observers checked.
We still need to relax way more when idle, but for comparison, before
this change, when doing nothing for 10 seconds on Immich, we'd spend
2.5 seconds updating intersection observers. After this change, we now
spend 600 ms.
This is not really a context, but more of a set of parameters for
creating a Parser. So, treat it as such: Rename it to ParsingParams,
and store its values and methods directly in the Parser instead of
keeping the ParsingContext around.
This has a nice side-effect of not including DOM/Document.h everywhere
that needs a Parser.
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
The main motivation behind this is to remove JS specifics of the Realm
from the implementation of the Heap.
As a side effect of this change, this is a bit nicer to read than the
previous approach, and in my opinion, also makes it a little more clear
that this method is specific to a JavaScript Realm.