mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2025-05-28 14:02:51 +00:00
LibWeb: Make :hover invalidation logic reusable for all pseudo classes
We achieve this by keeping track of all checked pseudo class selectors in the SelectorEngine code. We also give StyleComputer per-pseudo-class rule caches.
This commit is contained in:
parent
ed35f9e7c2
commit
e1777f6e79
Notes:
github-actions[bot]
2025-04-17 17:47:22 +00:00
Author: https://github.com/awesomekling
Commit: e1777f6e79
Pull-request: https://github.com/LadybirdBrowser/ladybird/pull/4388
13 changed files with 134 additions and 59 deletions
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright (c) 2018-2024, Andreas Kling <andreas@ladybird.org>
|
||||
* Copyright (c) 2018-2025, Andreas Kling <andreas@ladybird.org>
|
||||
* Copyright (c) 2023-2025, Sam Atkins <sam@ladybird.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
|
@ -18,6 +18,8 @@
|
|||
#include <LibWeb/CSS/ComputedValues.h>
|
||||
#include <LibWeb/CSS/LengthBox.h>
|
||||
#include <LibWeb/CSS/PropertyID.h>
|
||||
#include <LibWeb/CSS/PseudoClass.h>
|
||||
#include <LibWeb/CSS/PseudoClassBitmap.h>
|
||||
#include <LibWeb/CSS/StyleProperty.h>
|
||||
|
||||
namespace Web::CSS {
|
||||
|
@ -228,8 +230,15 @@ 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; }
|
||||
bool has_attempted_match_against_pseudo_class(PseudoClass pseudo_class) const
|
||||
{
|
||||
return m_attempted_pseudo_class_matches.get(pseudo_class);
|
||||
}
|
||||
|
||||
void set_attempted_pseudo_class_matches(PseudoClassBitmap const& results)
|
||||
{
|
||||
m_attempted_pseudo_class_matches = results;
|
||||
}
|
||||
|
||||
private:
|
||||
friend class StyleComputer;
|
||||
|
@ -256,7 +265,7 @@ private:
|
|||
|
||||
Optional<CSSPixels> m_line_height;
|
||||
|
||||
bool m_did_match_any_hover_rules { false };
|
||||
PseudoClassBitmap m_attempted_pseudo_class_matches;
|
||||
};
|
||||
|
||||
}
|
||||
|
|
45
Libraries/LibWeb/CSS/PseudoClassBitmap.h
Normal file
45
Libraries/LibWeb/CSS/PseudoClassBitmap.h
Normal file
|
@ -0,0 +1,45 @@
|
|||
/*
|
||||
* Copyright (c) 2025, Andreas Kling <andreas@ladybird.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <LibWeb/CSS/PseudoClass.h>
|
||||
|
||||
namespace Web::CSS {
|
||||
|
||||
class PseudoClassBitmap {
|
||||
public:
|
||||
PseudoClassBitmap() = default;
|
||||
~PseudoClassBitmap() = default;
|
||||
|
||||
void set(PseudoClass pseudo_class, bool bit)
|
||||
{
|
||||
size_t const index = to_underlying(pseudo_class);
|
||||
if (bit)
|
||||
m_bits |= 1LLU << index;
|
||||
else
|
||||
m_bits &= ~(1LLU << index);
|
||||
}
|
||||
|
||||
bool get(PseudoClass pseudo_class) const
|
||||
{
|
||||
size_t const index = to_underlying(pseudo_class);
|
||||
return (m_bits & (1LLU << index)) != 0;
|
||||
}
|
||||
|
||||
void operator|=(PseudoClassBitmap const& other)
|
||||
{
|
||||
m_bits |= other.m_bits;
|
||||
}
|
||||
|
||||
private:
|
||||
u64 m_bits { 0 };
|
||||
};
|
||||
|
||||
// NOTE: If this changes, we'll have to tweak PseudoClassBitmap a little bit :)
|
||||
static_assert(to_underlying(PseudoClass::__Count) <= 64);
|
||||
|
||||
}
|
|
@ -101,16 +101,12 @@ Selector::Selector(Vector<CompoundSelector>&& compound_selectors)
|
|||
break;
|
||||
}
|
||||
if (simple_selector.type == SimpleSelector::Type::PseudoClass) {
|
||||
if (simple_selector.pseudo_class().type == PseudoClass::Hover) {
|
||||
m_contains_hover_pseudo_class = true;
|
||||
}
|
||||
m_contained_pseudo_classes.set(simple_selector.pseudo_class().type, true);
|
||||
for (auto const& child_selector : simple_selector.pseudo_class().argument_selector_list) {
|
||||
if (child_selector->contains_the_nesting_selector()) {
|
||||
m_contains_the_nesting_selector = true;
|
||||
}
|
||||
if (child_selector->contains_hover_pseudo_class()) {
|
||||
m_contains_hover_pseudo_class = true;
|
||||
}
|
||||
m_contained_pseudo_classes |= child_selector->m_contained_pseudo_classes;
|
||||
}
|
||||
if (m_contains_the_nesting_selector)
|
||||
break;
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright (c) 2018-2020, Andreas Kling <andreas@ladybird.org>
|
||||
* Copyright (c) 2018-2025, Andreas Kling <andreas@ladybird.org>
|
||||
* Copyright (c) 2021-2025, Sam Atkins <sam@ladybird.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
|
@ -14,6 +14,7 @@
|
|||
#include <LibWeb/CSS/Keyword.h>
|
||||
#include <LibWeb/CSS/Parser/ComponentValue.h>
|
||||
#include <LibWeb/CSS/PseudoClass.h>
|
||||
#include <LibWeb/CSS/PseudoClassBitmap.h>
|
||||
#include <LibWeb/CSS/PseudoElement.h>
|
||||
|
||||
namespace Web::CSS {
|
||||
|
@ -236,7 +237,7 @@ public:
|
|||
Optional<PseudoElementSelector> const& pseudo_element() const { return m_pseudo_element; }
|
||||
NonnullRefPtr<Selector> relative_to(SimpleSelector const&) const;
|
||||
bool contains_the_nesting_selector() const { return m_contains_the_nesting_selector; }
|
||||
bool contains_hover_pseudo_class() const { return m_contains_hover_pseudo_class; }
|
||||
bool contains_pseudo_class(PseudoClass pseudo_class) const { return m_contained_pseudo_classes.get(pseudo_class); }
|
||||
bool contains_unknown_webkit_pseudo_element() const;
|
||||
RefPtr<Selector> absolutized(SimpleSelector const& selector_for_nesting) const;
|
||||
u32 specificity() const;
|
||||
|
@ -259,7 +260,8 @@ private:
|
|||
bool m_can_use_fast_matches { false };
|
||||
bool m_can_use_ancestor_filter { false };
|
||||
bool m_contains_the_nesting_selector { false };
|
||||
bool m_contains_hover_pseudo_class { false };
|
||||
|
||||
PseudoClassBitmap m_contained_pseudo_classes;
|
||||
|
||||
void collect_ancestor_hashes();
|
||||
|
||||
|
|
|
@ -433,7 +433,10 @@ static bool matches_optimal_value_pseudo_class(DOM::Element const& element, HTML
|
|||
|
||||
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)
|
||||
{
|
||||
context.attempted_pseudo_class_matches.set(pseudo_class.type, true);
|
||||
switch (pseudo_class.type) {
|
||||
case CSS::PseudoClass::__Count:
|
||||
VERIFY_NOT_REACHED();
|
||||
case CSS::PseudoClass::Link:
|
||||
case CSS::PseudoClass::AnyLink:
|
||||
// NOTE: AnyLink should match whether the link is visited or not, so if we ever start matching
|
||||
|
@ -448,7 +451,6 @@ 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();
|
||||
|
@ -586,6 +588,8 @@ static inline bool matches_pseudo_class(CSS::Selector::SimpleSelector::PseudoCla
|
|||
|
||||
int index = 1;
|
||||
switch (pseudo_class.type) {
|
||||
case CSS::PseudoClass::__Count:
|
||||
VERIFY_NOT_REACHED();
|
||||
case CSS::PseudoClass::NthChild: {
|
||||
if (!matches_selector_list(pseudo_class.argument_selector_list, element))
|
||||
return false;
|
||||
|
|
|
@ -20,7 +20,7 @@ struct MatchContext {
|
|||
GC::Ptr<CSS::CSSStyleSheet const> style_sheet_for_rule {};
|
||||
GC::Ptr<DOM::Element const> subject {};
|
||||
bool collect_per_element_selector_involvement_metadata { false };
|
||||
bool did_match_any_hover_rules { false };
|
||||
CSS::PseudoClassBitmap attempted_pseudo_class_matches {};
|
||||
};
|
||||
|
||||
bool matches(CSS::Selector const&, DOM::Element const&, GC::Ptr<DOM::Element const> shadow_host, MatchContext& context, Optional<CSS::PseudoElement> = {}, GC::Ptr<DOM::ParentNode const> scope = {}, SelectorKind selector_kind = SelectorKind::Normal, GC::Ptr<DOM::Element const> anchor = nullptr);
|
||||
|
|
|
@ -422,10 +422,10 @@ RuleCache const* StyleComputer::rule_cache_for_cascade_origin(CascadeOrigin casc
|
|||
return true;
|
||||
}
|
||||
|
||||
RuleCache const& StyleComputer::get_hover_rules() const
|
||||
RuleCache const& StyleComputer::get_pseudo_class_rule_cache(PseudoClass pseudo_class) const
|
||||
{
|
||||
build_rule_cache_if_needed();
|
||||
return *m_hover_rule_cache;
|
||||
return *m_pseudo_class_rule_cache[to_underlying(pseudo_class)];
|
||||
}
|
||||
|
||||
InvalidationSet StyleComputer::invalidation_set_for_properties(Vector<InvalidationSet::Property> const& properties) const
|
||||
|
@ -472,7 +472,7 @@ bool StyleComputer::invalidation_property_used_in_has_selector(InvalidationSet::
|
|||
return false;
|
||||
}
|
||||
|
||||
Vector<MatchingRule const*> StyleComputer::collect_matching_rules(DOM::Element const& element, CascadeOrigin cascade_origin, Optional<CSS::PseudoElement> pseudo_element, bool& did_match_any_hover_rules, FlyString const& qualified_layer_name) const
|
||||
Vector<MatchingRule const*> StyleComputer::collect_matching_rules(DOM::Element const& element, CascadeOrigin cascade_origin, Optional<CSS::PseudoElement> pseudo_element, PseudoClassBitmap& attempted_pseudo_class_matches, 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;
|
||||
|
@ -567,8 +567,7 @@ Vector<MatchingRule const*> StyleComputer::collect_matching_rules(DOM::Element c
|
|||
.collect_per_element_selector_involvement_metadata = true,
|
||||
};
|
||||
ScopeGuard guard = [&] {
|
||||
if (context.did_match_any_hover_rules)
|
||||
did_match_any_hover_rules = true;
|
||||
attempted_pseudo_class_matches |= context.attempted_pseudo_class_matches;
|
||||
};
|
||||
if (!SelectorEngine::matches(selector, element, shadow_host_to_use, context, pseudo_element))
|
||||
continue;
|
||||
|
@ -1566,24 +1565,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::PseudoElement> pseudo_element, bool& did_match_any_pseudo_element_rules, bool& did_match_any_hover_rules, ComputeStyleMode mode) const
|
||||
GC::Ref<CascadedProperties> StyleComputer::compute_cascaded_values(DOM::Element& element, Optional<CSS::PseudoElement> pseudo_element, bool& did_match_any_pseudo_element_rules, PseudoClassBitmap& attempted_pseudo_class_matches, 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, did_match_any_hover_rules);
|
||||
matching_rule_set.user_agent_rules = collect_matching_rules(element, CascadeOrigin::UserAgent, pseudo_element, attempted_pseudo_class_matches);
|
||||
sort_matching_rules(matching_rule_set.user_agent_rules);
|
||||
matching_rule_set.user_rules = collect_matching_rules(element, CascadeOrigin::User, pseudo_element, did_match_any_hover_rules);
|
||||
matching_rule_set.user_rules = collect_matching_rules(element, CascadeOrigin::User, pseudo_element, attempted_pseudo_class_matches);
|
||||
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, did_match_any_hover_rules, layer_name);
|
||||
auto layer_rules = collect_matching_rules(element, CascadeOrigin::Author, pseudo_element, attempted_pseudo_class_matches, 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, did_match_any_hover_rules);
|
||||
auto unlayered_author_rules = collect_matching_rules(element, CascadeOrigin::Author, pseudo_element, attempted_pseudo_class_matches);
|
||||
sort_matching_rules(unlayered_author_rules);
|
||||
matching_rule_set.author_rules.append({ {}, unlayered_author_rules });
|
||||
|
||||
|
@ -2427,8 +2426,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;
|
||||
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);
|
||||
PseudoClassBitmap attempted_pseudo_class_matches;
|
||||
auto cascaded_properties = compute_cascaded_values(element, pseudo_element, did_match_any_pseudo_element_rules, attempted_pseudo_class_matches, mode);
|
||||
|
||||
element.set_cascaded_properties(pseudo_element, cascaded_properties);
|
||||
|
||||
|
@ -2462,8 +2461,7 @@ GC::Ptr<ComputedProperties> StyleComputer::compute_style_impl(DOM::Element& elem
|
|||
}
|
||||
|
||||
auto computed_properties = compute_properties(element, pseudo_element, cascaded_properties);
|
||||
if (did_match_any_hover_rules)
|
||||
computed_properties->set_did_match_any_hover_rules();
|
||||
computed_properties->set_attempted_pseudo_class_matches(attempted_pseudo_class_matches);
|
||||
return computed_properties;
|
||||
}
|
||||
|
||||
|
@ -2834,10 +2832,17 @@ void StyleComputer::make_rule_cache_for_cascade_origin(CascadeOrigin cascade_ori
|
|||
}
|
||||
}
|
||||
|
||||
if (selector.contains_hover_pseudo_class()) {
|
||||
// For hover rule cache we intentionally pass pseudo_element as None, because we don't want to bucket hover rules by pseudo element type
|
||||
m_hover_rule_cache->add_rule(matching_rule, {}, contains_root_pseudo_class);
|
||||
for (size_t i = 0; i < to_underlying(PseudoClass::__Count); ++i) {
|
||||
auto pseudo_class = static_cast<PseudoClass>(i);
|
||||
// If we're not building a rule cache for this pseudo class, just ignore it.
|
||||
if (!m_pseudo_class_rule_cache[i])
|
||||
continue;
|
||||
if (selector.contains_pseudo_class(pseudo_class)) {
|
||||
// For pseudo class rule caches we intentionally pass no pseudo-element, because we don't want to bucket pseudo class rules by pseudo-element type.
|
||||
m_pseudo_class_rule_cache[i]->add_rule(matching_rule, {}, contains_root_pseudo_class);
|
||||
}
|
||||
}
|
||||
|
||||
rule_cache.add_rule(matching_rule, pseudo_element, contains_root_pseudo_class);
|
||||
}
|
||||
++rule_index;
|
||||
|
@ -2965,7 +2970,14 @@ void StyleComputer::build_rule_cache()
|
|||
|
||||
build_qualified_layer_names_cache();
|
||||
|
||||
m_hover_rule_cache = make<RuleCache>();
|
||||
m_pseudo_class_rule_cache[to_underlying(PseudoClass::Hover)] = make<RuleCache>();
|
||||
m_pseudo_class_rule_cache[to_underlying(PseudoClass::Active)] = make<RuleCache>();
|
||||
m_pseudo_class_rule_cache[to_underlying(PseudoClass::Focus)] = make<RuleCache>();
|
||||
m_pseudo_class_rule_cache[to_underlying(PseudoClass::FocusWithin)] = make<RuleCache>();
|
||||
m_pseudo_class_rule_cache[to_underlying(PseudoClass::FocusVisible)] = make<RuleCache>();
|
||||
m_pseudo_class_rule_cache[to_underlying(PseudoClass::Target)] = make<RuleCache>();
|
||||
m_pseudo_class_rule_cache[to_underlying(PseudoClass::TargetWithin)] = make<RuleCache>();
|
||||
|
||||
make_rule_cache_for_cascade_origin(CascadeOrigin::Author, *m_selector_insights);
|
||||
make_rule_cache_for_cascade_origin(CascadeOrigin::User, *m_selector_insights);
|
||||
make_rule_cache_for_cascade_origin(CascadeOrigin::UserAgent, *m_selector_insights);
|
||||
|
@ -2985,7 +2997,7 @@ void StyleComputer::invalidate_rule_cache()
|
|||
// If we are sure that it's safe, we could keep it as an optimization.
|
||||
m_user_agent_rule_cache = nullptr;
|
||||
|
||||
m_hover_rule_cache = nullptr;
|
||||
m_pseudo_class_rule_cache = {};
|
||||
m_style_invalidation_data = nullptr;
|
||||
}
|
||||
|
||||
|
|
|
@ -160,8 +160,9 @@ public:
|
|||
[[nodiscard]] GC::Ref<ComputedProperties> compute_style(DOM::Element&, Optional<CSS::PseudoElement> = {}) const;
|
||||
[[nodiscard]] GC::Ptr<ComputedProperties> compute_pseudo_element_style_if_needed(DOM::Element&, Optional<CSS::PseudoElement>) const;
|
||||
|
||||
RuleCache const& get_hover_rules() const;
|
||||
[[nodiscard]] Vector<MatchingRule const*> collect_matching_rules(DOM::Element const&, CascadeOrigin, Optional<CSS::PseudoElement>, bool& did_match_any_hover_rules, FlyString const& qualified_layer_name = {}) const;
|
||||
[[nodiscard]] RuleCache const& get_pseudo_class_rule_cache(PseudoClass) const;
|
||||
|
||||
[[nodiscard]] Vector<MatchingRule const*> collect_matching_rules(DOM::Element const&, CascadeOrigin, Optional<CSS::PseudoElement>, PseudoClassBitmap& attempted_psuedo_class_matches, FlyString const& qualified_layer_name = {}) const;
|
||||
|
||||
InvalidationSet invalidation_set_for_properties(Vector<InvalidationSet::Property> const&) const;
|
||||
bool invalidation_property_used_in_has_selector(InvalidationSet::Property const&) const;
|
||||
|
@ -213,7 +214,7 @@ private:
|
|||
struct MatchingFontCandidate;
|
||||
|
||||
[[nodiscard]] GC::Ptr<ComputedProperties> compute_style_impl(DOM::Element&, Optional<CSS::PseudoElement>, ComputeStyleMode) const;
|
||||
[[nodiscard]] GC::Ref<CascadedProperties> compute_cascaded_values(DOM::Element&, Optional<CSS::PseudoElement>, bool& did_match_any_pseudo_element_rules, bool& did_match_any_hover_rules, ComputeStyleMode) const;
|
||||
[[nodiscard]] GC::Ref<CascadedProperties> compute_cascaded_values(DOM::Element&, Optional<CSS::PseudoElement>, bool& did_match_any_pseudo_element_rules, PseudoClassBitmap& attempted_pseudo_class_matches, 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;
|
||||
|
@ -292,7 +293,7 @@ private:
|
|||
static void collect_selector_insights(Selector const&, SelectorInsights&);
|
||||
|
||||
OwnPtr<SelectorInsights> m_selector_insights;
|
||||
OwnPtr<RuleCache> m_hover_rule_cache;
|
||||
Array<OwnPtr<RuleCache>, to_underlying(PseudoClass::__Count)> m_pseudo_class_rule_cache;
|
||||
OwnPtr<StyleInvalidationData> m_style_invalidation_data;
|
||||
OwnPtr<RuleCachesForDocumentAndShadowRoots> m_author_rule_cache;
|
||||
OwnPtr<RuleCachesForDocumentAndShadowRoots> m_user_rule_cache;
|
||||
|
|
|
@ -1779,11 +1779,11 @@ void Document::invalidate_style_of_elements_affected_by_has()
|
|||
}
|
||||
}
|
||||
|
||||
void Document::invalidate_style_for_elements_affected_by_hover_change(Node& old_new_hovered_common_ancestor, GC::Ptr<Node> hovered_node)
|
||||
void Document::invalidate_style_for_elements_affected_by_pseudo_class_change(CSS::PseudoClass pseudo_class, auto& element_slot, Node& old_new_common_ancestor, auto node)
|
||||
{
|
||||
auto const& hover_rules = style_computer().get_hover_rules();
|
||||
auto const& rules = style_computer().get_pseudo_class_rule_cache(pseudo_class);
|
||||
|
||||
auto& root = old_new_hovered_common_ancestor.root();
|
||||
auto& root = old_new_common_ancestor.root();
|
||||
auto shadow_root = is<ShadowRoot>(root) ? static_cast<ShadowRoot const*>(&root) : nullptr;
|
||||
|
||||
auto& style_computer = this->style_computer();
|
||||
|
@ -1814,12 +1814,12 @@ void Document::invalidate_style_for_elements_affected_by_hover_change(Node& old_
|
|||
return false;
|
||||
};
|
||||
|
||||
auto matches_different_set_of_hover_rules_after_hovered_element_change = [&](Element const& element) {
|
||||
auto matches_different_set_of_rules_after_state_change = [&](Element const& element) {
|
||||
bool result = false;
|
||||
hover_rules.for_each_matching_rules(element, {}, [&](auto& rules) {
|
||||
rules.for_each_matching_rules(element, {}, [&](auto& rules) {
|
||||
for (auto& rule : rules) {
|
||||
bool before = does_rule_match_on_element(element, rule);
|
||||
TemporaryChange change { m_hovered_node, hovered_node };
|
||||
TemporaryChange change { element_slot, node };
|
||||
bool after = does_rule_match_on_element(element, rule);
|
||||
if (before != after) {
|
||||
result = true;
|
||||
|
@ -1831,17 +1831,17 @@ void Document::invalidate_style_for_elements_affected_by_hover_change(Node& old_
|
|||
return result;
|
||||
};
|
||||
|
||||
Function<void(Node&)> invalidate_hovered_elements_recursively = [&](Node& node) -> void {
|
||||
Function<void(Node&)> invalidate_affected_elements_recursively = [&](Node& node) -> void {
|
||||
if (node.is_element()) {
|
||||
auto& element = static_cast<Element&>(node);
|
||||
style_computer.push_ancestor(element);
|
||||
if (element.affected_by_hover() && matches_different_set_of_hover_rules_after_hovered_element_change(element)) {
|
||||
if (element.affected_by_pseudo_class(pseudo_class) && matches_different_set_of_rules_after_state_change(element)) {
|
||||
element.set_needs_style_update(true);
|
||||
}
|
||||
}
|
||||
|
||||
node.for_each_child([&](auto& child) {
|
||||
invalidate_hovered_elements_recursively(child);
|
||||
invalidate_affected_elements_recursively(child);
|
||||
return IterationDecision::Continue;
|
||||
});
|
||||
|
||||
|
@ -1849,12 +1849,12 @@ void Document::invalidate_style_for_elements_affected_by_hover_change(Node& old_
|
|||
style_computer.pop_ancestor(static_cast<Element&>(node));
|
||||
};
|
||||
|
||||
invalidate_hovered_elements_recursively(root);
|
||||
invalidate_affected_elements_recursively(root);
|
||||
}
|
||||
|
||||
void Document::set_hovered_node(Node* node)
|
||||
void Document::set_hovered_node(GC::Ptr<Node> node)
|
||||
{
|
||||
if (m_hovered_node.ptr() == node)
|
||||
if (m_hovered_node == node)
|
||||
return;
|
||||
|
||||
GC::Ptr<Node> old_hovered_node = move(m_hovered_node);
|
||||
|
@ -1868,11 +1868,11 @@ void Document::set_hovered_node(Node* node)
|
|||
new_hovered_node_root = node->root();
|
||||
if (old_hovered_node_root != new_hovered_node_root) {
|
||||
if (old_hovered_node_root)
|
||||
invalidate_style_for_elements_affected_by_hover_change(*old_hovered_node_root, node);
|
||||
invalidate_style_for_elements_affected_by_pseudo_class_change(CSS::PseudoClass::Hover, m_hovered_node, *old_hovered_node_root, node);
|
||||
if (new_hovered_node_root)
|
||||
invalidate_style_for_elements_affected_by_hover_change(*new_hovered_node_root, node);
|
||||
invalidate_style_for_elements_affected_by_pseudo_class_change(CSS::PseudoClass::Hover, m_hovered_node, *new_hovered_node_root, node);
|
||||
} else {
|
||||
invalidate_style_for_elements_affected_by_hover_change(*common_ancestor, node);
|
||||
invalidate_style_for_elements_affected_by_pseudo_class_change(CSS::PseudoClass::Hover, m_hovered_node, *common_ancestor, node);
|
||||
}
|
||||
|
||||
m_hovered_node = node;
|
||||
|
|
|
@ -253,8 +253,9 @@ public:
|
|||
|
||||
virtual FlyString node_name() const override { return "#document"_fly_string; }
|
||||
|
||||
void invalidate_style_for_elements_affected_by_hover_change(Node& old_new_hovered_common_ancestor, GC::Ptr<Node> hovered_node);
|
||||
void set_hovered_node(Node*);
|
||||
void invalidate_style_for_elements_affected_by_pseudo_class_change(CSS::PseudoClass, auto& element_slot, Node& old_new_common_ancestor, auto node);
|
||||
|
||||
void set_hovered_node(GC::Ptr<Node>);
|
||||
Node* hovered_node() { return m_hovered_node.ptr(); }
|
||||
Node const* hovered_node() const { return m_hovered_node.ptr(); }
|
||||
|
||||
|
|
|
@ -1207,16 +1207,16 @@ GC::Ptr<Layout::NodeWithStyle> Element::get_pseudo_element_node(CSS::PseudoEleme
|
|||
return nullptr;
|
||||
}
|
||||
|
||||
bool Element::affected_by_hover() const
|
||||
bool Element::affected_by_pseudo_class(CSS::PseudoClass pseudo_class) const
|
||||
{
|
||||
if (m_computed_properties && m_computed_properties->did_match_any_hover_rules()) {
|
||||
if (m_computed_properties && m_computed_properties->has_attempted_match_against_pseudo_class(pseudo_class)) {
|
||||
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())
|
||||
if (pseudo_element.computed_properties->has_attempted_match_against_pseudo_class(pseudo_class))
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -271,7 +271,7 @@ 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;
|
||||
[[nodiscard]] bool affected_by_pseudo_class(CSS::PseudoClass) const;
|
||||
bool includes_properties_from_invalidation_set(CSS::InvalidationSet const&) const;
|
||||
|
||||
void set_pseudo_element_node(Badge<Layout::TreeBuilder>, CSS::PseudoElement, GC::Ptr<Layout::NodeWithStyle>);
|
||||
|
|
|
@ -60,6 +60,7 @@ enum class PseudoClass {
|
|||
member_generator.appendln(" @name:titlecase@,");
|
||||
});
|
||||
generator.append(R"~~~(
|
||||
__Count,
|
||||
};
|
||||
|
||||
Optional<PseudoClass> pseudo_class_from_string(StringView);
|
||||
|
@ -123,6 +124,8 @@ Optional<PseudoClass> pseudo_class_from_string(StringView string)
|
|||
StringView pseudo_class_name(PseudoClass pseudo_class)
|
||||
{
|
||||
switch (pseudo_class) {
|
||||
case PseudoClass::__Count:
|
||||
VERIFY_NOT_REACHED();
|
||||
)~~~");
|
||||
|
||||
pseudo_classes_data.for_each_member([&](auto& name, auto&) {
|
||||
|
@ -144,6 +147,8 @@ StringView pseudo_class_name(PseudoClass pseudo_class)
|
|||
PseudoClassMetadata pseudo_class_metadata(PseudoClass pseudo_class)
|
||||
{
|
||||
switch (pseudo_class) {
|
||||
case PseudoClass::__Count:
|
||||
VERIFY_NOT_REACHED();
|
||||
)~~~");
|
||||
|
||||
pseudo_classes_data.for_each_member([&](auto& name, JsonValue const& value) {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue