mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2025-07-29 20:29:18 +00:00
LibWeb: Optimize :hover style invalidation
Instead of recalculating styles for all nodes in the common ancestor of the new and old hovered nodes' subtrees, this change introduces the following approach: - While calculating ComputedProperties, a flag is saved if any rule applied to an element is affected by the hover state during the execution of SelectorEngine::matches(). - When the hovered element changes, styles are marked for recalculation only if the flag saved in ComputedProperties indicates that the element could be affected by the hover state.
This commit is contained in:
parent
db58986e5f
commit
e465e922bd
Notes:
github-actions[bot]
2025-01-04 19:33:47 +00:00
Author: https://github.com/kalenikaliaksandr
Commit: e465e922bd
Pull-request: https://github.com/LadybirdBrowser/ladybird/pull/3121
9 changed files with 124 additions and 77 deletions
|
@ -213,6 +213,9 @@ public:
|
|||
|
||||
static float resolve_opacity_value(CSSStyleValue const& value);
|
||||
|
||||
bool did_match_any_hover_rules() const { return m_did_match_any_hover_rules; }
|
||||
void set_did_match_any_hover_rules() { m_did_match_any_hover_rules = true; }
|
||||
|
||||
private:
|
||||
friend class StyleComputer;
|
||||
|
||||
|
@ -236,6 +239,8 @@ private:
|
|||
mutable RefPtr<Gfx::FontCascadeList> m_font_list;
|
||||
|
||||
Optional<CSSPixels> m_line_height;
|
||||
|
||||
bool m_did_match_any_hover_rules { false };
|
||||
};
|
||||
|
||||
}
|
||||
|
|
|
@ -35,7 +35,7 @@
|
|||
|
||||
namespace Web::SelectorEngine {
|
||||
|
||||
static inline bool matches(CSS::Selector const& selector, Optional<CSS::CSSStyleSheet const&> style_sheet_for_rule, int component_list_index, DOM::Element const& element, GC::Ptr<DOM::Element const> shadow_host, GC::Ptr<DOM::ParentNode const> scope, SelectorKind selector_kind, GC::Ptr<DOM::Element const> anchor = nullptr);
|
||||
static inline bool matches(CSS::Selector const& selector, int component_list_index, DOM::Element const& element, GC::Ptr<DOM::Element const> shadow_host, MatchContext& context, GC::Ptr<DOM::ParentNode const> scope, SelectorKind selector_kind, GC::Ptr<DOM::Element const> anchor = nullptr);
|
||||
|
||||
// Upward traversal for descendant (' ') and immediate child combinator ('>')
|
||||
// If we're starting inside a shadow tree, traversal stops at the nearest shadow host.
|
||||
|
@ -80,10 +80,10 @@ static inline bool matches_lang_pseudo_class(DOM::Element const& element, Vector
|
|||
}
|
||||
|
||||
// https://drafts.csswg.org/selectors-4/#relational
|
||||
static inline bool matches_relative_selector(CSS::Selector const& selector, size_t compound_index, Optional<CSS::CSSStyleSheet const&> style_sheet_for_rule, DOM::Element const& element, GC::Ptr<DOM::Element const> shadow_host, GC::Ref<DOM::Element const> anchor)
|
||||
static inline bool matches_relative_selector(CSS::Selector const& selector, size_t compound_index, DOM::Element const& element, GC::Ptr<DOM::Element const> shadow_host, MatchContext& context, GC::Ref<DOM::Element const> anchor)
|
||||
{
|
||||
if (compound_index >= selector.compound_selectors().size())
|
||||
return matches(selector, style_sheet_for_rule, element, shadow_host, {}, {}, SelectorKind::Relative, anchor);
|
||||
return matches(selector, element, shadow_host, context, {}, {}, SelectorKind::Relative, anchor);
|
||||
|
||||
switch (selector.compound_selectors()[compound_index].combinator) {
|
||||
// Shouldn't be possible because we've parsed relative selectors, which always have a combinator, implicitly or explicitly.
|
||||
|
@ -95,7 +95,7 @@ static inline bool matches_relative_selector(CSS::Selector const& selector, size
|
|||
if (!descendant.is_element())
|
||||
return TraversalDecision::Continue;
|
||||
auto const& descendant_element = static_cast<DOM::Element const&>(descendant);
|
||||
if (matches(selector, style_sheet_for_rule, descendant_element, shadow_host, {}, {}, SelectorKind::Relative, anchor)) {
|
||||
if (matches(selector, descendant_element, shadow_host, context, {}, {}, SelectorKind::Relative, anchor)) {
|
||||
has = true;
|
||||
return TraversalDecision::Break;
|
||||
}
|
||||
|
@ -109,9 +109,9 @@ static inline bool matches_relative_selector(CSS::Selector const& selector, size
|
|||
if (!child.is_element())
|
||||
return IterationDecision::Continue;
|
||||
auto const& child_element = static_cast<DOM::Element const&>(child);
|
||||
if (!matches(selector, style_sheet_for_rule, compound_index, child_element, shadow_host, {}, SelectorKind::Relative, anchor))
|
||||
if (!matches(selector, compound_index, child_element, shadow_host, context, {}, SelectorKind::Relative, anchor))
|
||||
return IterationDecision::Continue;
|
||||
if (matches_relative_selector(selector, compound_index + 1, style_sheet_for_rule, child_element, shadow_host, anchor)) {
|
||||
if (matches_relative_selector(selector, compound_index + 1, child_element, shadow_host, context, anchor)) {
|
||||
has = true;
|
||||
return IterationDecision::Break;
|
||||
}
|
||||
|
@ -123,15 +123,15 @@ static inline bool matches_relative_selector(CSS::Selector const& selector, size
|
|||
auto* sibling = element.next_element_sibling();
|
||||
if (!sibling)
|
||||
return false;
|
||||
if (!matches(selector, style_sheet_for_rule, compound_index, *sibling, shadow_host, {}, SelectorKind::Relative, anchor))
|
||||
if (!matches(selector, compound_index, *sibling, shadow_host, context, {}, SelectorKind::Relative, anchor))
|
||||
return false;
|
||||
return matches_relative_selector(selector, compound_index + 1, style_sheet_for_rule, *sibling, shadow_host, anchor);
|
||||
return matches_relative_selector(selector, compound_index + 1, *sibling, shadow_host, context, anchor);
|
||||
}
|
||||
case CSS::Selector::Combinator::SubsequentSibling: {
|
||||
for (auto const* sibling = element.next_element_sibling(); sibling; sibling = sibling->next_element_sibling()) {
|
||||
if (!matches(selector, style_sheet_for_rule, compound_index, *sibling, shadow_host, {}, SelectorKind::Relative, anchor))
|
||||
if (!matches(selector, compound_index, *sibling, shadow_host, context, {}, SelectorKind::Relative, anchor))
|
||||
continue;
|
||||
if (matches_relative_selector(selector, compound_index + 1, style_sheet_for_rule, *sibling, shadow_host, anchor))
|
||||
if (matches_relative_selector(selector, compound_index + 1, *sibling, shadow_host, context, anchor))
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
|
@ -143,9 +143,9 @@ static inline bool matches_relative_selector(CSS::Selector const& selector, size
|
|||
}
|
||||
|
||||
// https://drafts.csswg.org/selectors-4/#relational
|
||||
static inline bool matches_has_pseudo_class(CSS::Selector const& selector, Optional<CSS::CSSStyleSheet const&> style_sheet_for_rule, DOM::Element const& anchor, GC::Ptr<DOM::Element const> shadow_host)
|
||||
static inline bool matches_has_pseudo_class(CSS::Selector const& selector, DOM::Element const& anchor, GC::Ptr<DOM::Element const> shadow_host, MatchContext& context)
|
||||
{
|
||||
return matches_relative_selector(selector, 0, style_sheet_for_rule, anchor, shadow_host, anchor);
|
||||
return matches_relative_selector(selector, 0, anchor, shadow_host, context, anchor);
|
||||
}
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/semantics-other.html#selector-link
|
||||
|
@ -215,7 +215,7 @@ static inline bool matches_indeterminate_pseudo_class(DOM::Element const& elemen
|
|||
return false;
|
||||
}
|
||||
|
||||
static inline Web::DOM::Attr const* get_optionally_namespaced_attribute(CSS::Selector::SimpleSelector::Attribute const& attribute, Optional<CSS::CSSStyleSheet const&> style_sheet_for_rule, DOM::Element const& element)
|
||||
static inline Web::DOM::Attr const* get_optionally_namespaced_attribute(CSS::Selector::SimpleSelector::Attribute const& attribute, GC::Ptr<CSS::CSSStyleSheet const> style_sheet_for_rule, DOM::Element const& element)
|
||||
{
|
||||
auto const& qualified_name = attribute.qualified_name;
|
||||
auto const& attribute_name = qualified_name.name.name;
|
||||
|
@ -237,7 +237,7 @@ static inline Web::DOM::Attr const* get_optionally_namespaced_attribute(CSS::Sel
|
|||
case CSS::Selector::SimpleSelector::QualifiedName::NamespaceType::Any:
|
||||
return element.attributes()->get_attribute_namespace_agnostic(attribute_name);
|
||||
case CSS::Selector::SimpleSelector::QualifiedName::NamespaceType::Named:
|
||||
if (!style_sheet_for_rule.has_value())
|
||||
if (!style_sheet_for_rule)
|
||||
return nullptr;
|
||||
auto const& selector_namespace = style_sheet_for_rule->namespace_uri(qualified_name.namespace_);
|
||||
if (!selector_namespace.has_value())
|
||||
|
@ -247,7 +247,7 @@ static inline Web::DOM::Attr const* get_optionally_namespaced_attribute(CSS::Sel
|
|||
VERIFY_NOT_REACHED();
|
||||
}
|
||||
|
||||
static inline bool matches_attribute(CSS::Selector::SimpleSelector::Attribute const& attribute, [[maybe_unused]] Optional<CSS::CSSStyleSheet const&> style_sheet_for_rule, DOM::Element const& element)
|
||||
static inline bool matches_attribute(CSS::Selector::SimpleSelector::Attribute const& attribute, [[maybe_unused]] GC::Ptr<CSS::CSSStyleSheet const> style_sheet_for_rule, DOM::Element const& element)
|
||||
{
|
||||
auto const& attribute_name = attribute.qualified_name.name.name;
|
||||
|
||||
|
@ -435,7 +435,7 @@ static bool matches_open_state_pseudo_class(DOM::Element const& element, bool op
|
|||
}
|
||||
|
||||
// https://drafts.csswg.org/css-scoping/#host-selector
|
||||
static inline bool matches_host_pseudo_class(GC::Ref<DOM::Element const> element, GC::Ptr<DOM::Element const> shadow_host, CSS::SelectorList const& argument_selector_list, Optional<CSS::CSSStyleSheet const&> style_sheet_for_rule)
|
||||
static inline bool matches_host_pseudo_class(GC::Ref<DOM::Element const> element, GC::Ptr<DOM::Element const> shadow_host, MatchContext& context, CSS::SelectorList const& argument_selector_list)
|
||||
{
|
||||
// When evaluated in the context of a shadow tree, it matches the shadow tree’s shadow host if the shadow host,
|
||||
// in its normal context, matches the selector argument. In any other context, it matches nothing.
|
||||
|
@ -444,12 +444,12 @@ static inline bool matches_host_pseudo_class(GC::Ref<DOM::Element const> element
|
|||
|
||||
// NOTE: There's either 0 or 1 argument selector, since the syntax is :host or :host(<compound-selector>)
|
||||
if (!argument_selector_list.is_empty())
|
||||
return matches(argument_selector_list.first(), style_sheet_for_rule, element, nullptr);
|
||||
return matches(argument_selector_list.first(), element, nullptr, context);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static inline bool matches_pseudo_class(CSS::Selector::SimpleSelector::PseudoClassSelector const& pseudo_class, Optional<CSS::CSSStyleSheet const&> style_sheet_for_rule, DOM::Element const& element, GC::Ptr<DOM::Element const> shadow_host, GC::Ptr<DOM::ParentNode const> scope, SelectorKind selector_kind)
|
||||
static inline bool matches_pseudo_class(CSS::Selector::SimpleSelector::PseudoClassSelector const& pseudo_class, DOM::Element const& element, GC::Ptr<DOM::Element const> shadow_host, MatchContext& context, GC::Ptr<DOM::ParentNode const> scope, SelectorKind selector_kind)
|
||||
{
|
||||
switch (pseudo_class.type) {
|
||||
case CSS::PseudoClass::Link:
|
||||
|
@ -477,6 +477,7 @@ static inline bool matches_pseudo_class(CSS::Selector::SimpleSelector::PseudoCla
|
|||
case CSS::PseudoClass::Active:
|
||||
return element.is_active();
|
||||
case CSS::PseudoClass::Hover:
|
||||
context.did_match_any_hover_rules = true;
|
||||
return matches_hover_pseudo_class(element);
|
||||
case CSS::PseudoClass::Focus:
|
||||
return element.is_focused();
|
||||
|
@ -513,7 +514,7 @@ static inline bool matches_pseudo_class(CSS::Selector::SimpleSelector::PseudoCla
|
|||
case CSS::PseudoClass::Root:
|
||||
return is<HTML::HTMLHtmlElement>(element);
|
||||
case CSS::PseudoClass::Host:
|
||||
return matches_host_pseudo_class(element, shadow_host, pseudo_class.argument_selector_list, style_sheet_for_rule);
|
||||
return matches_host_pseudo_class(element, shadow_host, context, pseudo_class.argument_selector_list);
|
||||
case CSS::PseudoClass::Scope:
|
||||
return scope ? &element == scope : is<HTML::HTMLHtmlElement>(element);
|
||||
case CSS::PseudoClass::FirstOfType:
|
||||
|
@ -545,20 +546,20 @@ static inline bool matches_pseudo_class(CSS::Selector::SimpleSelector::PseudoCla
|
|||
return false;
|
||||
// These selectors should be relative selectors (https://drafts.csswg.org/selectors-4/#relative-selector)
|
||||
for (auto& selector : pseudo_class.argument_selector_list) {
|
||||
if (matches_has_pseudo_class(selector, style_sheet_for_rule, element, shadow_host))
|
||||
if (matches_has_pseudo_class(selector, element, shadow_host, context))
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
case CSS::PseudoClass::Is:
|
||||
case CSS::PseudoClass::Where:
|
||||
for (auto& selector : pseudo_class.argument_selector_list) {
|
||||
if (matches(selector, style_sheet_for_rule, element, shadow_host))
|
||||
if (matches(selector, element, shadow_host, context))
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
case CSS::PseudoClass::Not:
|
||||
for (auto& selector : pseudo_class.argument_selector_list) {
|
||||
if (matches(selector, style_sheet_for_rule, element, shadow_host))
|
||||
if (matches(selector, element, shadow_host, context))
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
|
@ -575,11 +576,11 @@ static inline bool matches_pseudo_class(CSS::Selector::SimpleSelector::PseudoCla
|
|||
if (!parent)
|
||||
return false;
|
||||
|
||||
auto matches_selector_list = [&style_sheet_for_rule, shadow_host](CSS::SelectorList const& list, DOM::Element const& element) {
|
||||
auto matches_selector_list = [&context, shadow_host](CSS::SelectorList const& list, DOM::Element const& element) {
|
||||
if (list.is_empty())
|
||||
return true;
|
||||
for (auto const& child_selector : list) {
|
||||
if (matches(child_selector, style_sheet_for_rule, element, shadow_host)) {
|
||||
if (matches(child_selector, element, shadow_host, context)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
@ -758,12 +759,12 @@ static inline bool matches_pseudo_class(CSS::Selector::SimpleSelector::PseudoCla
|
|||
static ALWAYS_INLINE bool matches_namespace(
|
||||
CSS::Selector::SimpleSelector::QualifiedName const& qualified_name,
|
||||
DOM::Element const& element,
|
||||
Optional<CSS::CSSStyleSheet const&> style_sheet_for_rule)
|
||||
GC::Ptr<CSS::CSSStyleSheet const> style_sheet_for_rule)
|
||||
{
|
||||
switch (qualified_name.namespace_type) {
|
||||
case CSS::Selector::SimpleSelector::QualifiedName::NamespaceType::Default:
|
||||
// "if no default namespace has been declared for selectors, this is equivalent to *|E."
|
||||
if (!style_sheet_for_rule.has_value() || !style_sheet_for_rule->default_namespace_rule())
|
||||
if (!style_sheet_for_rule || !style_sheet_for_rule->default_namespace_rule())
|
||||
return true;
|
||||
// "Otherwise it is equivalent to ns|E where ns is the default namespace."
|
||||
return element.namespace_uri() == style_sheet_for_rule->default_namespace_rule()->namespace_uri();
|
||||
|
@ -778,7 +779,7 @@ static ALWAYS_INLINE bool matches_namespace(
|
|||
// Unrecognized namespace prefixes are invalid, so don't match.
|
||||
// (We can't detect this at parse time, since a namespace rule may be inserted later.)
|
||||
// So, if we don't have a context to look up namespaces from, we fail to match.
|
||||
if (!style_sheet_for_rule.has_value())
|
||||
if (!style_sheet_for_rule)
|
||||
return false;
|
||||
|
||||
auto selector_namespace = style_sheet_for_rule->namespace_uri(qualified_name.namespace_);
|
||||
|
@ -787,7 +788,7 @@ static ALWAYS_INLINE bool matches_namespace(
|
|||
VERIFY_NOT_REACHED();
|
||||
}
|
||||
|
||||
static inline bool matches(CSS::Selector::SimpleSelector const& component, Optional<CSS::CSSStyleSheet const&> style_sheet_for_rule, DOM::Element const& element, GC::Ptr<DOM::Element const> shadow_host, GC::Ptr<DOM::ParentNode const> scope, SelectorKind selector_kind, [[maybe_unused]] GC::Ptr<DOM::Element const> anchor)
|
||||
static inline bool matches(CSS::Selector::SimpleSelector const& component, DOM::Element const& element, GC::Ptr<DOM::Element const> shadow_host, MatchContext& context, GC::Ptr<DOM::ParentNode const> scope, SelectorKind selector_kind, [[maybe_unused]] GC::Ptr<DOM::Element const> anchor)
|
||||
{
|
||||
if (should_block_shadow_host_matching(component, shadow_host, element))
|
||||
return false;
|
||||
|
@ -807,7 +808,7 @@ static inline bool matches(CSS::Selector::SimpleSelector const& component, Optio
|
|||
}
|
||||
}
|
||||
|
||||
return matches_namespace(qualified_name, element, style_sheet_for_rule);
|
||||
return matches_namespace(qualified_name, element, context.style_sheet_for_rule);
|
||||
}
|
||||
case CSS::Selector::SimpleSelector::Type::Id:
|
||||
return component.name() == element.id();
|
||||
|
@ -818,9 +819,9 @@ static inline bool matches(CSS::Selector::SimpleSelector const& component, Optio
|
|||
return element.has_class(component.name(), case_sensitivity);
|
||||
}
|
||||
case CSS::Selector::SimpleSelector::Type::Attribute:
|
||||
return matches_attribute(component.attribute(), style_sheet_for_rule, element);
|
||||
return matches_attribute(component.attribute(), context.style_sheet_for_rule, element);
|
||||
case CSS::Selector::SimpleSelector::Type::PseudoClass:
|
||||
return matches_pseudo_class(component.pseudo_class(), style_sheet_for_rule, element, shadow_host, scope, selector_kind);
|
||||
return matches_pseudo_class(component.pseudo_class(), element, shadow_host, context, scope, selector_kind);
|
||||
case CSS::Selector::SimpleSelector::Type::PseudoElement:
|
||||
// Pseudo-element matching/not-matching is handled in the top level matches().
|
||||
return true;
|
||||
|
@ -828,7 +829,7 @@ static inline bool matches(CSS::Selector::SimpleSelector const& component, Optio
|
|||
// Nesting either behaves like :is(), or like :scope.
|
||||
// :is() is handled already, by us replacing it with :is() directly, so if we
|
||||
// got here, it's :scope.
|
||||
return matches_pseudo_class(CSS::Selector::SimpleSelector::PseudoClassSelector { .type = CSS::PseudoClass::Scope }, style_sheet_for_rule, element, shadow_host, scope, selector_kind);
|
||||
return matches_pseudo_class(CSS::Selector::SimpleSelector::PseudoClassSelector { .type = CSS::PseudoClass::Scope }, element, shadow_host, context, scope, selector_kind);
|
||||
case CSS::Selector::SimpleSelector::Type::Invalid:
|
||||
// Invalid selectors never match
|
||||
return false;
|
||||
|
@ -836,11 +837,11 @@ static inline bool matches(CSS::Selector::SimpleSelector const& component, Optio
|
|||
VERIFY_NOT_REACHED();
|
||||
}
|
||||
|
||||
bool matches(CSS::Selector const& selector, Optional<CSS::CSSStyleSheet const&> style_sheet_for_rule, int component_list_index, DOM::Element const& element, GC::Ptr<DOM::Element const> shadow_host, GC::Ptr<DOM::ParentNode const> scope, SelectorKind selector_kind, GC::Ptr<DOM::Element const> anchor)
|
||||
bool matches(CSS::Selector const& selector, int component_list_index, DOM::Element const& element, GC::Ptr<DOM::Element const> shadow_host, MatchContext& context, GC::Ptr<DOM::ParentNode const> scope, SelectorKind selector_kind, GC::Ptr<DOM::Element const> anchor)
|
||||
{
|
||||
auto& compound_selector = selector.compound_selectors()[component_list_index];
|
||||
for (auto& simple_selector : compound_selector.simple_selectors) {
|
||||
if (!matches(simple_selector, style_sheet_for_rule, element, shadow_host, scope, selector_kind, anchor)) {
|
||||
if (!matches(simple_selector, element, shadow_host, context, scope, selector_kind, anchor)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
@ -861,7 +862,7 @@ bool matches(CSS::Selector const& selector, Optional<CSS::CSSStyleSheet const&>
|
|||
continue;
|
||||
if (ancestor == anchor)
|
||||
return false;
|
||||
if (matches(selector, style_sheet_for_rule, component_list_index - 1, static_cast<DOM::Element const&>(*ancestor), shadow_host, scope, selector_kind, anchor))
|
||||
if (matches(selector, component_list_index - 1, static_cast<DOM::Element const&>(*ancestor), shadow_host, context, scope, selector_kind, anchor))
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
|
@ -870,17 +871,17 @@ bool matches(CSS::Selector const& selector, Optional<CSS::CSSStyleSheet const&>
|
|||
auto parent = traverse_up(element, shadow_host);
|
||||
if (!parent || !parent->is_element())
|
||||
return false;
|
||||
return matches(selector, style_sheet_for_rule, component_list_index - 1, static_cast<DOM::Element const&>(*parent), shadow_host, scope, selector_kind, anchor);
|
||||
return matches(selector, component_list_index - 1, static_cast<DOM::Element const&>(*parent), shadow_host, context, scope, selector_kind, anchor);
|
||||
}
|
||||
case CSS::Selector::Combinator::NextSibling:
|
||||
VERIFY(component_list_index != 0);
|
||||
if (auto* sibling = element.previous_element_sibling())
|
||||
return matches(selector, style_sheet_for_rule, component_list_index - 1, *sibling, shadow_host, scope, selector_kind, anchor);
|
||||
return matches(selector, component_list_index - 1, *sibling, shadow_host, context, scope, selector_kind, anchor);
|
||||
return false;
|
||||
case CSS::Selector::Combinator::SubsequentSibling:
|
||||
VERIFY(component_list_index != 0);
|
||||
for (auto* sibling = element.previous_element_sibling(); sibling; sibling = sibling->previous_element_sibling()) {
|
||||
if (matches(selector, style_sheet_for_rule, component_list_index - 1, *sibling, shadow_host, scope, selector_kind, anchor))
|
||||
if (matches(selector, component_list_index - 1, *sibling, shadow_host, context, scope, selector_kind, anchor))
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
|
@ -890,24 +891,24 @@ bool matches(CSS::Selector const& selector, Optional<CSS::CSSStyleSheet const&>
|
|||
VERIFY_NOT_REACHED();
|
||||
}
|
||||
|
||||
bool matches(CSS::Selector const& selector, Optional<CSS::CSSStyleSheet const&> style_sheet_for_rule, DOM::Element const& element, GC::Ptr<DOM::Element const> shadow_host, Optional<CSS::Selector::PseudoElement::Type> pseudo_element, GC::Ptr<DOM::ParentNode const> scope, SelectorKind selector_kind, GC::Ptr<DOM::Element const> anchor)
|
||||
bool matches(CSS::Selector const& selector, DOM::Element const& element, GC::Ptr<DOM::Element const> shadow_host, MatchContext& context, Optional<CSS::Selector::PseudoElement::Type> pseudo_element, GC::Ptr<DOM::ParentNode const> scope, SelectorKind selector_kind, GC::Ptr<DOM::Element const> anchor)
|
||||
{
|
||||
VERIFY(!selector.compound_selectors().is_empty());
|
||||
if (pseudo_element.has_value() && selector.pseudo_element().has_value() && selector.pseudo_element().value().type() != pseudo_element)
|
||||
return false;
|
||||
if (!pseudo_element.has_value() && selector.pseudo_element().has_value())
|
||||
return false;
|
||||
return matches(selector, style_sheet_for_rule, selector.compound_selectors().size() - 1, element, shadow_host, scope, selector_kind, anchor);
|
||||
return matches(selector, selector.compound_selectors().size() - 1, element, shadow_host, context, scope, selector_kind, anchor);
|
||||
}
|
||||
|
||||
static bool fast_matches_simple_selector(CSS::Selector::SimpleSelector const& simple_selector, Optional<CSS::CSSStyleSheet const&> style_sheet_for_rule, DOM::Element const& element, GC::Ptr<DOM::Element const> shadow_host)
|
||||
static bool fast_matches_simple_selector(CSS::Selector::SimpleSelector const& simple_selector, DOM::Element const& element, GC::Ptr<DOM::Element const> shadow_host, MatchContext& context)
|
||||
{
|
||||
if (should_block_shadow_host_matching(simple_selector, shadow_host, element))
|
||||
return false;
|
||||
|
||||
switch (simple_selector.type) {
|
||||
case CSS::Selector::SimpleSelector::Type::Universal:
|
||||
return matches_namespace(simple_selector.qualified_name(), element, style_sheet_for_rule);
|
||||
return matches_namespace(simple_selector.qualified_name(), element, context.style_sheet_for_rule);
|
||||
case CSS::Selector::SimpleSelector::Type::TagName:
|
||||
if (element.document().document_type() == DOM::Document::Type::HTML) {
|
||||
if (simple_selector.qualified_name().name.lowercase_name != element.local_name())
|
||||
|
@ -915,7 +916,7 @@ static bool fast_matches_simple_selector(CSS::Selector::SimpleSelector const& si
|
|||
} else if (!Infra::is_ascii_case_insensitive_match(simple_selector.qualified_name().name.name, element.local_name())) {
|
||||
return false;
|
||||
}
|
||||
return matches_namespace(simple_selector.qualified_name(), element, style_sheet_for_rule);
|
||||
return matches_namespace(simple_selector.qualified_name(), element, context.style_sheet_for_rule);
|
||||
case CSS::Selector::SimpleSelector::Type::Class: {
|
||||
// Class selectors are matched case insensitively in quirks mode.
|
||||
// See: https://drafts.csswg.org/selectors-4/#class-html
|
||||
|
@ -925,30 +926,30 @@ static bool fast_matches_simple_selector(CSS::Selector::SimpleSelector const& si
|
|||
case CSS::Selector::SimpleSelector::Type::Id:
|
||||
return simple_selector.name() == element.id();
|
||||
case CSS::Selector::SimpleSelector::Type::Attribute:
|
||||
return matches_attribute(simple_selector.attribute(), style_sheet_for_rule, element);
|
||||
return matches_attribute(simple_selector.attribute(), context.style_sheet_for_rule, element);
|
||||
case CSS::Selector::SimpleSelector::Type::PseudoClass:
|
||||
return matches_pseudo_class(simple_selector.pseudo_class(), style_sheet_for_rule, element, shadow_host, nullptr, SelectorKind::Normal);
|
||||
return matches_pseudo_class(simple_selector.pseudo_class(), element, shadow_host, context, nullptr, SelectorKind::Normal);
|
||||
default:
|
||||
VERIFY_NOT_REACHED();
|
||||
}
|
||||
}
|
||||
|
||||
static bool fast_matches_compound_selector(CSS::Selector::CompoundSelector const& compound_selector, Optional<CSS::CSSStyleSheet const&> style_sheet_for_rule, DOM::Element const& element, GC::Ptr<DOM::Element const> shadow_host)
|
||||
static bool fast_matches_compound_selector(CSS::Selector::CompoundSelector const& compound_selector, DOM::Element const& element, GC::Ptr<DOM::Element const> shadow_host, MatchContext& context)
|
||||
{
|
||||
for (auto const& simple_selector : compound_selector.simple_selectors) {
|
||||
if (!fast_matches_simple_selector(simple_selector, style_sheet_for_rule, element, shadow_host))
|
||||
if (!fast_matches_simple_selector(simple_selector, element, shadow_host, context))
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool fast_matches(CSS::Selector const& selector, Optional<CSS::CSSStyleSheet const&> style_sheet_for_rule, DOM::Element const& element_to_match, GC::Ptr<DOM::Element const> shadow_host)
|
||||
bool fast_matches(CSS::Selector const& selector, DOM::Element const& element_to_match, GC::Ptr<DOM::Element const> shadow_host, MatchContext& context)
|
||||
{
|
||||
DOM::Element const* current = &element_to_match;
|
||||
|
||||
ssize_t compound_selector_index = selector.compound_selectors().size() - 1;
|
||||
|
||||
if (!fast_matches_compound_selector(selector.compound_selectors().last(), style_sheet_for_rule, *current, shadow_host))
|
||||
if (!fast_matches_compound_selector(selector.compound_selectors().last(), *current, shadow_host, context))
|
||||
return false;
|
||||
|
||||
// NOTE: If we fail after following a child combinator, we may need to backtrack
|
||||
|
@ -971,7 +972,7 @@ bool fast_matches(CSS::Selector const& selector, Optional<CSS::CSSStyleSheet con
|
|||
backtrack_state = { current->parent_element(), compound_selector_index };
|
||||
compound_selector = &selector.compound_selectors()[--compound_selector_index];
|
||||
for (current = current->parent_element(); current; current = current->parent_element()) {
|
||||
if (fast_matches_compound_selector(*compound_selector, style_sheet_for_rule, *current, shadow_host))
|
||||
if (fast_matches_compound_selector(*compound_selector, *current, shadow_host, context))
|
||||
break;
|
||||
}
|
||||
if (!current)
|
||||
|
@ -982,7 +983,7 @@ bool fast_matches(CSS::Selector const& selector, Optional<CSS::CSSStyleSheet con
|
|||
current = current->parent_element();
|
||||
if (!current)
|
||||
return false;
|
||||
if (!fast_matches_compound_selector(*compound_selector, style_sheet_for_rule, *current, shadow_host)) {
|
||||
if (!fast_matches_compound_selector(*compound_selector, *current, shadow_host, context)) {
|
||||
if (backtrack_state.element) {
|
||||
current = backtrack_state.element;
|
||||
compound_selector_index = backtrack_state.compound_selector_index;
|
||||
|
|
|
@ -16,9 +16,14 @@ enum class SelectorKind {
|
|||
Relative,
|
||||
};
|
||||
|
||||
bool matches(CSS::Selector const&, Optional<CSS::CSSStyleSheet const&> style_sheet_for_rule, DOM::Element const&, GC::Ptr<DOM::Element const> shadow_host, Optional<CSS::Selector::PseudoElement::Type> = {}, GC::Ptr<DOM::ParentNode const> scope = {}, SelectorKind selector_kind = SelectorKind::Normal, GC::Ptr<DOM::Element const> anchor = nullptr);
|
||||
struct MatchContext {
|
||||
GC::Ptr<CSS::CSSStyleSheet const> style_sheet_for_rule {};
|
||||
bool did_match_any_hover_rules { false };
|
||||
};
|
||||
|
||||
[[nodiscard]] bool fast_matches(CSS::Selector const&, Optional<CSS::CSSStyleSheet const&> style_sheet_for_rule, DOM::Element const&, GC::Ptr<DOM::Element const> shadow_host);
|
||||
bool matches(CSS::Selector const&, DOM::Element const&, GC::Ptr<DOM::Element const> shadow_host, MatchContext& context, Optional<CSS::Selector::PseudoElement::Type> = {}, GC::Ptr<DOM::ParentNode const> scope = {}, SelectorKind selector_kind = SelectorKind::Normal, GC::Ptr<DOM::Element const> anchor = nullptr);
|
||||
|
||||
[[nodiscard]] bool fast_matches(CSS::Selector const&, DOM::Element const&, GC::Ptr<DOM::Element const> shadow_host, MatchContext& context);
|
||||
[[nodiscard]] bool can_use_fast_matches(CSS::Selector const&);
|
||||
|
||||
[[nodiscard]] bool matches_hover_pseudo_class(DOM::Element const&);
|
||||
|
|
|
@ -422,7 +422,7 @@ bool StyleComputer::should_reject_with_ancestor_filter(Selector const& selector)
|
|||
return false;
|
||||
}
|
||||
|
||||
Vector<MatchingRule> StyleComputer::collect_matching_rules(DOM::Element const& element, CascadeOrigin cascade_origin, Optional<CSS::Selector::PseudoElement::Type> pseudo_element, FlyString const& qualified_layer_name) const
|
||||
Vector<MatchingRule> StyleComputer::collect_matching_rules(DOM::Element const& element, CascadeOrigin cascade_origin, Optional<CSS::Selector::PseudoElement::Type> pseudo_element, bool& did_match_any_hover_rules, FlyString const& qualified_layer_name) const
|
||||
{
|
||||
auto const& root_node = element.root();
|
||||
auto shadow_root = is<DOM::ShadowRoot>(root_node) ? static_cast<DOM::ShadowRoot const*>(&root_node) : nullptr;
|
||||
|
@ -435,22 +435,16 @@ Vector<MatchingRule> StyleComputer::collect_matching_rules(DOM::Element const& e
|
|||
|
||||
auto const& rule_cache = rule_cache_for_cascade_origin(cascade_origin);
|
||||
|
||||
bool is_hovered = SelectorEngine::matches_hover_pseudo_class(element);
|
||||
|
||||
Vector<MatchingRule, 512> rules_to_run;
|
||||
auto add_rules_to_run = [&](Vector<MatchingRule> const& rules) {
|
||||
rules_to_run.grow_capacity(rules_to_run.size() + rules.size());
|
||||
if (pseudo_element.has_value()) {
|
||||
for (auto const& rule : rules) {
|
||||
if (rule.must_be_hovered && !is_hovered)
|
||||
continue;
|
||||
if (rule.contains_pseudo_element && filter_namespace_rule(element, rule) && filter_layer(qualified_layer_name, rule))
|
||||
rules_to_run.unchecked_append(rule);
|
||||
}
|
||||
} else {
|
||||
for (auto const& rule : rules) {
|
||||
if (rule.must_be_hovered && !is_hovered)
|
||||
continue;
|
||||
if (!rule.contains_pseudo_element && filter_namespace_rule(element, rule) && filter_layer(qualified_layer_name, rule))
|
||||
rules_to_run.unchecked_append(rule);
|
||||
}
|
||||
|
@ -537,11 +531,16 @@ Vector<MatchingRule> StyleComputer::collect_matching_rules(DOM::Element const& e
|
|||
|
||||
auto const& selector = rule_to_run.absolutized_selectors()[rule_to_run.selector_index];
|
||||
|
||||
SelectorEngine::MatchContext context { .style_sheet_for_rule = *rule_to_run.sheet };
|
||||
ScopeGuard guard = [&] {
|
||||
if (context.did_match_any_hover_rules)
|
||||
did_match_any_hover_rules = true;
|
||||
};
|
||||
if (rule_to_run.can_use_fast_matches) {
|
||||
if (!SelectorEngine::fast_matches(selector, *rule_to_run.sheet, element, shadow_host_to_use))
|
||||
if (!SelectorEngine::fast_matches(selector, element, shadow_host_to_use, context))
|
||||
continue;
|
||||
} else {
|
||||
if (!SelectorEngine::matches(selector, *rule_to_run.sheet, element, shadow_host_to_use, pseudo_element))
|
||||
if (!SelectorEngine::matches(selector, element, shadow_host_to_use, context, pseudo_element))
|
||||
continue;
|
||||
}
|
||||
matching_rules.append(rule_to_run);
|
||||
|
@ -1488,24 +1487,24 @@ void StyleComputer::start_needed_transitions(ComputedProperties const& previous_
|
|||
|
||||
// https://www.w3.org/TR/css-cascade/#cascading
|
||||
// https://drafts.csswg.org/css-cascade-5/#layering
|
||||
GC::Ref<CascadedProperties> StyleComputer::compute_cascaded_values(DOM::Element& element, Optional<CSS::Selector::PseudoElement::Type> pseudo_element, bool& did_match_any_pseudo_element_rules, ComputeStyleMode mode) const
|
||||
GC::Ref<CascadedProperties> StyleComputer::compute_cascaded_values(DOM::Element& element, Optional<CSS::Selector::PseudoElement::Type> pseudo_element, bool& did_match_any_pseudo_element_rules, bool& did_match_any_hover_rules, ComputeStyleMode mode) const
|
||||
{
|
||||
auto cascaded_properties = m_document->heap().allocate<CascadedProperties>();
|
||||
|
||||
// First, we collect all the CSS rules whose selectors match `element`:
|
||||
MatchingRuleSet matching_rule_set;
|
||||
matching_rule_set.user_agent_rules = collect_matching_rules(element, CascadeOrigin::UserAgent, pseudo_element);
|
||||
matching_rule_set.user_agent_rules = collect_matching_rules(element, CascadeOrigin::UserAgent, pseudo_element, did_match_any_hover_rules);
|
||||
sort_matching_rules(matching_rule_set.user_agent_rules);
|
||||
matching_rule_set.user_rules = collect_matching_rules(element, CascadeOrigin::User, pseudo_element);
|
||||
matching_rule_set.user_rules = collect_matching_rules(element, CascadeOrigin::User, pseudo_element, did_match_any_hover_rules);
|
||||
sort_matching_rules(matching_rule_set.user_rules);
|
||||
// @layer-ed author rules
|
||||
for (auto const& layer_name : m_qualified_layer_names_in_order) {
|
||||
auto layer_rules = collect_matching_rules(element, CascadeOrigin::Author, pseudo_element, layer_name);
|
||||
auto layer_rules = collect_matching_rules(element, CascadeOrigin::Author, pseudo_element, did_match_any_hover_rules, layer_name);
|
||||
sort_matching_rules(layer_rules);
|
||||
matching_rule_set.author_rules.append({ layer_name, layer_rules });
|
||||
}
|
||||
// Un-@layer-ed author rules
|
||||
auto unlayered_author_rules = collect_matching_rules(element, CascadeOrigin::Author, pseudo_element);
|
||||
auto unlayered_author_rules = collect_matching_rules(element, CascadeOrigin::Author, pseudo_element, did_match_any_hover_rules);
|
||||
sort_matching_rules(unlayered_author_rules);
|
||||
matching_rule_set.author_rules.append({ {}, unlayered_author_rules });
|
||||
|
||||
|
@ -2299,7 +2298,8 @@ GC::Ptr<ComputedProperties> StyleComputer::compute_style_impl(DOM::Element& elem
|
|||
|
||||
// 1. Perform the cascade. This produces the "specified style"
|
||||
bool did_match_any_pseudo_element_rules = false;
|
||||
auto cascaded_properties = compute_cascaded_values(element, pseudo_element, did_match_any_pseudo_element_rules, mode);
|
||||
bool did_match_any_hover_rules = false;
|
||||
auto cascaded_properties = compute_cascaded_values(element, pseudo_element, did_match_any_pseudo_element_rules, did_match_any_hover_rules, mode);
|
||||
|
||||
element.set_cascaded_properties(pseudo_element, cascaded_properties);
|
||||
|
||||
|
@ -2332,7 +2332,10 @@ GC::Ptr<ComputedProperties> StyleComputer::compute_style_impl(DOM::Element& elem
|
|||
}
|
||||
}
|
||||
|
||||
return compute_properties(element, pseudo_element, cascaded_properties);
|
||||
auto computed_properties = compute_properties(element, pseudo_element, cascaded_properties);
|
||||
if (did_match_any_hover_rules)
|
||||
computed_properties->set_did_match_any_hover_rules();
|
||||
return computed_properties;
|
||||
}
|
||||
|
||||
GC::Ref<ComputedProperties> StyleComputer::compute_properties(DOM::Element& element, Optional<Selector::PseudoElement::Type> pseudo_element, CascadedProperties& cascaded_properties) const
|
||||
|
|
|
@ -146,7 +146,7 @@ public:
|
|||
[[nodiscard]] GC::Ref<ComputedProperties> compute_style(DOM::Element&, Optional<CSS::Selector::PseudoElement::Type> = {}) const;
|
||||
[[nodiscard]] GC::Ptr<ComputedProperties> compute_pseudo_element_style_if_needed(DOM::Element&, Optional<CSS::Selector::PseudoElement::Type>) const;
|
||||
|
||||
Vector<MatchingRule> collect_matching_rules(DOM::Element const&, CascadeOrigin, Optional<CSS::Selector::PseudoElement::Type>, FlyString const& qualified_layer_name = {}) const;
|
||||
Vector<MatchingRule> collect_matching_rules(DOM::Element const&, CascadeOrigin, Optional<CSS::Selector::PseudoElement::Type>, bool& did_match_any_hover_rules, FlyString const& qualified_layer_name = {}) const;
|
||||
|
||||
void invalidate_rule_cache();
|
||||
|
||||
|
@ -191,7 +191,7 @@ private:
|
|||
[[nodiscard]] bool should_reject_with_ancestor_filter(Selector const&) const;
|
||||
|
||||
[[nodiscard]] GC::Ptr<ComputedProperties> compute_style_impl(DOM::Element&, Optional<CSS::Selector::PseudoElement::Type>, ComputeStyleMode) const;
|
||||
[[nodiscard]] GC::Ref<CascadedProperties> compute_cascaded_values(DOM::Element&, Optional<CSS::Selector::PseudoElement::Type>, bool& did_match_any_pseudo_element_rules, ComputeStyleMode) const;
|
||||
[[nodiscard]] GC::Ref<CascadedProperties> compute_cascaded_values(DOM::Element&, Optional<CSS::Selector::PseudoElement::Type>, bool& did_match_any_pseudo_element_rules, bool& did_match_any_hover_rules, ComputeStyleMode) const;
|
||||
static RefPtr<Gfx::FontCascadeList const> find_matching_font_weight_ascending(Vector<MatchingFontCandidate> const& candidates, int target_weight, float font_size_in_pt, bool inclusive);
|
||||
static RefPtr<Gfx::FontCascadeList const> find_matching_font_weight_descending(Vector<MatchingFontCandidate> const& candidates, int target_weight, float font_size_in_pt, bool inclusive);
|
||||
RefPtr<Gfx::FontCascadeList const> font_matching_algorithm(FlyString const& family_name, int weight, int slope, float font_size_in_pt) const;
|
||||
|
|
|
@ -1494,10 +1494,22 @@ void Document::set_hovered_node(Node* node)
|
|||
m_hovered_node = node;
|
||||
|
||||
auto* common_ancestor = find_common_ancestor(old_hovered_node, m_hovered_node);
|
||||
if (common_ancestor)
|
||||
common_ancestor->invalidate_style(StyleInvalidationReason::Hover);
|
||||
else
|
||||
if (!style_computer().has_has_selectors()) {
|
||||
Node& invalidation_root = common_ancestor ? *common_ancestor : document();
|
||||
invalidation_root.for_each_in_inclusive_subtree([&](Node& node) {
|
||||
if (!node.is_element())
|
||||
return TraversalDecision::Continue;
|
||||
auto& element = static_cast<Element&>(node);
|
||||
if (element.affected_by_hover()) {
|
||||
element.set_needs_style_update(true);
|
||||
} else {
|
||||
element.set_needs_inherited_style_update(true);
|
||||
}
|
||||
return TraversalDecision::Continue;
|
||||
});
|
||||
} else {
|
||||
invalidate_style(StyleInvalidationReason::Hover);
|
||||
}
|
||||
|
||||
// https://w3c.github.io/uievents/#mouseout
|
||||
if (old_hovered_node && old_hovered_node != m_hovered_node) {
|
||||
|
|
|
@ -742,7 +742,8 @@ WebIDL::ExceptionOr<bool> Element::matches(StringView selectors) const
|
|||
// 3. If the result of match a selector against an element, using s, this, and scoping root this, returns success, then return true; otherwise, return false.
|
||||
auto sel = maybe_selectors.value();
|
||||
for (auto& s : sel) {
|
||||
if (SelectorEngine::matches(s, {}, *this, nullptr, {}, static_cast<ParentNode const*>(this)))
|
||||
SelectorEngine::MatchContext context;
|
||||
if (SelectorEngine::matches(s, *this, nullptr, context, {}, static_cast<ParentNode const*>(this)))
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
|
@ -761,7 +762,8 @@ WebIDL::ExceptionOr<DOM::Element const*> Element::closest(StringView selectors)
|
|||
auto matches_selectors = [this](CSS::SelectorList const& selector_list, Element const* element) {
|
||||
// 4. For each element in elements, if match a selector against an element, using s, element, and scoping root this, returns success, return element.
|
||||
for (auto const& selector : selector_list) {
|
||||
if (SelectorEngine::matches(selector, {}, *element, nullptr, {}, this))
|
||||
SelectorEngine::MatchContext context;
|
||||
if (SelectorEngine::matches(selector, *element, nullptr, context, {}, this))
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
|
@ -1123,6 +1125,22 @@ GC::Ptr<Layout::NodeWithStyle> Element::get_pseudo_element_node(CSS::Selector::P
|
|||
return nullptr;
|
||||
}
|
||||
|
||||
bool Element::affected_by_hover() const
|
||||
{
|
||||
if (m_computed_properties && m_computed_properties->did_match_any_hover_rules()) {
|
||||
return true;
|
||||
}
|
||||
if (m_pseudo_element_data) {
|
||||
for (auto& pseudo_element : *m_pseudo_element_data) {
|
||||
if (!pseudo_element.computed_properties)
|
||||
continue;
|
||||
if (pseudo_element.computed_properties->did_match_any_hover_rules())
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool Element::has_pseudo_elements() const
|
||||
{
|
||||
if (m_pseudo_element_data) {
|
||||
|
|
|
@ -261,6 +261,8 @@ public:
|
|||
|
||||
static GC::Ptr<Layout::NodeWithStyle> create_layout_node_for_display_type(DOM::Document&, CSS::Display const&, GC::Ref<CSS::ComputedProperties>, Element*);
|
||||
|
||||
bool affected_by_hover() const;
|
||||
|
||||
void set_pseudo_element_node(Badge<Layout::TreeBuilder>, CSS::Selector::PseudoElement::Type, GC::Ptr<Layout::NodeWithStyle>);
|
||||
GC::Ptr<Layout::NodeWithStyle> get_pseudo_element_node(CSS::Selector::PseudoElement::Type) const;
|
||||
bool has_pseudo_elements() const;
|
||||
|
|
|
@ -70,7 +70,8 @@ static WebIDL::ExceptionOr<Variant<GC::Ptr<Element>, GC::Ref<NodeList>>> scope_m
|
|||
// FIXME: This should be shadow-including. https://drafts.csswg.org/selectors-4/#match-a-selector-against-a-tree
|
||||
node.for_each_in_subtree_of_type<Element>([&](auto& element) {
|
||||
for (auto& selector : selectors) {
|
||||
if (SelectorEngine::matches(selector, {}, element, nullptr, {}, node)) {
|
||||
SelectorEngine::MatchContext context;
|
||||
if (SelectorEngine::matches(selector, element, nullptr, context, {}, node)) {
|
||||
if (return_matches == ReturnMatches::First) {
|
||||
single_result = &element;
|
||||
return TraversalDecision::Break;
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue