From 66263f142b7aa5bceff74420908bae226b26cea4 Mon Sep 17 00:00:00 2001 From: Andreas Kling Date: Thu, 13 Nov 2025 19:08:08 +0100 Subject: [PATCH] LibWeb: Add StyleScope to keep style caches per Document/ShadowRoot Before this change, we've been maintaining various StyleComputer caches at the document level. This made sense for old-school documents without shadow trees, since all the style information was document-wide anyway. However, documents with many shadow trees ended up suffering since any time you mutated a style sheet inside a shadow tree, *all* style caches for the entire document would get invalidated. This was particularly expensive on Reddit, which has tons of shadow trees with their own style elements. Every time we'd create one of their custom elements, we'd invalidate the document-level "rule cache" and have to rebuild it, taking about ~60ms each time (ouch). This commit introduces a new object called StyleScope. Every Document and ShadowRoot has its own StyleScope. Rule caches etc are moved from StyleComputer to StyleScope. Rule cache invalidation now happens at StyleScope level. As an example, rule cache rebuilds now take ~1ms on Reddit instead of ~60ms. This is largely a mechanical change, moving things around, but there's one key detail to be aware of: due to the :host selector, which works across the shadow DOM boundary and reaches from inside a shadow tree out into the light tree, there are various places where we have to check both the shadow tree's StyleScope *and* the document-level StyleScope in order to get all rules that may apply. --- Libraries/LibWeb/CMakeLists.txt | 1 + Libraries/LibWeb/CSS/CSSLayerBlockRule.cpp | 2 +- Libraries/LibWeb/CSS/CSSLayerBlockRule.h | 2 +- .../LibWeb/CSS/CSSLayerStatementRule.cpp | 2 +- Libraries/LibWeb/CSS/CSSLayerStatementRule.h | 2 +- Libraries/LibWeb/CSS/CSSStyleSheet.cpp | 3 +- Libraries/LibWeb/CSS/StyleComputer.cpp | 409 ++-------------- Libraries/LibWeb/CSS/StyleComputer.h | 88 +--- Libraries/LibWeb/CSS/StyleScope.cpp | 441 ++++++++++++++++++ Libraries/LibWeb/CSS/StyleScope.h | 121 +++++ Libraries/LibWeb/CSS/StyleSheetList.cpp | 22 +- Libraries/LibWeb/DOM/AbstractElement.cpp | 8 + Libraries/LibWeb/DOM/AbstractElement.h | 2 + Libraries/LibWeb/DOM/AdoptedStyleSheets.cpp | 6 +- Libraries/LibWeb/DOM/Document.cpp | 85 ++-- Libraries/LibWeb/DOM/Document.h | 15 +- Libraries/LibWeb/DOM/Node.cpp | 55 ++- Libraries/LibWeb/DOM/ShadowRoot.cpp | 17 + Libraries/LibWeb/DOM/ShadowRoot.h | 7 + Libraries/LibWeb/Forward.h | 1 + Libraries/LibWeb/Page/Page.cpp | 6 +- 21 files changed, 734 insertions(+), 561 deletions(-) create mode 100644 Libraries/LibWeb/CSS/StyleScope.cpp create mode 100644 Libraries/LibWeb/CSS/StyleScope.h diff --git a/Libraries/LibWeb/CMakeLists.txt b/Libraries/LibWeb/CMakeLists.txt index 53e2c6fa84f..54eef248d3f 100644 --- a/Libraries/LibWeb/CMakeLists.txt +++ b/Libraries/LibWeb/CMakeLists.txt @@ -215,6 +215,7 @@ set(SOURCES CSS/StyleProperty.cpp CSS/StylePropertyMapReadOnly.cpp CSS/StylePropertyMap.cpp + CSS/StyleScope.cpp CSS/StyleSheet.cpp CSS/StyleSheetIdentifier.cpp CSS/StyleSheetList.cpp diff --git a/Libraries/LibWeb/CSS/CSSLayerBlockRule.cpp b/Libraries/LibWeb/CSS/CSSLayerBlockRule.cpp index 4d7625c0cbc..c6b01453421 100644 --- a/Libraries/LibWeb/CSS/CSSLayerBlockRule.cpp +++ b/Libraries/LibWeb/CSS/CSSLayerBlockRule.cpp @@ -69,7 +69,7 @@ String CSSLayerBlockRule::serialized() const return builder.to_string_without_validation(); } -FlyString CSSLayerBlockRule::internal_qualified_name(Badge) const +FlyString CSSLayerBlockRule::internal_qualified_name(Badge) const { auto const& parent_name = parent_layer_internal_qualified_name(); if (parent_name.is_empty()) diff --git a/Libraries/LibWeb/CSS/CSSLayerBlockRule.h b/Libraries/LibWeb/CSS/CSSLayerBlockRule.h index afdf3fc2277..9055ea8dbea 100644 --- a/Libraries/LibWeb/CSS/CSSLayerBlockRule.h +++ b/Libraries/LibWeb/CSS/CSSLayerBlockRule.h @@ -24,7 +24,7 @@ public: FlyString const& name() const { return m_name; } FlyString const& internal_name() const { return m_name_internal; } - FlyString internal_qualified_name(Badge) const; + FlyString internal_qualified_name(Badge) const; private: CSSLayerBlockRule(JS::Realm&, FlyString name, CSSRuleList&); diff --git a/Libraries/LibWeb/CSS/CSSLayerStatementRule.cpp b/Libraries/LibWeb/CSS/CSSLayerStatementRule.cpp index ac1a923754d..d44ccc9ca9f 100644 --- a/Libraries/LibWeb/CSS/CSSLayerStatementRule.cpp +++ b/Libraries/LibWeb/CSS/CSSLayerStatementRule.cpp @@ -40,7 +40,7 @@ String CSSLayerStatementRule::serialized() const return builder.to_string_without_validation(); } -Vector CSSLayerStatementRule::internal_qualified_name_list(Badge) const +Vector CSSLayerStatementRule::internal_qualified_name_list(Badge) const { Vector qualified_layer_names; diff --git a/Libraries/LibWeb/CSS/CSSLayerStatementRule.h b/Libraries/LibWeb/CSS/CSSLayerStatementRule.h index 7cd42c929f7..6de94623303 100644 --- a/Libraries/LibWeb/CSS/CSSLayerStatementRule.h +++ b/Libraries/LibWeb/CSS/CSSLayerStatementRule.h @@ -22,7 +22,7 @@ public: // FIXME: Should be FrozenArray ReadonlySpan name_list() const { return m_name_list; } - Vector internal_qualified_name_list(Badge) const; + Vector internal_qualified_name_list(Badge) const; private: CSSLayerStatementRule(JS::Realm&, Vector name_list); diff --git a/Libraries/LibWeb/CSS/CSSStyleSheet.cpp b/Libraries/LibWeb/CSS/CSSStyleSheet.cpp index 34fe2e39e33..897727115a7 100644 --- a/Libraries/LibWeb/CSS/CSSStyleSheet.cpp +++ b/Libraries/LibWeb/CSS/CSSStyleSheet.cpp @@ -353,7 +353,8 @@ void CSSStyleSheet::invalidate_owners(DOM::StyleInvalidationReason reason) m_did_match = {}; for (auto& document_or_shadow_root : m_owning_documents_or_shadow_roots) { document_or_shadow_root->invalidate_style(reason); - document_or_shadow_root->document().style_computer().invalidate_rule_cache(); + auto& style_scope = document_or_shadow_root->is_shadow_root() ? as(*document_or_shadow_root).style_scope() : document_or_shadow_root->document().style_scope(); + style_scope.invalidate_rule_cache(); } } diff --git a/Libraries/LibWeb/CSS/StyleComputer.cpp b/Libraries/LibWeb/CSS/StyleComputer.cpp index 44d47e734af..c00ba845306 100644 --- a/Libraries/LibWeb/CSS/StyleComputer.cpp +++ b/Libraries/LibWeb/CSS/StyleComputer.cpp @@ -204,7 +204,6 @@ StyleComputer::StyleComputer(DOM::Document& document) , m_root_element_font_metrics(m_default_font_metrics) { m_ancestor_filter = make>(); - m_qualified_layer_names_in_order.append({}); } StyleComputer::~StyleComputer() = default; @@ -214,7 +213,6 @@ void StyleComputer::visit_edges(Visitor& visitor) Base::visit_edges(visitor); visitor.visit(m_document); visitor.visit(m_loaded_fonts); - visitor.visit(m_user_style_sheet); } FontLoader::FontLoader(StyleComputer& style_computer, GC::Ptr parent_style_sheet, FlyString family_name, Vector unicode_ranges, Vector urls, Function)> on_load) @@ -356,46 +354,6 @@ struct StyleComputer::MatchingFontCandidate { } }; -static CSSStyleSheet& default_stylesheet() -{ - static GC::Root sheet; - if (!sheet.cell()) { - extern String default_stylesheet_source; - sheet = GC::make_root(parse_css_stylesheet(CSS::Parser::ParsingParams(internal_css_realm()), default_stylesheet_source)); - } - return *sheet; -} - -static CSSStyleSheet& quirks_mode_stylesheet() -{ - static GC::Root sheet; - if (!sheet.cell()) { - extern String quirks_mode_stylesheet_source; - sheet = GC::make_root(parse_css_stylesheet(CSS::Parser::ParsingParams(internal_css_realm()), quirks_mode_stylesheet_source)); - } - return *sheet; -} - -static CSSStyleSheet& mathml_stylesheet() -{ - static GC::Root sheet; - if (!sheet.cell()) { - extern String mathml_stylesheet_source; - sheet = GC::make_root(parse_css_stylesheet(CSS::Parser::ParsingParams(internal_css_realm()), mathml_stylesheet_source)); - } - return *sheet; -} - -static CSSStyleSheet& svg_stylesheet() -{ - static GC::Root sheet; - if (!sheet.cell()) { - extern String svg_stylesheet_source; - sheet = GC::make_root(parse_css_stylesheet(CSS::Parser::ParsingParams(internal_css_realm()), svg_stylesheet_source)); - } - return *sheet; -} - Optional StyleComputer::user_agent_style_sheet_source(StringView name) { extern String default_stylesheet_source; @@ -414,46 +372,23 @@ Optional StyleComputer::user_agent_style_sheet_source(StringView name) return {}; } -template -void StyleComputer::for_each_stylesheet(CascadeOrigin cascade_origin, Callback callback) const -{ - if (cascade_origin == CascadeOrigin::UserAgent) { - callback(default_stylesheet(), {}); - if (document().in_quirks_mode()) - callback(quirks_mode_stylesheet(), {}); - callback(mathml_stylesheet(), {}); - callback(svg_stylesheet(), {}); - } - if (cascade_origin == CascadeOrigin::User) { - if (m_user_style_sheet) - callback(*m_user_style_sheet, {}); - } - if (cascade_origin == CascadeOrigin::Author) { - document().for_each_active_css_style_sheet([&](auto& sheet, auto shadow_root) { - callback(sheet, shadow_root); - }); - } -} - RuleCache const* StyleComputer::rule_cache_for_cascade_origin(CascadeOrigin cascade_origin, Optional qualified_layer_name, GC::Ptr shadow_root) const { - auto const* rule_caches_for_document_and_shadow_roots = [&]() -> RuleCachesForDocumentAndShadowRoots const* { + auto& style_scope = shadow_root ? shadow_root->style_scope() : document().style_scope(); + style_scope.build_rule_cache_if_needed(); + + auto const* rule_caches_by_layer = [&]() -> RuleCaches const* { switch (cascade_origin) { case CascadeOrigin::Author: - return m_author_rule_cache; + return style_scope.m_author_rule_cache; case CascadeOrigin::User: - return m_user_rule_cache; + return style_scope.m_user_rule_cache; case CascadeOrigin::UserAgent: - return m_user_agent_rule_cache; + return style_scope.m_user_agent_rule_cache; default: VERIFY_NOT_REACHED(); } }(); - auto const* rule_caches_by_layer = [&]() -> RuleCaches const* { - if (shadow_root) - return rule_caches_for_document_and_shadow_roots->for_shadow_roots.get(*shadow_root).value_or(nullptr); - return &rule_caches_for_document_and_shadow_roots->for_document; - }(); if (!rule_caches_by_layer) return nullptr; if (!qualified_layer_name.has_value()) @@ -469,17 +404,11 @@ RuleCache const* StyleComputer::rule_cache_for_cascade_origin(CascadeOrigin casc return true; } -RuleCache const& StyleComputer::get_pseudo_class_rule_cache(PseudoClass pseudo_class) const +InvalidationSet StyleComputer::invalidation_set_for_properties(Vector const& properties, StyleScope const& style_scope) const { - build_rule_cache_if_needed(); - return *m_pseudo_class_rule_cache[to_underlying(pseudo_class)]; -} - -InvalidationSet StyleComputer::invalidation_set_for_properties(Vector const& properties) const -{ - if (!m_style_invalidation_data) + if (!style_scope.m_style_invalidation_data) return {}; - auto const& descendant_invalidation_sets = m_style_invalidation_data->descendant_invalidation_sets; + auto const& descendant_invalidation_sets = style_scope.m_style_invalidation_data->descendant_invalidation_sets; InvalidationSet result; for (auto const& property : properties) { if (auto it = descendant_invalidation_sets.find(property); it != descendant_invalidation_sets.end()) @@ -488,29 +417,29 @@ InvalidationSet StyleComputer::invalidation_set_for_properties(Vectorids_used_in_has_selectors.contains(property.name())) + if (style_scope.m_style_invalidation_data->ids_used_in_has_selectors.contains(property.name())) return true; break; case InvalidationSet::Property::Type::Class: - if (m_style_invalidation_data->class_names_used_in_has_selectors.contains(property.name())) + if (style_scope.m_style_invalidation_data->class_names_used_in_has_selectors.contains(property.name())) return true; break; case InvalidationSet::Property::Type::Attribute: - if (m_style_invalidation_data->attribute_names_used_in_has_selectors.contains(property.name())) + if (style_scope.m_style_invalidation_data->attribute_names_used_in_has_selectors.contains(property.name())) return true; break; case InvalidationSet::Property::Type::TagName: - if (m_style_invalidation_data->tag_names_used_in_has_selectors.contains(property.name())) + if (style_scope.m_style_invalidation_data->tag_names_used_in_has_selectors.contains(property.name())) return true; break; case InvalidationSet::Property::Type::PseudoClass: - if (m_style_invalidation_data->pseudo_classes_used_in_has_selectors.contains(property.value.get())) + if (style_scope.m_style_invalidation_data->pseudo_classes_used_in_has_selectors.contains(property.value.get())) return true; break; default: @@ -1565,7 +1494,7 @@ void StyleComputer::start_needed_transitions(ComputedProperties const& previous_ } } -StyleComputer::MatchingRuleSet StyleComputer::build_matching_rule_set(DOM::AbstractElement abstract_element, PseudoClassBitmap& attempted_pseudo_class_matches, bool& did_match_any_pseudo_element_rules, ComputeStyleMode mode) const +StyleComputer::MatchingRuleSet StyleComputer::build_matching_rule_set(DOM::AbstractElement abstract_element, PseudoClassBitmap& attempted_pseudo_class_matches, bool& did_match_any_pseudo_element_rules, ComputeStyleMode mode, StyleScope const& style_scope) const { // First, we collect all the CSS rules whose selectors match `element`: MatchingRuleSet matching_rule_set; @@ -1575,7 +1504,7 @@ StyleComputer::MatchingRuleSet StyleComputer::build_matching_rule_set(DOM::Abstr sort_matching_rules(matching_rule_set.user_rules); // @layer-ed author rules - for (auto const& layer_name : m_qualified_layer_names_in_order) { + for (auto const& layer_name : style_scope.m_qualified_layer_names_in_order) { auto layer_rules = collect_matching_rules(abstract_element, CascadeOrigin::Author, attempted_pseudo_class_matches, layer_name); sort_matching_rules(layer_rules); matching_rule_set.author_rules.append({ layer_name, layer_rules }); @@ -2377,17 +2306,19 @@ GC::Ref StyleComputer::create_document_style() const GC::Ref StyleComputer::compute_style(DOM::AbstractElement abstract_element, Optional did_change_custom_properties) const { - return *compute_style_impl(abstract_element, ComputeStyleMode::Normal, did_change_custom_properties); + auto& style_scope = abstract_element.style_scope(); + return *compute_style_impl(abstract_element, ComputeStyleMode::Normal, did_change_custom_properties, style_scope); } GC::Ptr StyleComputer::compute_pseudo_element_style_if_needed(DOM::AbstractElement abstract_element, Optional did_change_custom_properties) const { - return compute_style_impl(abstract_element, ComputeStyleMode::CreatePseudoElementStyleIfNeeded, did_change_custom_properties); + auto& style_scope = abstract_element.style_scope(); + return compute_style_impl(abstract_element, ComputeStyleMode::CreatePseudoElementStyleIfNeeded, did_change_custom_properties, style_scope); } -GC::Ptr StyleComputer::compute_style_impl(DOM::AbstractElement abstract_element, ComputeStyleMode mode, Optional did_change_custom_properties) const +GC::Ptr StyleComputer::compute_style_impl(DOM::AbstractElement abstract_element, ComputeStyleMode mode, Optional did_change_custom_properties, StyleScope const& style_scope) const { - build_rule_cache_if_needed(); + style_scope.build_rule_cache_if_needed(); // Special path for elements that represent a pseudo-element in some element's internal shadow tree. if (abstract_element.element().use_pseudo_element().has_value()) { @@ -2418,7 +2349,7 @@ GC::Ptr StyleComputer::compute_style_impl(DOM::AbstractEleme // 1. Perform the cascade. This produces the "specified style" bool did_match_any_pseudo_element_rules = false; PseudoClassBitmap attempted_pseudo_class_matches; - auto matching_rule_set = build_matching_rule_set(abstract_element, attempted_pseudo_class_matches, did_match_any_pseudo_element_rules, mode); + auto matching_rule_set = build_matching_rule_set(abstract_element, attempted_pseudo_class_matches, did_match_any_pseudo_element_rules, mode, style_scope); auto old_custom_properties = abstract_element.custom_properties(); @@ -2665,13 +2596,6 @@ GC::Ref StyleComputer::compute_properties(DOM::AbstractEleme return computed_style; } -void StyleComputer::build_rule_cache_if_needed() const -{ - if (has_valid_rule_cache()) - return; - const_cast(*this).build_rule_cache(); -} - struct SimplifiedSelectorForBucketing { CSS::Selector::SimpleSelector::Type type; FlyString name; @@ -2708,272 +2632,6 @@ static Optional is_roundabout_selector_bucketabl return {}; } -void StyleComputer::collect_selector_insights(Selector const& selector, SelectorInsights& insights) -{ - for (auto const& compound_selector : selector.compound_selectors()) { - for (auto const& simple_selector : compound_selector.simple_selectors) { - if (simple_selector.type == Selector::SimpleSelector::Type::PseudoClass) { - if (simple_selector.pseudo_class().type == PseudoClass::Has) { - insights.has_has_selectors = true; - } - for (auto const& argument_selector : simple_selector.pseudo_class().argument_selector_list) { - collect_selector_insights(*argument_selector, insights); - } - } - } - } -} - -void StyleComputer::make_rule_cache_for_cascade_origin(CascadeOrigin cascade_origin, SelectorInsights& insights) -{ - Vector matching_rules; - size_t style_sheet_index = 0; - for_each_stylesheet(cascade_origin, [&](auto& sheet, GC::Ptr shadow_root) { - auto& rule_caches = [&] -> RuleCaches& { - RuleCachesForDocumentAndShadowRoots* rule_caches_for_document_or_shadow_root = nullptr; - switch (cascade_origin) { - case CascadeOrigin::Author: - rule_caches_for_document_or_shadow_root = m_author_rule_cache; - break; - case CascadeOrigin::User: - rule_caches_for_document_or_shadow_root = m_user_rule_cache; - break; - case CascadeOrigin::UserAgent: - rule_caches_for_document_or_shadow_root = m_user_agent_rule_cache; - break; - default: - VERIFY_NOT_REACHED(); - } - if (!shadow_root) - return rule_caches_for_document_or_shadow_root->for_document; - return *rule_caches_for_document_or_shadow_root->for_shadow_roots.ensure(*shadow_root, [] { return make(); }); - }(); - - size_t rule_index = 0; - sheet.for_each_effective_style_producing_rule([&](auto const& rule) { - SelectorList const& absolutized_selectors = [&]() { - if (rule.type() == CSSRule::Type::Style) - return static_cast(rule).absolutized_selectors(); - if (rule.type() == CSSRule::Type::NestedDeclarations) - return static_cast(rule).parent_style_rule().absolutized_selectors(); - VERIFY_NOT_REACHED(); - }(); - - for (auto const& selector : absolutized_selectors) { - m_style_invalidation_data->build_invalidation_sets_for_selector(selector); - } - - for (CSS::Selector const& selector : absolutized_selectors) { - MatchingRule matching_rule { - shadow_root, - &rule, - sheet, - sheet.default_namespace(), - selector, - style_sheet_index, - rule_index, - selector.specificity(), - cascade_origin, - false, - }; - - auto const& qualified_layer_name = matching_rule.qualified_layer_name(); - auto& rule_cache = qualified_layer_name.is_empty() ? rule_caches.main : *rule_caches.by_layer.ensure(qualified_layer_name, [] { return make(); }); - - bool contains_root_pseudo_class = false; - Optional pseudo_element; - - collect_selector_insights(selector, insights); - - for (auto const& simple_selector : selector.compound_selectors().last().simple_selectors) { - if (!matching_rule.contains_pseudo_element) { - if (simple_selector.type == CSS::Selector::SimpleSelector::Type::PseudoElement) { - matching_rule.contains_pseudo_element = true; - pseudo_element = simple_selector.pseudo_element().type(); - matching_rule.slotted = pseudo_element == PseudoElement::Slotted; - } - } - if (!contains_root_pseudo_class) { - if (simple_selector.type == CSS::Selector::SimpleSelector::Type::PseudoClass - && simple_selector.pseudo_class().type == CSS::PseudoClass::Root) { - contains_root_pseudo_class = true; - } - } - } - - for (size_t i = 0; i < to_underlying(PseudoClass::__Count); ++i) { - auto pseudo_class = static_cast(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; - }); - - // Loosely based on https://drafts.csswg.org/css-animations-2/#keyframe-processing - sheet.for_each_effective_keyframes_at_rule([&](CSSKeyframesRule const& rule) { - auto keyframe_set = adopt_ref(*new Animations::KeyframeEffect::KeyFrameSet); - HashTable animated_properties; - - // Forwards pass, resolve all the user-specified keyframe properties. - for (auto const& keyframe_rule : *rule.css_rules()) { - auto const& keyframe = as(*keyframe_rule); - Animations::KeyframeEffect::KeyFrameSet::ResolvedKeyFrame resolved_keyframe; - - auto key = static_cast(keyframe.key().value() * Animations::KeyframeEffect::AnimationKeyFrameKeyScaleFactor); - auto const& keyframe_style = *keyframe.style(); - for (auto const& it : keyframe_style.properties()) { - if (!is_animatable_property(it.property_id)) - continue; - - // Unresolved properties will be resolved in collect_animation_into() - for_each_property_expanding_shorthands(it.property_id, it.value, [&](PropertyID shorthand_id, StyleValue const& shorthand_value) { - animated_properties.set(shorthand_id); - resolved_keyframe.properties.set(shorthand_id, NonnullRefPtr { shorthand_value }); - }); - } - - keyframe_set->keyframes_by_key.insert(key, resolved_keyframe); - } - - Animations::KeyframeEffect::generate_initial_and_final_frames(keyframe_set, animated_properties); - - if constexpr (LIBWEB_CSS_DEBUG) { - dbgln("Resolved keyframe set '{}' into {} keyframes:", rule.name(), keyframe_set->keyframes_by_key.size()); - for (auto it = keyframe_set->keyframes_by_key.begin(); it != keyframe_set->keyframes_by_key.end(); ++it) - dbgln(" - keyframe {}: {} properties", it.key(), it->properties.size()); - } - - rule_caches.main.rules_by_animation_keyframes.set(rule.name(), move(keyframe_set)); - }); - ++style_sheet_index; - }); -} - -struct LayerNode { - OrderedHashMap children {}; -}; - -static void flatten_layer_names_tree(Vector& layer_names, StringView const& parent_qualified_name, FlyString const& name, LayerNode const& node) -{ - FlyString qualified_name = parent_qualified_name.is_empty() ? name : MUST(String::formatted("{}.{}", parent_qualified_name, name)); - - for (auto const& item : node.children) - flatten_layer_names_tree(layer_names, qualified_name, item.key, item.value); - - layer_names.append(qualified_name); -} - -void StyleComputer::build_qualified_layer_names_cache() -{ - LayerNode root; - - auto insert_layer_name = [&](FlyString const& internal_qualified_name) { - auto* node = &root; - internal_qualified_name.bytes_as_string_view() - .for_each_split_view('.', SplitBehavior::Nothing, [&](StringView part) { - auto local_name = MUST(FlyString::from_utf8(part)); - node = &node->children.ensure(local_name); - }); - }; - - // Walk all style sheets, identifying when we first see a @layer name, and add its qualified name to the list. - // TODO: Separate the light and shadow-dom layers. - for_each_stylesheet(CascadeOrigin::Author, [&](auto& sheet, GC::Ptr) { - // NOTE: Postorder so that a @layer block is iterated after its children, - // because we want those children to occur before it in the list. - sheet.for_each_effective_rule(TraversalOrder::Postorder, [&](auto& rule) { - switch (rule.type()) { - case CSSRule::Type::Import: - // TODO: Handle `layer(foo)` in import rules once we implement that. - break; - case CSSRule::Type::LayerBlock: { - auto& layer_block = static_cast(rule); - insert_layer_name(layer_block.internal_qualified_name({})); - break; - } - case CSSRule::Type::LayerStatement: { - auto& layer_statement = static_cast(rule); - auto qualified_names = layer_statement.internal_qualified_name_list({}); - for (auto& name : qualified_names) - insert_layer_name(name); - break; - } - - // Ignore everything else - case CSSRule::Type::Style: - case CSSRule::Type::Media: - case CSSRule::Type::FontFace: - case CSSRule::Type::Keyframes: - case CSSRule::Type::Keyframe: - case CSSRule::Type::Margin: - case CSSRule::Type::Namespace: - case CSSRule::Type::NestedDeclarations: - case CSSRule::Type::Page: - case CSSRule::Type::Property: - case CSSRule::Type::Supports: - break; - } - }); - }); - - // Now, produce a flat list of qualified names to use later - m_qualified_layer_names_in_order.clear(); - flatten_layer_names_tree(m_qualified_layer_names_in_order, ""sv, {}, root); -} - -void StyleComputer::build_rule_cache() -{ - m_author_rule_cache = make(); - m_user_rule_cache = make(); - m_user_agent_rule_cache = make(); - - m_selector_insights = make(); - m_style_invalidation_data = make(); - - if (auto user_style_source = document().page().user_style(); user_style_source.has_value()) { - m_user_style_sheet = GC::make_root(parse_css_stylesheet(CSS::Parser::ParsingParams(document()), user_style_source.value())); - } - - build_qualified_layer_names_cache(); - - m_pseudo_class_rule_cache[to_underlying(PseudoClass::Hover)] = make(); - m_pseudo_class_rule_cache[to_underlying(PseudoClass::Active)] = make(); - m_pseudo_class_rule_cache[to_underlying(PseudoClass::Focus)] = make(); - m_pseudo_class_rule_cache[to_underlying(PseudoClass::FocusWithin)] = make(); - m_pseudo_class_rule_cache[to_underlying(PseudoClass::FocusVisible)] = make(); - m_pseudo_class_rule_cache[to_underlying(PseudoClass::Target)] = make(); - - 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); -} - -void StyleComputer::invalidate_rule_cache() -{ - m_author_rule_cache = nullptr; - - // NOTE: We could be smarter about keeping the user rule cache, and style sheet. - // Currently we are re-parsing the user style sheet every time we build the caches, - // as it may have changed. - m_user_rule_cache = nullptr; - m_user_style_sheet = nullptr; - - // NOTE: It might not be necessary to throw away the UA rule cache. - // If we are sure that it's safe, we could keep it as an optimization. - m_user_agent_rule_cache = nullptr; - - m_pseudo_class_rule_cache = {}; - m_style_invalidation_data = nullptr; -} - void StyleComputer::did_load_font(FlyString const&) { m_font_matching_algorithm_cache = {}; @@ -3782,21 +3440,6 @@ size_t StyleComputer::number_of_css_font_faces_with_loading_in_progress() const return count; } -bool StyleComputer::may_have_has_selectors() const -{ - if (!has_valid_rule_cache()) - return true; - - build_rule_cache_if_needed(); - return m_selector_insights->has_has_selectors; -} - -bool StyleComputer::have_has_selectors() const -{ - build_rule_cache_if_needed(); - return m_selector_insights->has_has_selectors; -} - void RuleCache::add_rule(MatchingRule const& matching_rule, Optional pseudo_element, bool contains_root_pseudo_class) { if (matching_rule.slotted) { diff --git a/Libraries/LibWeb/CSS/StyleComputer.h b/Libraries/LibWeb/CSS/StyleComputer.h index e52aa01c9f4..690018a3c14 100644 --- a/Libraries/LibWeb/CSS/StyleComputer.h +++ b/Libraries/LibWeb/CSS/StyleComputer.h @@ -20,6 +20,7 @@ #include #include #include +#include #include #include #include @@ -75,26 +76,6 @@ private: CounterType m_buckets[bucket_count]; }; -struct MatchingRule { - GC::Ptr shadow_root; - GC::Ptr rule; // Either CSSStyleRule or CSSNestedDeclarations - GC::Ptr sheet; - Optional default_namespace; - Selector const& selector; - size_t style_sheet_index { 0 }; - size_t rule_index { 0 }; - - u32 specificity { 0 }; - CascadeOrigin cascade_origin; - bool contains_pseudo_element { false }; - bool slotted { false }; - - // Helpers to deal with the fact that `rule` might be a CSSStyleRule or a CSSNestedDeclarations - CSSStyleProperties const& declaration() const; - SelectorList const& absolutized_selectors() const; - FlyString const& qualified_layer_name() const; -}; - struct FontFaceKey; struct OwnFontFaceKey { @@ -120,22 +101,6 @@ struct FontMatchingAlgorithmCacheKey { [[nodiscard]] bool operator==(FontMatchingAlgorithmCacheKey const& other) const = default; }; -struct RuleCache { - HashMap> rules_by_id; - HashMap> rules_by_class; - HashMap> rules_by_tag_name; - HashMap, AK::ASCIICaseInsensitiveFlyStringTraits> rules_by_attribute_name; - Array, to_underlying(CSS::PseudoElement::KnownPseudoElementCount)> rules_by_pseudo_element; - Vector root_rules; - Vector slotted_rules; - Vector other_rules; - - HashMap> rules_by_animation_keyframes; - - void add_rule(MatchingRule const&, Optional, bool contains_root_pseudo_class); - void for_each_matching_rules(DOM::AbstractElement, Function const&)> callback) const; -}; - class FontLoader; class WEB_API StyleComputer final : public GC::Cell { @@ -164,15 +129,10 @@ public: [[nodiscard]] GC::Ref compute_style(DOM::AbstractElement, Optional did_change_custom_properties = {}) const; [[nodiscard]] GC::Ptr compute_pseudo_element_style_if_needed(DOM::AbstractElement, Optional did_change_custom_properties) const; - [[nodiscard]] RuleCache const& get_pseudo_class_rule_cache(PseudoClass) const; - [[nodiscard]] Vector collect_matching_rules(DOM::AbstractElement, CascadeOrigin, PseudoClassBitmap& attempted_pseudo_class_matches, Optional qualified_layer_name = {}) const; - InvalidationSet invalidation_set_for_properties(Vector const&) const; - bool invalidation_property_used_in_has_selector(InvalidationSet::Property const&) const; - - [[nodiscard]] bool has_valid_rule_cache() const { return m_author_rule_cache; } - void invalidate_rule_cache(); + InvalidationSet invalidation_set_for_properties(Vector const&, StyleScope const&) const; + bool invalidation_property_used_in_has_selector(InvalidationSet::Property const&, StyleScope const&) const; Gfx::Font const& initial_font() const; @@ -193,9 +153,6 @@ public: void collect_animation_into(DOM::AbstractElement, GC::Ref animation, ComputedProperties&) const; - [[nodiscard]] bool may_have_has_selectors() const; - [[nodiscard]] bool have_has_selectors() const; - size_t number_of_css_font_faces_with_loading_in_progress() const; [[nodiscard]] GC::Ref compute_properties(DOM::AbstractElement, CascadedProperties&) const; @@ -242,10 +199,10 @@ private: Vector author_rules; }; - [[nodiscard]] MatchingRuleSet build_matching_rule_set(DOM::AbstractElement, PseudoClassBitmap& attempted_pseudo_class_matches, bool& did_match_any_pseudo_element_rules, ComputeStyleMode) const; + [[nodiscard]] MatchingRuleSet build_matching_rule_set(DOM::AbstractElement, PseudoClassBitmap& attempted_pseudo_class_matches, bool& did_match_any_pseudo_element_rules, ComputeStyleMode, StyleScope const&) const; LogicalAliasMappingContext compute_logical_alias_mapping_context(DOM::AbstractElement, ComputeStyleMode, MatchingRuleSet const&) const; - [[nodiscard]] GC::Ptr compute_style_impl(DOM::AbstractElement, ComputeStyleMode, Optional did_change_custom_properties) const; + [[nodiscard]] GC::Ptr compute_style_impl(DOM::AbstractElement, ComputeStyleMode, Optional did_change_custom_properties, StyleScope const&) const; [[nodiscard]] GC::Ref compute_cascaded_values(DOM::AbstractElement, bool did_match_any_pseudo_element_rules, ComputeStyleMode, MatchingRuleSet const&, Optional, ReadonlySpan properties_to_cascade) const; static RefPtr find_matching_font_weight_ascending(Vector const& candidates, int target_weight, float font_size_in_pt, Gfx::FontVariationSettings const& variations, bool inclusive); static RefPtr find_matching_font_weight_descending(Vector const& candidates, int target_weight, float font_size_in_pt, Gfx::FontVariationSettings const& variations, bool inclusive); @@ -257,16 +214,10 @@ private: void resolve_effective_overflow_values(ComputedProperties&) const; void transform_box_type_if_needed(ComputedProperties&, DOM::AbstractElement) const; - template - void for_each_stylesheet(CascadeOrigin, Callback) const; - [[nodiscard]] CSSPixelRect viewport_rect() const { return m_viewport_rect; } [[nodiscard]] Length::FontMetrics calculate_root_element_font_metrics(ComputedProperties const&) const; - Vector m_qualified_layer_names_in_order; - void build_qualified_layer_names_cache(); - void cascade_declarations( CascadedProperties&, DOM::AbstractElement, @@ -277,39 +228,10 @@ private: Optional, ReadonlySpan properties_to_cascade) const; - void build_rule_cache(); - void build_rule_cache_if_needed() const; - GC::Ref m_document; - struct SelectorInsights { - bool has_has_selectors { false }; - }; - - struct RuleCaches { - RuleCache main; - HashMap> by_layer; - }; - - struct RuleCachesForDocumentAndShadowRoots { - RuleCaches for_document; - HashMap, NonnullOwnPtr> for_shadow_roots; - }; - - void make_rule_cache_for_cascade_origin(CascadeOrigin, SelectorInsights&); - [[nodiscard]] RuleCache const* rule_cache_for_cascade_origin(CascadeOrigin, Optional qualified_layer_name, GC::Ptr) const; - static void collect_selector_insights(Selector const&, SelectorInsights&); - - OwnPtr m_selector_insights; - Array, to_underlying(PseudoClass::__Count)> m_pseudo_class_rule_cache; - OwnPtr m_style_invalidation_data; - OwnPtr m_author_rule_cache; - OwnPtr m_user_rule_cache; - OwnPtr m_user_agent_rule_cache; - GC::Ptr m_user_style_sheet; - using FontLoaderList = Vector>; HashMap m_loaded_fonts; diff --git a/Libraries/LibWeb/CSS/StyleScope.cpp b/Libraries/LibWeb/CSS/StyleScope.cpp new file mode 100644 index 00000000000..a1a7e163078 --- /dev/null +++ b/Libraries/LibWeb/CSS/StyleScope.cpp @@ -0,0 +1,441 @@ +/* + * Copyright (c) 2018-2025, Andreas Kling + * Copyright (c) 2022-2025, Aliaksandr Kalenik + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace Web::CSS { + +void StyleScope::visit_edges(GC::Cell::Visitor& visitor) +{ + visitor.visit(m_user_style_sheet); +} + +StyleScope::StyleScope(GC::Ref node) + : m_node(node) +{ + m_qualified_layer_names_in_order.append({}); +} + +void StyleScope::build_rule_cache() +{ + m_author_rule_cache = make(); + m_user_rule_cache = make(); + m_user_agent_rule_cache = make(); + + m_selector_insights = make(); + m_style_invalidation_data = make(); + + if (auto user_style_source = document().page().user_style(); user_style_source.has_value()) { + m_user_style_sheet = GC::make_root(parse_css_stylesheet(CSS::Parser::ParsingParams(document()), user_style_source.value())); + } + + build_qualified_layer_names_cache(); + + m_pseudo_class_rule_cache[to_underlying(PseudoClass::Hover)] = make(); + m_pseudo_class_rule_cache[to_underlying(PseudoClass::Active)] = make(); + m_pseudo_class_rule_cache[to_underlying(PseudoClass::Focus)] = make(); + m_pseudo_class_rule_cache[to_underlying(PseudoClass::FocusWithin)] = make(); + m_pseudo_class_rule_cache[to_underlying(PseudoClass::FocusVisible)] = make(); + m_pseudo_class_rule_cache[to_underlying(PseudoClass::Target)] = make(); + + 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); +} + +void StyleScope::invalidate_rule_cache() +{ + m_author_rule_cache = nullptr; + + // NOTE: We could be smarter about keeping the user rule cache, and style sheet. + // Currently we are re-parsing the user style sheet every time we build the caches, + // as it may have changed. + m_user_rule_cache = nullptr; + m_user_style_sheet = nullptr; + + // NOTE: It might not be necessary to throw away the UA rule cache. + // If we are sure that it's safe, we could keep it as an optimization. + m_user_agent_rule_cache = nullptr; + + m_pseudo_class_rule_cache = {}; + m_style_invalidation_data = nullptr; +} + +void StyleScope::build_rule_cache_if_needed() const +{ + if (has_valid_rule_cache()) + return; + const_cast(*this).build_rule_cache(); +} + +static CSSStyleSheet& default_stylesheet() +{ + static GC::Root sheet; + if (!sheet.cell()) { + extern String default_stylesheet_source; + sheet = GC::make_root(parse_css_stylesheet(CSS::Parser::ParsingParams(internal_css_realm()), default_stylesheet_source)); + } + return *sheet; +} + +static CSSStyleSheet& quirks_mode_stylesheet() +{ + static GC::Root sheet; + if (!sheet.cell()) { + extern String quirks_mode_stylesheet_source; + sheet = GC::make_root(parse_css_stylesheet(CSS::Parser::ParsingParams(internal_css_realm()), quirks_mode_stylesheet_source)); + } + return *sheet; +} + +static CSSStyleSheet& mathml_stylesheet() +{ + static GC::Root sheet; + if (!sheet.cell()) { + extern String mathml_stylesheet_source; + sheet = GC::make_root(parse_css_stylesheet(CSS::Parser::ParsingParams(internal_css_realm()), mathml_stylesheet_source)); + } + return *sheet; +} + +static CSSStyleSheet& svg_stylesheet() +{ + static GC::Root sheet; + if (!sheet.cell()) { + extern String svg_stylesheet_source; + sheet = GC::make_root(parse_css_stylesheet(CSS::Parser::ParsingParams(internal_css_realm()), svg_stylesheet_source)); + } + return *sheet; +} + +template +void StyleScope::for_each_stylesheet(CascadeOrigin cascade_origin, Callback callback) const +{ + if (cascade_origin == CascadeOrigin::UserAgent) { + callback(default_stylesheet()); + if (document().in_quirks_mode()) + callback(quirks_mode_stylesheet()); + callback(mathml_stylesheet()); + callback(svg_stylesheet()); + } + if (cascade_origin == CascadeOrigin::User) { + if (m_user_style_sheet) + callback(*m_user_style_sheet); + } + if (cascade_origin == CascadeOrigin::Author) { + for_each_active_css_style_sheet(move(callback)); + } +} + +void StyleScope::make_rule_cache_for_cascade_origin(CascadeOrigin cascade_origin, SelectorInsights& insights) +{ + GC::Ptr scope_shadow_root; + if (m_node->is_shadow_root()) + scope_shadow_root = as(*m_node); + + Vector matching_rules; + size_t style_sheet_index = 0; + for_each_stylesheet(cascade_origin, [&](auto& sheet) { + auto& rule_caches = [&] -> RuleCaches& { + switch (cascade_origin) { + case CascadeOrigin::Author: + return *m_author_rule_cache; + case CascadeOrigin::User: + return *m_user_rule_cache; + case CascadeOrigin::UserAgent: + return *m_user_agent_rule_cache; + default: + VERIFY_NOT_REACHED(); + } + }(); + + size_t rule_index = 0; + sheet.for_each_effective_style_producing_rule([&](auto const& rule) { + SelectorList const& absolutized_selectors = [&]() { + if (rule.type() == CSSRule::Type::Style) + return static_cast(rule).absolutized_selectors(); + if (rule.type() == CSSRule::Type::NestedDeclarations) + return static_cast(rule).parent_style_rule().absolutized_selectors(); + VERIFY_NOT_REACHED(); + }(); + + for (auto const& selector : absolutized_selectors) { + m_style_invalidation_data->build_invalidation_sets_for_selector(selector); + } + + for (CSS::Selector const& selector : absolutized_selectors) { + MatchingRule matching_rule { + scope_shadow_root, + &rule, + sheet, + sheet.default_namespace(), + selector, + style_sheet_index, + rule_index, + selector.specificity(), + cascade_origin, + false, + }; + + auto const& qualified_layer_name = matching_rule.qualified_layer_name(); + auto& rule_cache = qualified_layer_name.is_empty() ? rule_caches.main : *rule_caches.by_layer.ensure(qualified_layer_name, [] { return make(); }); + + bool contains_root_pseudo_class = false; + Optional pseudo_element; + + collect_selector_insights(selector, insights); + + for (auto const& simple_selector : selector.compound_selectors().last().simple_selectors) { + if (!matching_rule.contains_pseudo_element) { + if (simple_selector.type == CSS::Selector::SimpleSelector::Type::PseudoElement) { + matching_rule.contains_pseudo_element = true; + pseudo_element = simple_selector.pseudo_element().type(); + matching_rule.slotted = pseudo_element == PseudoElement::Slotted; + } + } + if (!contains_root_pseudo_class) { + if (simple_selector.type == CSS::Selector::SimpleSelector::Type::PseudoClass + && simple_selector.pseudo_class().type == CSS::PseudoClass::Root) { + contains_root_pseudo_class = true; + } + } + } + + for (size_t i = 0; i < to_underlying(PseudoClass::__Count); ++i) { + auto pseudo_class = static_cast(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; + }); + + // Loosely based on https://drafts.csswg.org/css-animations-2/#keyframe-processing + sheet.for_each_effective_keyframes_at_rule([&](CSSKeyframesRule const& rule) { + auto keyframe_set = adopt_ref(*new Animations::KeyframeEffect::KeyFrameSet); + HashTable animated_properties; + + // Forwards pass, resolve all the user-specified keyframe properties. + for (auto const& keyframe_rule : *rule.css_rules()) { + auto const& keyframe = as(*keyframe_rule); + Animations::KeyframeEffect::KeyFrameSet::ResolvedKeyFrame resolved_keyframe; + + auto key = static_cast(keyframe.key().value() * Animations::KeyframeEffect::AnimationKeyFrameKeyScaleFactor); + auto const& keyframe_style = *keyframe.style(); + for (auto const& it : keyframe_style.properties()) { + if (!is_animatable_property(it.property_id)) + continue; + + // Unresolved properties will be resolved in collect_animation_into() + StyleComputer::for_each_property_expanding_shorthands(it.property_id, it.value, [&](PropertyID shorthand_id, StyleValue const& shorthand_value) { + animated_properties.set(shorthand_id); + resolved_keyframe.properties.set(shorthand_id, NonnullRefPtr { shorthand_value }); + }); + } + + keyframe_set->keyframes_by_key.insert(key, resolved_keyframe); + } + + Animations::KeyframeEffect::generate_initial_and_final_frames(keyframe_set, animated_properties); + + if constexpr (LIBWEB_CSS_DEBUG) { + dbgln("Resolved keyframe set '{}' into {} keyframes:", rule.name(), keyframe_set->keyframes_by_key.size()); + for (auto it = keyframe_set->keyframes_by_key.begin(); it != keyframe_set->keyframes_by_key.end(); ++it) + dbgln(" - keyframe {}: {} properties", it.key(), it->properties.size()); + } + + rule_caches.main.rules_by_animation_keyframes.set(rule.name(), move(keyframe_set)); + }); + ++style_sheet_index; + }); +} + +void StyleScope::collect_selector_insights(Selector const& selector, SelectorInsights& insights) +{ + for (auto const& compound_selector : selector.compound_selectors()) { + for (auto const& simple_selector : compound_selector.simple_selectors) { + if (simple_selector.type == Selector::SimpleSelector::Type::PseudoClass) { + if (simple_selector.pseudo_class().type == PseudoClass::Has) { + insights.has_has_selectors = true; + } + for (auto const& argument_selector : simple_selector.pseudo_class().argument_selector_list) { + collect_selector_insights(*argument_selector, insights); + } + } + } + } +} + +struct LayerNode { + OrderedHashMap children {}; +}; + +static void flatten_layer_names_tree(Vector& layer_names, StringView const& parent_qualified_name, FlyString const& name, LayerNode const& node) +{ + FlyString qualified_name = parent_qualified_name.is_empty() ? name : MUST(String::formatted("{}.{}", parent_qualified_name, name)); + + for (auto const& item : node.children) + flatten_layer_names_tree(layer_names, qualified_name, item.key, item.value); + + layer_names.append(qualified_name); +} + +void StyleScope::build_qualified_layer_names_cache() +{ + LayerNode root; + + auto insert_layer_name = [&](FlyString const& internal_qualified_name) { + auto* node = &root; + internal_qualified_name.bytes_as_string_view() + .for_each_split_view('.', SplitBehavior::Nothing, [&](StringView part) { + auto local_name = MUST(FlyString::from_utf8(part)); + node = &node->children.ensure(local_name); + }); + }; + + // Walk all style sheets, identifying when we first see a @layer name, and add its qualified name to the list. + // TODO: Separate the light and shadow-dom layers. + for_each_stylesheet(CascadeOrigin::Author, [&](auto& sheet) { + // NOTE: Postorder so that a @layer block is iterated after its children, + // because we want those children to occur before it in the list. + sheet.for_each_effective_rule(TraversalOrder::Postorder, [&](auto& rule) { + switch (rule.type()) { + case CSSRule::Type::Import: + // TODO: Handle `layer(foo)` in import rules once we implement that. + break; + case CSSRule::Type::LayerBlock: { + auto& layer_block = static_cast(rule); + insert_layer_name(layer_block.internal_qualified_name({})); + break; + } + case CSSRule::Type::LayerStatement: { + auto& layer_statement = static_cast(rule); + auto qualified_names = layer_statement.internal_qualified_name_list({}); + for (auto& name : qualified_names) + insert_layer_name(name); + break; + } + + // Ignore everything else + case CSSRule::Type::Style: + case CSSRule::Type::Media: + case CSSRule::Type::FontFace: + case CSSRule::Type::Keyframes: + case CSSRule::Type::Keyframe: + case CSSRule::Type::Margin: + case CSSRule::Type::Namespace: + case CSSRule::Type::NestedDeclarations: + case CSSRule::Type::Page: + case CSSRule::Type::Property: + case CSSRule::Type::Supports: + break; + } + }); + }); + + // Now, produce a flat list of qualified names to use later + m_qualified_layer_names_in_order.clear(); + flatten_layer_names_tree(m_qualified_layer_names_in_order, ""sv, {}, root); +} + +bool StyleScope::may_have_has_selectors() const +{ + if (!has_valid_rule_cache()) + return true; + + build_rule_cache_if_needed(); + return m_selector_insights->has_has_selectors; +} + +bool StyleScope::have_has_selectors() const +{ + build_rule_cache_if_needed(); + return m_selector_insights->has_has_selectors; +} + +DOM::Document& StyleScope::document() const +{ + return m_node->document(); +} + +RuleCache const& StyleScope::get_pseudo_class_rule_cache(PseudoClass pseudo_class) const +{ + build_rule_cache_if_needed(); + return *m_pseudo_class_rule_cache[to_underlying(pseudo_class)]; +} + +void StyleScope::for_each_active_css_style_sheet(Function&& callback) const +{ + if (auto* shadow_root = as_if(*m_node)) { + shadow_root->for_each_active_css_style_sheet(move(callback)); + } else { + m_node->document().for_each_active_css_style_sheet(move(callback)); + } +} + +void StyleScope::invalidate_style_of_elements_affected_by_has() +{ + if (m_pending_nodes_for_style_invalidation_due_to_presence_of_has.is_empty()) { + return; + } + + ScopeGuard clear_pending_nodes_guard = [&] { + m_pending_nodes_for_style_invalidation_due_to_presence_of_has.clear(); + }; + + // It's ok to call have_has_selectors() instead of may_have_has_selectors() here and force + // rule cache build, because it's going to be built soon anyway, since we could get here + // only from update_style(). + if (!have_has_selectors()) { + return; + } + + auto nodes = move(m_pending_nodes_for_style_invalidation_due_to_presence_of_has); + for (auto const& node : nodes) { + if (!node) + continue; + for (auto ancestor = node.ptr(); ancestor; ancestor = ancestor->parent_or_shadow_host()) { + if (!ancestor->is_element()) + continue; + auto& element = static_cast(*ancestor); + element.invalidate_style_if_affected_by_has(); + + auto* parent = ancestor->parent_or_shadow_host(); + if (!parent) + return; + + // If any ancestor's sibling was tested against selectors like ".a:has(+ .b)" or ".a:has(~ .b)" + // its style might be affected by the change in descendant node. + parent->for_each_child_of_type([&](auto& ancestor_sibling_element) { + if (ancestor_sibling_element.affected_by_has_pseudo_class_with_relative_selector_that_has_sibling_combinator()) + ancestor_sibling_element.invalidate_style_if_affected_by_has(); + return IterationDecision::Continue; + }); + } + } +} + +} diff --git a/Libraries/LibWeb/CSS/StyleScope.h b/Libraries/LibWeb/CSS/StyleScope.h new file mode 100644 index 00000000000..7073e76c28f --- /dev/null +++ b/Libraries/LibWeb/CSS/StyleScope.h @@ -0,0 +1,121 @@ +/* + * Copyright (c) 2025, Andreas Kling + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace Web::CSS { + +struct MatchingRule { + GC::Ptr shadow_root; + GC::Ptr rule; // Either CSSStyleRule or CSSNestedDeclarations + GC::Ptr sheet; + Optional default_namespace; + Selector const& selector; + size_t style_sheet_index { 0 }; + size_t rule_index { 0 }; + + u32 specificity { 0 }; + CascadeOrigin cascade_origin; + bool contains_pseudo_element { false }; + bool slotted { false }; + + // Helpers to deal with the fact that `rule` might be a CSSStyleRule or a CSSNestedDeclarations + CSSStyleProperties const& declaration() const; + SelectorList const& absolutized_selectors() const; + FlyString const& qualified_layer_name() const; +}; + +struct RuleCache { + HashMap> rules_by_id; + HashMap> rules_by_class; + HashMap> rules_by_tag_name; + HashMap, AK::ASCIICaseInsensitiveFlyStringTraits> rules_by_attribute_name; + Array, to_underlying(CSS::PseudoElement::KnownPseudoElementCount)> rules_by_pseudo_element; + Vector root_rules; + Vector slotted_rules; + Vector other_rules; + + HashMap> rules_by_animation_keyframes; + + void add_rule(MatchingRule const&, Optional, bool contains_root_pseudo_class); + void for_each_matching_rules(DOM::AbstractElement, Function const&)> callback) const; +}; + +struct RuleCaches { + RuleCache main; + HashMap> by_layer; +}; + +struct SelectorInsights { + bool has_has_selectors { false }; +}; + +class StyleScope { +public: + explicit StyleScope(GC::Ref); + + DOM::Node& node() const { return m_node; } + DOM::Document& document() const; + + RuleCaches const& author_rule_cache() const { return *m_author_rule_cache; } + RuleCaches const& user_rule_cache() const { return *m_user_rule_cache; } + RuleCaches const& user_agent_rule_cache() const { return *m_user_agent_rule_cache; } + + [[nodiscard]] bool has_valid_rule_cache() const { return m_author_rule_cache; } + void invalidate_rule_cache(); + + [[nodiscard]] RuleCache const& get_pseudo_class_rule_cache(PseudoClass) const; + + template + void for_each_stylesheet(CascadeOrigin, Callback) const; + + void make_rule_cache_for_cascade_origin(CascadeOrigin, SelectorInsights&); + + void build_rule_cache(); + void build_rule_cache_if_needed() const; + + static void collect_selector_insights(Selector const&, SelectorInsights&); + + void build_qualified_layer_names_cache(); + + [[nodiscard]] bool may_have_has_selectors() const; + [[nodiscard]] bool have_has_selectors() const; + + void for_each_active_css_style_sheet(Function&& callback) const; + + void invalidate_style_of_elements_affected_by_has(); + + void schedule_ancestors_style_invalidation_due_to_presence_of_has(DOM::Node& node) { m_pending_nodes_for_style_invalidation_due_to_presence_of_has.set(node); } + + void visit_edges(GC::Cell::Visitor&); + + Vector m_qualified_layer_names_in_order; + OwnPtr m_selector_insights; + Array, to_underlying(PseudoClass::__Count)> m_pseudo_class_rule_cache; + OwnPtr m_style_invalidation_data; + OwnPtr m_author_rule_cache; + OwnPtr m_user_rule_cache; + OwnPtr m_user_agent_rule_cache; + + GC::Ptr m_user_style_sheet; + + HashTable> m_pending_nodes_for_style_invalidation_due_to_presence_of_has; + + GC::Ref m_node; +}; + +} diff --git a/Libraries/LibWeb/CSS/StyleSheetList.cpp b/Libraries/LibWeb/CSS/StyleSheetList.cpp index 4f8a945b060..cfa06660cc9 100644 --- a/Libraries/LibWeb/CSS/StyleSheetList.cpp +++ b/Libraries/LibWeb/CSS/StyleSheetList.cpp @@ -116,8 +116,15 @@ void StyleSheetList::add_sheet(CSSStyleSheet& sheet) return; } - document().style_computer().invalidate_rule_cache(); - document_or_shadow_root().invalidate_style(DOM::StyleInvalidationReason::StyleSheetListAddSheet); + if (auto* shadow_root = as_if(document_or_shadow_root())) { + if (auto* host = shadow_root->host()) { + host->invalidate_style(DOM::StyleInvalidationReason::StyleSheetListAddSheet); + } + shadow_root->style_scope().invalidate_rule_cache(); + } else { + document_or_shadow_root().invalidate_style(DOM::StyleInvalidationReason::StyleSheetListAddSheet); + document_or_shadow_root().document().style_scope().invalidate_rule_cache(); + } } void StyleSheetList::remove_sheet(CSSStyleSheet& sheet) @@ -131,8 +138,15 @@ void StyleSheetList::remove_sheet(CSSStyleSheet& sheet) return; } - m_document_or_shadow_root->document().style_computer().invalidate_rule_cache(); - document_or_shadow_root().invalidate_style(DOM::StyleInvalidationReason::StyleSheetListRemoveSheet); + if (auto* shadow_root = as_if(document_or_shadow_root())) { + if (auto* host = shadow_root->host()) { + host->invalidate_style(DOM::StyleInvalidationReason::StyleSheetListRemoveSheet); + } + shadow_root->style_scope().invalidate_rule_cache(); + } else { + document_or_shadow_root().invalidate_style(DOM::StyleInvalidationReason::StyleSheetListRemoveSheet); + document_or_shadow_root().document().style_scope().invalidate_rule_cache(); + } } GC::Ref StyleSheetList::create(GC::Ref document_or_shadow_root) diff --git a/Libraries/LibWeb/DOM/AbstractElement.cpp b/Libraries/LibWeb/DOM/AbstractElement.cpp index c18ca8e1d33..4e13bb6b184 100644 --- a/Libraries/LibWeb/DOM/AbstractElement.cpp +++ b/Libraries/LibWeb/DOM/AbstractElement.cpp @@ -209,4 +209,12 @@ String AbstractElement::debug_description() const return m_element->debug_description(); } +CSS::StyleScope const& AbstractElement::style_scope() const +{ + auto& root = m_element->root(); + if (root.is_shadow_root()) + return as(root).style_scope(); + return root.document().style_scope(); +} + } diff --git a/Libraries/LibWeb/DOM/AbstractElement.h b/Libraries/LibWeb/DOM/AbstractElement.h index 2d5cfd7b8b1..2dfcbce1044 100644 --- a/Libraries/LibWeb/DOM/AbstractElement.h +++ b/Libraries/LibWeb/DOM/AbstractElement.h @@ -60,6 +60,8 @@ public: String debug_description() const; bool operator==(AbstractElement const&) const = default; + CSS::StyleScope const& style_scope() const; + private: enum class WalkMethod : u8 { Previous, diff --git a/Libraries/LibWeb/DOM/AdoptedStyleSheets.cpp b/Libraries/LibWeb/DOM/AdoptedStyleSheets.cpp index 20f657706f5..b112e2e4809 100644 --- a/Libraries/LibWeb/DOM/AdoptedStyleSheets.cpp +++ b/Libraries/LibWeb/DOM/AdoptedStyleSheets.cpp @@ -31,7 +31,8 @@ GC::Ref create_adopted_style_sheets_list(Node& document return WebIDL::NotAllowedError::create(document_or_shadow_root.realm(), "Sharing a StyleSheet between documents is not allowed."_utf16); style_sheet.add_owning_document_or_shadow_root(document_or_shadow_root); - document_or_shadow_root.document().style_computer().invalidate_rule_cache(); + auto& style_scope = document_or_shadow_root.is_shadow_root() ? as(document_or_shadow_root).style_scope() : document_or_shadow_root.document().style_scope(); + style_scope.invalidate_rule_cache(); document_or_shadow_root.invalidate_style(DOM::StyleInvalidationReason::AdoptedStyleSheetsList); return {}; }); @@ -41,8 +42,9 @@ GC::Ref create_adopted_style_sheets_list(Node& document VERIFY(is(object)); auto& style_sheet = static_cast(object); + auto& style_scope = document_or_shadow_root.is_shadow_root() ? as(document_or_shadow_root).style_scope() : document_or_shadow_root.document().style_scope(); style_sheet.remove_owning_document_or_shadow_root(document_or_shadow_root); - document_or_shadow_root.document().style_computer().invalidate_rule_cache(); + style_scope.invalidate_rule_cache(); document_or_shadow_root.invalidate_style(DOM::StyleInvalidationReason::AdoptedStyleSheetsList); return {}; }); diff --git a/Libraries/LibWeb/DOM/Document.cpp b/Libraries/LibWeb/DOM/Document.cpp index f0973db5892..f9ec3ba32ea 100644 --- a/Libraries/LibWeb/DOM/Document.cpp +++ b/Libraries/LibWeb/DOM/Document.cpp @@ -469,6 +469,7 @@ Document::Document(JS::Realm& realm, URL::URL const& url, TemporaryDocumentForFr , m_editing_host_manager(EditingHostManager::create(realm, *this)) , m_dynamic_view_transition_style_sheet(parse_css_stylesheet(CSS::Parser::ParsingParams(realm), ""sv, {})) , m_style_invalidator(realm.heap().allocate()) + , m_style_scope(*this) { m_legacy_platform_object_flags = PlatformObject::LegacyPlatformObjectFlags { .supports_named_properties = true, @@ -544,6 +545,7 @@ WebIDL::ExceptionOr Document::populate_with_html_head_and_body() void Document::visit_edges(Cell::Visitor& visitor) { Base::visit_edges(visitor); + m_style_scope.visit_edges(visitor); visitor.visit(m_page); visitor.visit(m_window); visitor.visit(m_layout_root); @@ -1566,7 +1568,10 @@ void Document::update_style() // style change event. [CSS-Transitions-2] m_transition_generation++; - invalidate_style_of_elements_affected_by_has(); + style_scope().invalidate_style_of_elements_affected_by_has(); + for_each_shadow_root([&](auto& shadow_root) { + shadow_root.style_scope().invalidate_style_of_elements_affected_by_has(); + }); if (!m_style_invalidator->has_pending_invalidations() && !needs_full_style_update() && !needs_style_update() && !child_needs_style_update()) return; @@ -1786,54 +1791,13 @@ static Node* find_common_ancestor(Node* a, Node* b) return nullptr; } -void Document::invalidate_style_of_elements_affected_by_has() -{ - if (m_pending_nodes_for_style_invalidation_due_to_presence_of_has.is_empty()) { - return; - } - - ScopeGuard clear_pending_nodes_guard = [&] { - m_pending_nodes_for_style_invalidation_due_to_presence_of_has.clear(); - }; - - // It's ok to call have_has_selectors() instead of may_have_has_selectors() here and force - // rule cache build, because it's going to be build soon anyway, since we could get here - // only from update_style(). - if (!style_computer().have_has_selectors()) { - return; - } - - auto nodes = move(m_pending_nodes_for_style_invalidation_due_to_presence_of_has); - for (auto const& node : nodes) { - if (!node) - continue; - for (auto ancestor = node.ptr(); ancestor; ancestor = ancestor->parent_or_shadow_host()) { - if (!ancestor->is_element()) - continue; - auto& element = static_cast(*ancestor); - element.invalidate_style_if_affected_by_has(); - - auto* parent = ancestor->parent_or_shadow_host(); - if (!parent) - return; - - // If any ancestor's sibling was tested against selectors like ".a:has(+ .b)" or ".a:has(~ .b)" - // its style might be affected by the change in descendant node. - parent->for_each_child_of_type([&](auto& ancestor_sibling_element) { - if (ancestor_sibling_element.affected_by_has_pseudo_class_with_relative_selector_that_has_sibling_combinator()) - ancestor_sibling_element.invalidate_style_if_affected_by_has(); - return IterationDecision::Continue; - }); - } - } -} - 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& rules = style_computer().get_pseudo_class_rule_cache(pseudo_class); - auto& root = old_new_common_ancestor.root(); auto shadow_root = is(root) ? static_cast(&root) : nullptr; + auto& style_scope = shadow_root ? shadow_root->style_scope() : this->style_scope(); + + auto const& rules = style_scope.get_pseudo_class_rule_cache(pseudo_class); auto& style_computer = this->style_computer(); auto does_rule_match_on_element = [&](Element const& element, CSS::MatchingRule const& rule) { @@ -3486,13 +3450,25 @@ void Document::evaluate_media_queries_and_report_changes() void Document::evaluate_media_rules() { bool any_media_queries_changed_match_state = false; - for_each_active_css_style_sheet([&](CSS::CSSStyleSheet& style_sheet, auto) { + style_scope().for_each_active_css_style_sheet([&](CSS::CSSStyleSheet& style_sheet) { if (style_sheet.evaluate_media_queries(*this)) any_media_queries_changed_match_state = true; }); + for_each_shadow_root([&](auto& shadow_root) { + shadow_root.style_scope().for_each_active_css_style_sheet([&](CSS::CSSStyleSheet& style_sheet) { + if (style_sheet.evaluate_media_queries(*this)) + any_media_queries_changed_match_state = true; + }); + }); + if (any_media_queries_changed_match_state) { - style_computer().invalidate_rule_cache(); + // FIXME: Make this more efficient + style_scope().invalidate_rule_cache(); + for_each_shadow_root([&](auto& shadow_root) { + shadow_root.style_scope().invalidate_rule_cache(); + }); + invalidate_style(StyleInvalidationReason::MediaQueryChangedMatchState); } } @@ -6005,32 +5981,25 @@ WebIDL::ExceptionOr Document::set_adopted_style_sheets(JS::Value new_value return {}; } -void Document::for_each_active_css_style_sheet(Function)>&& callback) const +void Document::for_each_active_css_style_sheet(Function&& callback) const { if (m_style_sheets) { for (auto& style_sheet : m_style_sheets->sheets()) { if (!(style_sheet->is_alternate() && style_sheet->disabled())) - callback(*style_sheet, {}); + callback(*style_sheet); } } if (m_adopted_style_sheets) { m_adopted_style_sheets->for_each([&](auto& style_sheet) { if (!style_sheet.disabled()) - callback(style_sheet, {}); + callback(style_sheet); }); } if (m_dynamic_view_transition_style_sheet) { - callback(*m_dynamic_view_transition_style_sheet, {}); + callback(*m_dynamic_view_transition_style_sheet); } - - for_each_shadow_root([&](auto& shadow_root) { - shadow_root.for_each_css_style_sheet([&](auto& style_sheet) { - if (!style_sheet.disabled()) - callback(style_sheet, &shadow_root); - }); - }); } static Optional find_style_sheet_with_url(String const& url, CSS::CSSStyleSheet& style_sheet) diff --git a/Libraries/LibWeb/DOM/Document.h b/Libraries/LibWeb/DOM/Document.h index 3db410b7f0b..085509a235f 100644 --- a/Libraries/LibWeb/DOM/Document.h +++ b/Libraries/LibWeb/DOM/Document.h @@ -24,6 +24,7 @@ #include #include #include +#include #include #include #include @@ -262,7 +263,7 @@ public: CSS::StyleSheetList& style_sheets(); CSS::StyleSheetList const& style_sheets() const; - void for_each_active_css_style_sheet(Function)>&& callback) const; + void for_each_active_css_style_sheet(Function&& callback) const; CSS::StyleSheetList* style_sheets_for_bindings() { return &style_sheets(); } @@ -930,11 +931,6 @@ public: void add_render_blocking_element(GC::Ref); void remove_render_blocking_element(GC::Ref); - void schedule_ancestors_style_invalidation_due_to_presence_of_has(Node& node) - { - m_pending_nodes_for_style_invalidation_due_to_presence_of_has.set(node); - } - ElementByIdMap& element_by_id() const; auto& script_blocking_style_sheet_set() { return m_script_blocking_style_sheet_set; } @@ -951,6 +947,9 @@ public: NonnullRefPtr custom_property_initial_value(FlyString const& name) const; + CSS::StyleScope const& style_scope() const { return m_style_scope; } + CSS::StyleScope& style_scope() { return m_style_scope; } + protected: virtual void initialize(JS::Realm&) override; virtual void visit_edges(Cell::Visitor&) override; @@ -1341,12 +1340,12 @@ private: // https://drafts.csswg.org/css-view-transitions-1/#document-update-callback-queue Vector> m_update_callback_queue = {}; - HashTable> m_pending_nodes_for_style_invalidation_due_to_presence_of_has; - GC::Ref m_style_invalidator; // https://www.w3.org/TR/css-properties-values-api-1/#dom-window-registeredpropertyset-slot HashMap> m_registered_custom_properties; + + CSS::StyleScope m_style_scope; }; template<> diff --git a/Libraries/LibWeb/DOM/Node.cpp b/Libraries/LibWeb/DOM/Node.cpp index db446cd2dc5..aabe6d14699 100644 --- a/Libraries/LibWeb/DOM/Node.cpp +++ b/Libraries/LibWeb/DOM/Node.cpp @@ -420,10 +420,12 @@ void Node::invalidate_style(StyleInvalidationReason reason) if (is_character_data()) return; - if (document().style_computer().may_have_has_selectors()) { + auto& style_scope = root().is_shadow_root() ? static_cast(root()).style_scope() : document().style_scope(); + + if (style_scope.may_have_has_selectors()) { if (reason == StyleInvalidationReason::NodeRemove) { if (auto* parent = parent_or_shadow_host(); parent) { - document().schedule_ancestors_style_invalidation_due_to_presence_of_has(*parent); + style_scope.schedule_ancestors_style_invalidation_due_to_presence_of_has(*parent); parent->for_each_child_of_type([&](auto& element) { if (element.affected_by_has_pseudo_class_with_relative_selector_that_has_sibling_combinator()) element.invalidate_style_if_affected_by_has(); @@ -431,7 +433,7 @@ void Node::invalidate_style(StyleInvalidationReason reason) }); } } else { - document().schedule_ancestors_style_invalidation_due_to_presence_of_has(*this); + style_scope.schedule_ancestors_style_invalidation_due_to_presence_of_has(*this); } } @@ -499,29 +501,48 @@ void Node::invalidate_style(StyleInvalidationReason reason, Vectorroot(); + auto& style_scope = root.is_shadow_root() ? static_cast(root).style_scope() : document().style_scope(); + CSS::StyleScope* shadow_style_scope = nullptr; + if (auto* element = as_if(this); element && element->is_shadow_host()) { + if (auto element_shadow_root = element->shadow_root()) + shadow_style_scope = &element_shadow_root->style_scope(); + } + bool properties_used_in_has_selectors = false; for (auto const& property : properties) { - properties_used_in_has_selectors |= document().style_computer().invalidation_property_used_in_has_selector(property); + properties_used_in_has_selectors |= document().style_computer().invalidation_property_used_in_has_selector(property, style_scope); + if (shadow_style_scope) + properties_used_in_has_selectors |= document().style_computer().invalidation_property_used_in_has_selector(property, *shadow_style_scope); } if (properties_used_in_has_selectors) { - document().schedule_ancestors_style_invalidation_due_to_presence_of_has(*this); + style_scope.schedule_ancestors_style_invalidation_due_to_presence_of_has(*this); + if (shadow_style_scope) + shadow_style_scope->schedule_ancestors_style_invalidation_due_to_presence_of_has(*this); } - auto invalidation_set = document().style_computer().invalidation_set_for_properties(properties); - if (invalidation_set.needs_invalidate_whole_subtree()) { - invalidate_style(reason); - return; - } + auto invalidate_for_style_scope = [this, reason, &properties, &options](CSS::StyleScope& style_scope) { + auto invalidation_set = document().style_computer().invalidation_set_for_properties(properties, style_scope); - if (options.invalidate_self || invalidation_set.needs_invalidate_self()) { - set_needs_style_update(true); - } + if (invalidation_set.needs_invalidate_whole_subtree()) { + invalidate_style(reason); + return; + } - if (!invalidation_set.has_properties()) { - return; - } + if (options.invalidate_self || invalidation_set.needs_invalidate_self()) { + set_needs_style_update(true); + } - document().style_invalidator().add_pending_invalidation(*this, move(invalidation_set)); + if (!invalidation_set.has_properties()) { + return; + } + + document().style_invalidator().add_pending_invalidation(*this, move(invalidation_set)); + }; + + invalidate_for_style_scope(style_scope); + if (shadow_style_scope) + invalidate_for_style_scope(*shadow_style_scope); } Utf16String Node::child_text_content() const diff --git a/Libraries/LibWeb/DOM/ShadowRoot.cpp b/Libraries/LibWeb/DOM/ShadowRoot.cpp index d686bd2545c..c15fbba791b 100644 --- a/Libraries/LibWeb/DOM/ShadowRoot.cpp +++ b/Libraries/LibWeb/DOM/ShadowRoot.cpp @@ -23,6 +23,7 @@ GC_DEFINE_ALLOCATOR(ShadowRoot); ShadowRoot::ShadowRoot(Document& document, Element& host, Bindings::ShadowRootMode mode) : DocumentFragment(document) , m_mode(mode) + , m_style_scope(*this) { document.register_shadow_root({}, *this); set_host(&host); @@ -156,6 +157,7 @@ CSS::StyleSheetList const& ShadowRoot::style_sheets() const void ShadowRoot::visit_edges(Visitor& visitor) { Base::visit_edges(visitor); + m_style_scope.visit_edges(visitor); visitor.visit(m_style_sheets); visitor.visit(m_adopted_style_sheets); } @@ -196,6 +198,21 @@ void ShadowRoot::for_each_css_style_sheet(Function&& } } +void ShadowRoot::for_each_active_css_style_sheet(Function&& callback) const +{ + for (auto& style_sheet : style_sheets().sheets()) { + if (!style_sheet->disabled()) + callback(*style_sheet); + } + + if (m_adopted_style_sheets) { + m_adopted_style_sheets->for_each([&](auto& style_sheet) { + if (!style_sheet.disabled()) + callback(style_sheet); + }); + } +} + WebIDL::ExceptionOr>> ShadowRoot::get_animations() { return calculate_get_animations(*this); diff --git a/Libraries/LibWeb/DOM/ShadowRoot.h b/Libraries/LibWeb/DOM/ShadowRoot.h index db5874459fb..72e5650d42f 100644 --- a/Libraries/LibWeb/DOM/ShadowRoot.h +++ b/Libraries/LibWeb/DOM/ShadowRoot.h @@ -7,6 +7,7 @@ #pragma once #include +#include #include #include #include @@ -63,11 +64,15 @@ public: WebIDL::ExceptionOr set_adopted_style_sheets(JS::Value); void for_each_css_style_sheet(Function&& callback) const; + void for_each_active_css_style_sheet(Function&& callback) const; WebIDL::ExceptionOr>> get_animations(); ElementByIdMap& element_by_id() const; + CSS::StyleScope const& style_scope() const { return m_style_scope; } + CSS::StyleScope& style_scope() { return m_style_scope; } + virtual void finalize() override; protected: @@ -103,6 +108,8 @@ private: IntrusiveListNode m_list_node; + CSS::StyleScope m_style_scope; + public: using DocumentShadowRootList = IntrusiveList<&ShadowRoot::m_list_node>; }; diff --git a/Libraries/LibWeb/Forward.h b/Libraries/LibWeb/Forward.h index 6162d4c8136..7d3f2215bf5 100644 --- a/Libraries/LibWeb/Forward.h +++ b/Libraries/LibWeb/Forward.h @@ -370,6 +370,7 @@ class StringStyleValue; class StyleComputer; class StylePropertyMap; class StylePropertyMapReadOnly; +class StyleScope; class StyleSheet; class StyleSheetList; class StyleValue; diff --git a/Libraries/LibWeb/Page/Page.cpp b/Libraries/LibWeb/Page/Page.cpp index 0ccebeb5fd5..7a90029563c 100644 --- a/Libraries/LibWeb/Page/Page.cpp +++ b/Libraries/LibWeb/Page/Page.cpp @@ -599,7 +599,11 @@ void Page::set_user_style(String source) { m_user_style_sheet_source = source; if (top_level_traversable_is_initialized() && top_level_traversable()->active_document()) { - top_level_traversable()->active_document()->style_computer().invalidate_rule_cache(); + auto& document = *top_level_traversable()->active_document(); + document.style_scope().invalidate_rule_cache(); + document.for_each_shadow_root([](auto& shadow_root) { + shadow_root.style_scope().invalidate_rule_cache(); + }); } }