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(); + }); } }