LibWeb: Reduce allocations in IntersectionObserver hot path
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.
This commit is contained in:
parent
9abb7e4517
commit
e2d5d72f55
3 changed files with 26 additions and 30 deletions
|
|
@ -5188,19 +5188,11 @@ void Document::queue_an_intersection_observer_entry(IntersectionObserver::Inters
|
|||
}
|
||||
|
||||
// https://www.w3.org/TR/intersection-observer/#compute-the-intersection
|
||||
static CSSPixelRect compute_intersection(GC::Ref<Element> target, IntersectionObserver::IntersectionObserver const& observer)
|
||||
static CSSPixelRect compute_intersection(GC::Ref<Element> target, IntersectionObserver::IntersectionObserver const& observer, Painting::PaintableBox const* root_paintable, CSSPixelRect const& root_bounds)
|
||||
{
|
||||
// 1. Let intersectionRect be the result of getting the bounding box for target.
|
||||
auto intersection_rect = target->get_bounding_client_rect();
|
||||
|
||||
// Determine the root paintable so we know when to stop walking the containing block chain.
|
||||
GC::Ptr<Painting::PaintableBox const> root_paintable;
|
||||
auto intersection_root = observer.intersection_root();
|
||||
intersection_root.visit([&](auto& node) {
|
||||
if (node->paintable_box())
|
||||
root_paintable = node->paintable_box();
|
||||
});
|
||||
|
||||
// 2. Let container be the containing block of target.
|
||||
// 3. While container is not root:
|
||||
if (auto const* target_paintable = target->paintable_box()) {
|
||||
|
|
@ -5213,7 +5205,7 @@ static CSSPixelRect compute_intersection(GC::Ref<Element> target, IntersectionOb
|
|||
// intersectionRect by clipping to the viewport of the document, and update
|
||||
// container to be the browsing context container of container.
|
||||
|
||||
// NOTE: Steps 3.2 (map to container coordinate space) and 3.4 (update container) are
|
||||
// NOTE: Steps 3.2 (map to container coordinate space) and 3.5 (update container) are
|
||||
// unnecessary here because get_bounding_client_rect() and transform_rect_to_viewport()
|
||||
// already produce viewport-relative coordinates.
|
||||
|
||||
|
|
@ -5247,8 +5239,7 @@ static CSSPixelRect compute_intersection(GC::Ref<Element> target, IntersectionOb
|
|||
// FIXME: 4. Map intersectionRect to the coordinate space of root.
|
||||
|
||||
// 5. Update intersectionRect by intersecting it with the root intersection rectangle.
|
||||
auto root_intersection_rectangle = observer.root_intersection_rectangle();
|
||||
intersection_rect.intersect(root_intersection_rectangle);
|
||||
intersection_rect.intersect(root_bounds);
|
||||
|
||||
// FIXME: 6. Map intersectionRect to the coordinate space of the viewport of the document containing target.
|
||||
|
||||
|
|
@ -5272,6 +5263,12 @@ void Document::run_the_update_intersection_observations_steps(HighResolutionTime
|
|||
// 1. Let rootBounds be observer’s root intersection rectangle.
|
||||
auto root_bounds = observer->root_intersection_rectangle();
|
||||
|
||||
// Pre-compute per-observer values to avoid repeated work in the per-target loop.
|
||||
auto intersection_root_node = observer->intersection_root_node();
|
||||
auto* root_paintable = intersection_root_node->paintable_box();
|
||||
bool is_implicit_root = observer->is_implicit_root();
|
||||
bool root_is_element = intersection_root_node->is_element();
|
||||
|
||||
// 2. For each target in observer’s internal [[ObservationTargets]] slot, processed in the same order that
|
||||
// observe() was called on each target:
|
||||
for (auto& target : observer->observation_targets()) {
|
||||
|
|
@ -5288,25 +5285,21 @@ void Document::run_the_update_intersection_observations_steps(HighResolutionTime
|
|||
// intersectionRect be a DOMRectReadOnly with x, y, width, and height set to 0.
|
||||
CSSPixelRect intersection_rect { 0, 0, 0, 0 };
|
||||
|
||||
// SPEC ISSUE: It doesn't pass in intersection ratio to "queue an IntersectionObserverEntry" despite needing it.
|
||||
// SPEC ISSUE: It doesn’t pass in intersection ratio to "queue an IntersectionObserverEntry" despite needing it.
|
||||
// This is default 0, as isIntersecting is default false, see step 9.
|
||||
double intersection_ratio = 0.0;
|
||||
|
||||
// 2. If the intersection root is not the implicit root, and target is not in the same document as the intersection root, skip to step 11.
|
||||
// 3. If the intersection root is an Element, and target is not a descendant of the intersection root in the containing block chain, skip to step 11.
|
||||
// FIXME: Actually use the containing block chain.
|
||||
auto intersection_root = observer->intersection_root();
|
||||
auto intersection_root_document = intersection_root.visit([](auto& node) -> GC::Ref<Document> {
|
||||
return node->document();
|
||||
});
|
||||
// NOTE: Check if target has a layout node is not in the spec but required to match other browsers.
|
||||
if (target->layout_node() && (observer->root().has<Empty>() || &target->document() == intersection_root_document.ptr()) && !(intersection_root.has<GC::Root<DOM::Element>>() && !target->is_descendant_of(*intersection_root.get<GC::Root<DOM::Element>>()))) {
|
||||
if (target->layout_node() && (is_implicit_root || &target->document() == &intersection_root_node->document()) && !(root_is_element && !target->is_descendant_of(*intersection_root_node))) {
|
||||
// 4. Set targetRect to the DOMRectReadOnly obtained by getting the bounding box for target.
|
||||
target_rect = target->get_bounding_client_rect();
|
||||
|
||||
// 5. Let intersectionRect be the result of running the compute the intersection algorithm on target and
|
||||
// observer’s intersection root.
|
||||
intersection_rect = compute_intersection(target, observer);
|
||||
intersection_rect = compute_intersection(target, *observer, root_paintable, root_bounds);
|
||||
|
||||
// 6. Let targetArea be targetRect’s area.
|
||||
auto target_area = target_rect.width() * target_rect.height();
|
||||
|
|
|
|||
|
|
@ -243,18 +243,19 @@ String IntersectionObserver::scroll_margin() const
|
|||
// https://www.w3.org/TR/intersection-observer/#intersectionobserver-intersection-root
|
||||
Variant<GC::Root<DOM::Element>, GC::Root<DOM::Document>> IntersectionObserver::intersection_root() const
|
||||
{
|
||||
// The intersection root for an IntersectionObserver is the value of its root attribute
|
||||
// if the attribute is non-null;
|
||||
if (m_root) {
|
||||
if (m_root->is_element())
|
||||
return GC::make_root(static_cast<DOM::Element&>(*m_root));
|
||||
if (m_root->is_document())
|
||||
return GC::make_root(static_cast<DOM::Document&>(*m_root));
|
||||
VERIFY_NOT_REACHED();
|
||||
}
|
||||
auto root_node = intersection_root_node();
|
||||
if (root_node->is_element())
|
||||
return GC::make_root(static_cast<DOM::Element&>(*root_node));
|
||||
return GC::make_root(static_cast<DOM::Document&>(*root_node));
|
||||
}
|
||||
|
||||
// otherwise, it is the top-level browsing context’s document node, referred to as the implicit root.
|
||||
return GC::make_root(as<HTML::Window>(HTML::relevant_global_object(*this)).page().top_level_browsing_context().active_document());
|
||||
GC::Ref<DOM::Node> IntersectionObserver::intersection_root_node() const
|
||||
{
|
||||
if (m_root)
|
||||
return *m_root;
|
||||
|
||||
// The implicit root is the top-level browsing context’s document node.
|
||||
return *as<HTML::Window>(HTML::relevant_global_object(*this)).page().top_level_browsing_context().active_document();
|
||||
}
|
||||
|
||||
// https://www.w3.org/TR/intersection-observer/#intersectionobserver-root-intersection-rectangle
|
||||
|
|
|
|||
|
|
@ -51,6 +51,8 @@ public:
|
|||
bool track_visibility() const { return m_track_visibility; }
|
||||
|
||||
Variant<GC::Root<DOM::Element>, GC::Root<DOM::Document>> intersection_root() const;
|
||||
GC::Ref<DOM::Node> intersection_root_node() const;
|
||||
bool is_implicit_root() const { return !m_root; }
|
||||
CSSPixelRect root_intersection_rectangle() const;
|
||||
|
||||
void queue_entry(Badge<DOM::Document>, GC::Ref<IntersectionObserverEntry>);
|
||||
|
|
|
|||
Loading…
Reference in a new issue