LibWeb: Skip full document style update in getComputedStyle if possible

Before calling update_style() for a getComputedStyle property access,
we now check whether the target element actually needs a style update
by walking the flat tree ancestor chain. If neither the element nor any
of its ancestors have dirty style bits, and there are no document-level
reasons to recalculate style, we skip the update_style() call entirely.

We walk the flat tree (not the DOM tree) because style inheritance
follows slot assignment -- slotted elements inherit from their assigned
slot, not their DOM parent.

This avoids unnecessary work when scripts access computed style
properties on elements whose styles are already up-to-date, which is a
common pattern on the web.
This commit is contained in:
Andreas Kling 2026-02-12 19:35:56 +01:00 committed by Andreas Kling
parent b270b2cacb
commit 4a7ca32af0
3 changed files with 36 additions and 1 deletions

View file

@ -560,7 +560,7 @@ Optional<StyleProperty> CSSStyleProperties::get_direct_property(PropertyNameAndI
// always need update_layout() to ensure both style and layout tree are up to date.
abstract_element.document().update_layout(DOM::UpdateLayoutReason::ResolvedCSSStyleDeclarationProperty);
layout_node = abstract_element.layout_node();
} else {
} else if (abstract_element.document().element_needs_style_update(abstract_element)) {
// Just ensure styles are up to date.
abstract_element.document().update_style();
}

View file

@ -66,6 +66,7 @@
#include <LibWeb/ContentSecurityPolicy/Directives/Directive.h>
#include <LibWeb/ContentSecurityPolicy/Policy.h>
#include <LibWeb/ContentSecurityPolicy/PolicyList.h>
#include <LibWeb/DOM/AbstractElement.h>
#include <LibWeb/DOM/AccessibilityTreeNode.h>
#include <LibWeb/DOM/AdoptedStyleSheets.h>
#include <LibWeb/DOM/Attr.h>
@ -1741,6 +1742,39 @@ void Document::update_style()
m_needs_full_style_update = false;
}
bool Document::element_needs_style_update(AbstractElement const& abstract_element) const
{
// If there are document-level reasons to update style, we can't skip.
if (m_needs_full_style_update)
return true;
if (m_needs_animated_style_update)
return true;
if (m_needs_invalidation_of_elements_affected_by_has)
return true;
if (m_style_invalidator->has_pending_invalidations())
return true;
// Check the element itself.
if (abstract_element.element().needs_style_update())
return true;
if (abstract_element.element().entire_subtree_needs_style_update())
return true;
// Walk the inheritance ancestor chain. We use element_to_inherit_style_from()
// because style inheritance follows the flat tree (slotted elements inherit
// from their assigned slot, not their DOM parent). If any ancestor on the
// path has its style marked dirty, the target element's computed style could
// change via inherited properties, so we must update.
for (auto ancestor = abstract_element.element_to_inherit_style_from(); ancestor.has_value(); ancestor = ancestor->element_to_inherit_style_from()) {
if (ancestor->element().needs_style_update())
return true;
if (ancestor->element().entire_subtree_needs_style_update())
return true;
}
return false;
}
void Document::update_animated_style_if_needed()
{
if (!m_needs_animated_style_update)

View file

@ -360,6 +360,7 @@ public:
void obtain_theme_color();
void update_style();
[[nodiscard]] bool element_needs_style_update(AbstractElement const&) const;
void update_layout(UpdateLayoutReason);
void update_paint_and_hit_testing_properties_if_needed();
void update_animated_style_if_needed();