Przeglądaj źródła

LibWeb: Perform CSS custom property cascade once instead of per-property

Previously we would re-run the entire CSS selector machinery for each
property resolved. Instead of doing that, we now resolve a final set of
custom property key/value pairs at the start of the cascade.
Andreas Kling 3 lat temu
rodzic
commit
8d104b7de2

+ 42 - 52
Userland/Libraries/LibWeb/CSS/StyleComputer.cpp

@@ -410,42 +410,7 @@ static void set_property_expanding_shorthands(StyleProperties& style, CSS::Prope
     style.set_property(property_id, value);
     style.set_property(property_id, value);
 }
 }
 
 
-StyleComputer::CustomPropertyResolutionTuple StyleComputer::resolve_custom_property_with_specificity(DOM::Element& element, String const& custom_property_name) const
-{
-    if (auto maybe_property = element.resolve_custom_property(custom_property_name); maybe_property.has_value())
-        return maybe_property.value();
-
-    auto* parent_element = element.parent_element();
-    CustomPropertyResolutionTuple parent_resolved {};
-    if (parent_element)
-        parent_resolved = resolve_custom_property_with_specificity(*parent_element, custom_property_name);
-
-    auto matching_rules = collect_matching_rules(element);
-    sort_matching_rules(matching_rules);
-
-    for (int i = matching_rules.size() - 1; i >= 0; --i) {
-        auto& match = matching_rules[i];
-        if (match.specificity < parent_resolved.specificity)
-            continue;
-
-        auto custom_property_style = verify_cast<PropertyOwningCSSStyleDeclaration>(match.rule->declaration()).custom_property(custom_property_name);
-        if (custom_property_style.has_value()) {
-            element.add_custom_property(custom_property_name, { custom_property_style.value(), match.specificity });
-            return { custom_property_style.value(), match.specificity };
-        }
-    }
-
-    return parent_resolved;
-}
-
-Optional<StyleProperty> StyleComputer::resolve_custom_property(DOM::Element& element, String const& custom_property_name) const
-{
-    auto resolved_with_specificity = resolve_custom_property_with_specificity(element, custom_property_name);
-
-    return resolved_with_specificity.style;
-}
-
-bool StyleComputer::expand_unresolved_values(DOM::Element& element, StringView property_name, HashMap<String, NonnullRefPtr<PropertyDependencyNode>>& dependencies, Vector<StyleComponentValueRule> const& source, Vector<StyleComponentValueRule>& dest, size_t source_start_index) const
+bool StyleComputer::expand_unresolved_values(DOM::Element& element, StringView property_name, HashMap<String, NonnullRefPtr<PropertyDependencyNode>>& dependencies, Vector<StyleComponentValueRule> const& source, Vector<StyleComponentValueRule>& dest, size_t source_start_index, HashMap<String, StyleProperty const*> const& custom_properties) const
 {
 {
     // FIXME: Do this better!
     // FIXME: Do this better!
     // We build a copy of the tree of StyleComponentValueRules, with all var()s replaced with their contents.
     // We build a copy of the tree of StyleComponentValueRules, with all var()s replaced with their contents.
@@ -459,10 +424,10 @@ bool StyleComputer::expand_unresolved_values(DOM::Element& element, StringView p
         return false;
         return false;
     }
     }
 
 
-    auto get_custom_property = [this, &element](auto& name) -> RefPtr<StyleValue> {
-        auto custom_property = resolve_custom_property(element, name);
-        if (custom_property.has_value())
-            return custom_property.value().value;
+    auto get_custom_property = [&custom_properties](auto& name) -> RefPtr<StyleValue> {
+        auto it = custom_properties.find(name);
+        if (it != custom_properties.end())
+            return it->value->value;
         return nullptr;
         return nullptr;
     };
     };
 
 
@@ -502,14 +467,14 @@ bool StyleComputer::expand_unresolved_values(DOM::Element& element, StringView p
 
 
                 if (auto custom_property_value = get_custom_property(custom_property_name)) {
                 if (auto custom_property_value = get_custom_property(custom_property_name)) {
                     VERIFY(custom_property_value->is_unresolved());
                     VERIFY(custom_property_value->is_unresolved());
-                    if (!expand_unresolved_values(element, custom_property_name, dependencies, custom_property_value->as_unresolved().values(), dest))
+                    if (!expand_unresolved_values(element, custom_property_name, dependencies, custom_property_value->as_unresolved().values(), dest, 0, custom_properties))
                         return false;
                         return false;
                     continue;
                     continue;
                 }
                 }
 
 
                 // Use the provided fallback value, if any.
                 // Use the provided fallback value, if any.
                 if (var_contents.size() > 2 && var_contents[1].is(Token::Type::Comma)) {
                 if (var_contents.size() > 2 && var_contents[1].is(Token::Type::Comma)) {
-                    if (!expand_unresolved_values(element, property_name, dependencies, var_contents, dest, 2))
+                    if (!expand_unresolved_values(element, property_name, dependencies, var_contents, dest, 2, custom_properties))
                         return false;
                         return false;
                     continue;
                     continue;
                 }
                 }
@@ -517,7 +482,7 @@ bool StyleComputer::expand_unresolved_values(DOM::Element& element, StringView p
 
 
             auto const& source_function = value.function();
             auto const& source_function = value.function();
             Vector<StyleComponentValueRule> function_values;
             Vector<StyleComponentValueRule> function_values;
-            if (!expand_unresolved_values(element, property_name, dependencies, source_function.values(), function_values))
+            if (!expand_unresolved_values(element, property_name, dependencies, source_function.values(), function_values, 0, custom_properties))
                 return false;
                 return false;
             NonnullRefPtr<StyleFunctionRule> function = adopt_ref(*new StyleFunctionRule(source_function.name(), move(function_values)));
             NonnullRefPtr<StyleFunctionRule> function = adopt_ref(*new StyleFunctionRule(source_function.name(), move(function_values)));
             dest.empend(function);
             dest.empend(function);
@@ -526,7 +491,7 @@ bool StyleComputer::expand_unresolved_values(DOM::Element& element, StringView p
         if (value.is_block()) {
         if (value.is_block()) {
             auto const& source_block = value.block();
             auto const& source_block = value.block();
             Vector<StyleComponentValueRule> block_values;
             Vector<StyleComponentValueRule> block_values;
-            if (!expand_unresolved_values(element, property_name, dependencies, source_block.values(), block_values))
+            if (!expand_unresolved_values(element, property_name, dependencies, source_block.values(), block_values, 0, custom_properties))
                 return false;
                 return false;
             NonnullRefPtr<StyleBlockRule> block = adopt_ref(*new StyleBlockRule(source_block.token(), move(block_values)));
             NonnullRefPtr<StyleBlockRule> block = adopt_ref(*new StyleBlockRule(source_block.token(), move(block_values)));
             dest.empend(block);
             dest.empend(block);
@@ -538,7 +503,7 @@ bool StyleComputer::expand_unresolved_values(DOM::Element& element, StringView p
     return true;
     return true;
 }
 }
 
 
-RefPtr<StyleValue> StyleComputer::resolve_unresolved_style_value(DOM::Element& element, PropertyID property_id, UnresolvedStyleValue const& unresolved) const
+RefPtr<StyleValue> StyleComputer::resolve_unresolved_style_value(DOM::Element& element, PropertyID property_id, UnresolvedStyleValue const& unresolved, HashMap<String, StyleProperty const*> const& custom_properties) const
 {
 {
     // Unresolved always contains a var(), unless it is a custom property's value, in which case we shouldn't be trying
     // Unresolved always contains a var(), unless it is a custom property's value, in which case we shouldn't be trying
     // to produce a different StyleValue from it.
     // to produce a different StyleValue from it.
@@ -546,7 +511,7 @@ RefPtr<StyleValue> StyleComputer::resolve_unresolved_style_value(DOM::Element& e
 
 
     Vector<StyleComponentValueRule> expanded_values;
     Vector<StyleComponentValueRule> expanded_values;
     HashMap<String, NonnullRefPtr<PropertyDependencyNode>> dependencies;
     HashMap<String, NonnullRefPtr<PropertyDependencyNode>> dependencies;
-    if (!expand_unresolved_values(element, string_from_property_id(property_id), dependencies, unresolved.values(), expanded_values))
+    if (!expand_unresolved_values(element, string_from_property_id(property_id), dependencies, unresolved.values(), expanded_values, 0, custom_properties))
         return {};
         return {};
 
 
     if (auto parsed_value = Parser::parse_css_value({}, ParsingContext { document() }, property_id, expanded_values))
     if (auto parsed_value = Parser::parse_css_value({}, ParsingContext { document() }, property_id, expanded_values))
@@ -555,7 +520,7 @@ RefPtr<StyleValue> StyleComputer::resolve_unresolved_style_value(DOM::Element& e
     return {};
     return {};
 }
 }
 
 
-void StyleComputer::cascade_declarations(StyleProperties& style, DOM::Element& element, Vector<MatchingRule> const& matching_rules, CascadeOrigin cascade_origin, bool important) const
+void StyleComputer::cascade_declarations(StyleProperties& style, DOM::Element& element, Vector<MatchingRule> const& matching_rules, CascadeOrigin cascade_origin, bool important, HashMap<String, StyleProperty const*> const& custom_properties) const
 {
 {
     for (auto const& match : matching_rules) {
     for (auto const& match : matching_rules) {
         for (auto const& property : verify_cast<PropertyOwningCSSStyleDeclaration>(match.rule->declaration()).properties()) {
         for (auto const& property : verify_cast<PropertyOwningCSSStyleDeclaration>(match.rule->declaration()).properties()) {
@@ -563,7 +528,7 @@ void StyleComputer::cascade_declarations(StyleProperties& style, DOM::Element& e
                 continue;
                 continue;
             auto property_value = property.value;
             auto property_value = property.value;
             if (property.value->is_unresolved()) {
             if (property.value->is_unresolved()) {
-                if (auto resolved = resolve_unresolved_style_value(element, property.property_id, property.value->as_unresolved()))
+                if (auto resolved = resolve_unresolved_style_value(element, property.property_id, property.value->as_unresolved(), custom_properties))
                     property_value = resolved.release_nonnull();
                     property_value = resolved.release_nonnull();
             }
             }
             set_property_expanding_shorthands(style, property.property_id, property_value, m_document);
             set_property_expanding_shorthands(style, property.property_id, property_value, m_document);
@@ -581,6 +546,28 @@ void StyleComputer::cascade_declarations(StyleProperties& style, DOM::Element& e
     }
     }
 }
 }
 
 
+static HashMap<String, StyleProperty const*> cascade_custom_properties(DOM::Element& element, Vector<MatchingRule> const& matching_rules)
+{
+    HashMap<String, StyleProperty const*> custom_properties;
+
+    if (auto* parent_element = element.parent_element()) {
+        for (auto const& it : parent_element->custom_properties())
+            custom_properties.set(it.key, &it.value);
+    }
+
+    for (auto const& matching_rule : matching_rules) {
+        for (auto const& it : verify_cast<PropertyOwningCSSStyleDeclaration>(matching_rule.rule->declaration()).custom_properties()) {
+            custom_properties.set(it.key, &it.value);
+        }
+    }
+
+    element.custom_properties().clear();
+    for (auto& it : custom_properties)
+        element.add_custom_property(it.key, *it.value);
+
+    return custom_properties;
+}
+
 // https://www.w3.org/TR/css-cascade/#cascading
 // https://www.w3.org/TR/css-cascade/#cascading
 void StyleComputer::compute_cascaded_values(StyleProperties& style, DOM::Element& element) const
 void StyleComputer::compute_cascaded_values(StyleProperties& style, DOM::Element& element) const
 {
 {
@@ -591,15 +578,18 @@ void StyleComputer::compute_cascaded_values(StyleProperties& style, DOM::Element
     matching_rule_set.author_rules = collect_matching_rules(element, CascadeOrigin::Author);
     matching_rule_set.author_rules = collect_matching_rules(element, CascadeOrigin::Author);
     sort_matching_rules(matching_rule_set.author_rules);
     sort_matching_rules(matching_rule_set.author_rules);
 
 
+    // Then we resolve all the CSS custom properties ("variables") for this element:
+    auto custom_properties = cascade_custom_properties(element, matching_rule_set.author_rules);
+
     // Then we apply the declarations from the matched rules in cascade order:
     // Then we apply the declarations from the matched rules in cascade order:
 
 
     // Normal user agent declarations
     // Normal user agent declarations
-    cascade_declarations(style, element, matching_rule_set.user_agent_rules, CascadeOrigin::UserAgent, false);
+    cascade_declarations(style, element, matching_rule_set.user_agent_rules, CascadeOrigin::UserAgent, false, custom_properties);
 
 
     // FIXME: Normal user declarations
     // FIXME: Normal user declarations
 
 
     // Normal author declarations
     // Normal author declarations
-    cascade_declarations(style, element, matching_rule_set.author_rules, CascadeOrigin::Author, false);
+    cascade_declarations(style, element, matching_rule_set.author_rules, CascadeOrigin::Author, false, custom_properties);
 
 
     // Author presentational hints (NOTE: The spec doesn't say exactly how to prioritize these.)
     // Author presentational hints (NOTE: The spec doesn't say exactly how to prioritize these.)
     element.apply_presentational_hints(style);
     element.apply_presentational_hints(style);
@@ -607,12 +597,12 @@ void StyleComputer::compute_cascaded_values(StyleProperties& style, DOM::Element
     // FIXME: Animation declarations [css-animations-1]
     // FIXME: Animation declarations [css-animations-1]
 
 
     // Important author declarations
     // Important author declarations
-    cascade_declarations(style, element, matching_rule_set.author_rules, CascadeOrigin::Author, true);
+    cascade_declarations(style, element, matching_rule_set.author_rules, CascadeOrigin::Author, true, custom_properties);
 
 
     // FIXME: Important user declarations
     // FIXME: Important user declarations
 
 
     // Important user agent declarations
     // Important user agent declarations
-    cascade_declarations(style, element, matching_rule_set.user_agent_rules, CascadeOrigin::UserAgent, true);
+    cascade_declarations(style, element, matching_rule_set.user_agent_rules, CascadeOrigin::UserAgent, true, custom_properties);
 
 
     // FIXME: Transition declarations [css-transitions-1]
     // FIXME: Transition declarations [css-transitions-1]
 }
 }

+ 3 - 9
Userland/Libraries/LibWeb/CSS/StyleComputer.h

@@ -65,12 +65,6 @@ public:
     };
     };
 
 
     Vector<MatchingRule> collect_matching_rules(DOM::Element const&, CascadeOrigin = CascadeOrigin::Any) const;
     Vector<MatchingRule> collect_matching_rules(DOM::Element const&, CascadeOrigin = CascadeOrigin::Any) const;
-    struct CustomPropertyResolutionTuple {
-        Optional<StyleProperty> style {};
-        u32 specificity { 0 };
-    };
-    CustomPropertyResolutionTuple resolve_custom_property_with_specificity(DOM::Element&, String const&) const;
-    Optional<StyleProperty> resolve_custom_property(DOM::Element&, String const&) const;
 
 
 private:
 private:
     void compute_cascaded_values(StyleProperties&, DOM::Element&) const;
     void compute_cascaded_values(StyleProperties&, DOM::Element&) const;
@@ -81,8 +75,8 @@ private:
 
 
     void compute_defaulted_property_value(StyleProperties&, DOM::Element const*, CSS::PropertyID) const;
     void compute_defaulted_property_value(StyleProperties&, DOM::Element const*, CSS::PropertyID) const;
 
 
-    RefPtr<StyleValue> resolve_unresolved_style_value(DOM::Element&, PropertyID, UnresolvedStyleValue const&) const;
-    bool expand_unresolved_values(DOM::Element&, StringView property_name, HashMap<String, NonnullRefPtr<PropertyDependencyNode>>& dependencies, Vector<StyleComponentValueRule> const& source, Vector<StyleComponentValueRule>& dest, size_t source_start_index = 0) const;
+    RefPtr<StyleValue> resolve_unresolved_style_value(DOM::Element&, PropertyID, UnresolvedStyleValue const&, HashMap<String, StyleProperty const*> const&) const;
+    bool expand_unresolved_values(DOM::Element&, StringView property_name, HashMap<String, NonnullRefPtr<PropertyDependencyNode>>& dependencies, Vector<StyleComponentValueRule> const& source, Vector<StyleComponentValueRule>& dest, size_t source_start_index, HashMap<String, StyleProperty const*> const& custom_properties) const;
 
 
     template<typename Callback>
     template<typename Callback>
     void for_each_stylesheet(CascadeOrigin, Callback) const;
     void for_each_stylesheet(CascadeOrigin, Callback) const;
@@ -92,7 +86,7 @@ private:
         Vector<MatchingRule> author_rules;
         Vector<MatchingRule> author_rules;
     };
     };
 
 
-    void cascade_declarations(StyleProperties&, DOM::Element&, Vector<MatchingRule> const&, CascadeOrigin, bool important) const;
+    void cascade_declarations(StyleProperties&, DOM::Element&, Vector<MatchingRule> const&, CascadeOrigin, bool important, HashMap<String, StyleProperty const*> const&) const;
 
 
     DOM::Document& m_document;
     DOM::Document& m_document;
 };
 };

+ 6 - 8
Userland/Libraries/LibWeb/DOM/Element.h

@@ -111,15 +111,13 @@ public:
     const ShadowRoot* shadow_root() const { return m_shadow_root; }
     const ShadowRoot* shadow_root() const { return m_shadow_root; }
     void set_shadow_root(RefPtr<ShadowRoot>);
     void set_shadow_root(RefPtr<ShadowRoot>);
 
 
-    Optional<CSS::StyleComputer::CustomPropertyResolutionTuple> resolve_custom_property(const String& custom_property_name) const
+    void add_custom_property(String custom_property_name, CSS::StyleProperty style_property)
     {
     {
-        return m_custom_properties.get(custom_property_name);
+        m_custom_properties.set(move(custom_property_name), move(style_property));
     }
     }
-    void add_custom_property(const String& custom_property_name, CSS::StyleComputer::CustomPropertyResolutionTuple style_property)
-    {
-        m_custom_properties.set(custom_property_name, style_property);
-    }
-    HashMap<String, CSS::StyleComputer::CustomPropertyResolutionTuple> const& custom_properties() const { return m_custom_properties; }
+
+    HashMap<String, CSS::StyleProperty> const& custom_properties() const { return m_custom_properties; }
+    HashMap<String, CSS::StyleProperty>& custom_properties() { return m_custom_properties; }
 
 
     void queue_an_element_task(HTML::Task::Source, Function<void()>);
     void queue_an_element_task(HTML::Task::Source, Function<void()>);
 
 
@@ -146,7 +144,7 @@ private:
     RefPtr<CSS::CSSStyleDeclaration> m_inline_style;
     RefPtr<CSS::CSSStyleDeclaration> m_inline_style;
 
 
     RefPtr<CSS::StyleProperties> m_specified_css_values;
     RefPtr<CSS::StyleProperties> m_specified_css_values;
-    HashMap<String, CSS::StyleComputer::CustomPropertyResolutionTuple> m_custom_properties;
+    HashMap<String, CSS::StyleProperty> m_custom_properties;
 
 
     RefPtr<DOMTokenList> m_class_list;
     RefPtr<DOMTokenList> m_class_list;
     Vector<FlyString> m_classes;
     Vector<FlyString> m_classes;

+ 2 - 2
Userland/Services/WebContent/ClientConnection.cpp

@@ -288,9 +288,9 @@ Messages::WebContentServer::InspectDomNodeResponse ClientConnection::inspect_dom
             auto const* element_to_check = &element;
             auto const* element_to_check = &element;
             while (element_to_check) {
             while (element_to_check) {
                 for (auto const& property : element_to_check->custom_properties()) {
                 for (auto const& property : element_to_check->custom_properties()) {
-                    if (!seen_properties.contains(property.key) && property.value.style.has_value()) {
+                    if (!seen_properties.contains(property.key)) {
                         seen_properties.set(property.key);
                         seen_properties.set(property.key);
-                        serializer.add(property.key, property.value.style.value().value->to_string());
+                        serializer.add(property.key, property.value.value->to_string());
                     }
                     }
                 }
                 }