瀏覽代碼

LibWeb: Add basic support for the attr() CSS function

CSS Values and Units Module Level 5 defines attr as:
  `attr(<q-name> <attr-type>?, <declaration-value>?)`

This implementation does not contain support for the type argument,
effectively supporting `attr(<q-name>, <declaration-value>?)`
Simon Wanner 3 年之前
父節點
當前提交
6437f5da36

+ 10 - 10
Userland/Libraries/LibWeb/CSS/Parser/Parser.cpp

@@ -4294,9 +4294,9 @@ RefPtr<StyleValue> Parser::parse_as_css_value(PropertyID property_id)
 
 Result<NonnullRefPtr<StyleValue>, Parser::ParsingResult> Parser::parse_css_value(PropertyID property_id, TokenStream<StyleComponentValueRule>& tokens)
 {
-    auto block_contains_var = [](StyleBlockRule const& block, auto&& recurse) -> bool {
+    auto block_contains_var_or_attr = [](StyleBlockRule const& block, auto&& recurse) -> bool {
         for (auto const& token : block.values()) {
-            if (token.is_function() && token.function().name().equals_ignoring_case("var"sv))
+            if (token.is_function() && (token.function().name().equals_ignoring_case("var"sv) || token.function().name().equals_ignoring_case("attr"sv)))
                 return true;
             if (token.is_block() && recurse(token.block(), recurse))
                 return true;
@@ -4306,7 +4306,7 @@ Result<NonnullRefPtr<StyleValue>, Parser::ParsingResult> Parser::parse_css_value
 
     m_context.set_current_property_id(property_id);
     Vector<StyleComponentValueRule> component_values;
-    bool contains_var = false;
+    bool contains_var_or_attr = false;
 
     while (tokens.has_next_token()) {
         auto& token = tokens.next_token();
@@ -4324,18 +4324,18 @@ Result<NonnullRefPtr<StyleValue>, Parser::ParsingResult> Parser::parse_css_value
                 return ParsingResult::IncludesIgnoredVendorPrefix;
         }
 
-        if (!contains_var) {
-            if (token.is_function() && token.function().name().equals_ignoring_case("var"sv))
-                contains_var = true;
-            else if (token.is_block() && block_contains_var(token.block(), block_contains_var))
-                contains_var = true;
+        if (!contains_var_or_attr) {
+            if (token.is_function() && (token.function().name().equals_ignoring_case("var"sv) || token.function().name().equals_ignoring_case("attr"sv)))
+                contains_var_or_attr = true;
+            else if (token.is_block() && block_contains_var_or_attr(token.block(), block_contains_var_or_attr))
+                contains_var_or_attr = true;
         }
 
         component_values.append(token);
     }
 
-    if (property_id == PropertyID::Custom || contains_var)
-        return { UnresolvedStyleValue::create(move(component_values), contains_var) };
+    if (property_id == PropertyID::Custom || contains_var_or_attr)
+        return { UnresolvedStyleValue::create(move(component_values), contains_var_or_attr) };
 
     if (component_values.is_empty())
         return ParsingResult::SyntaxError;

+ 8 - 0
Userland/Libraries/LibWeb/CSS/Parser/Token.h

@@ -151,6 +151,14 @@ public:
     Position const& start_position() const { return m_start_position; }
     Position const& end_position() const { return m_end_position; }
 
+    static Token of_string(FlyString str)
+    {
+        Token token;
+        token.m_type = Type::String;
+        token.m_value = move(str);
+        return token;
+    }
+
 private:
     Type m_type { Type::Invalid };
 

+ 32 - 3
Userland/Libraries/LibWeb/CSS/StyleComputer.cpp

@@ -495,7 +495,7 @@ static RefPtr<StyleValue> get_custom_property(DOM::Element const& element, FlySt
 bool StyleComputer::expand_unresolved_values(DOM::Element& element, StringView property_name, HashMap<FlyString, NonnullRefPtr<PropertyDependencyNode>>& dependencies, Vector<StyleComponentValueRule> const& source, Vector<StyleComponentValueRule>& dest, size_t source_start_index) const
 {
     // 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 and attr()s replaced with their contents.
     // This is a very naive solution, and we could do better if the CSS Parser could accept tokens one at a time.
 
     // Arbitrary large value chosen to avoid the billion-laughs attack.
@@ -553,6 +553,35 @@ bool StyleComputer::expand_unresolved_values(DOM::Element& element, StringView p
                         return false;
                     continue;
                 }
+            } else if (value.function().name().equals_ignoring_case("attr"sv)) {
+                // https://drafts.csswg.org/css-values-5/#attr-substitution
+                auto const& attr_contents = value.function().values();
+                if (attr_contents.is_empty())
+                    return false;
+
+                auto const& attr_name_token = attr_contents.first();
+                if (!attr_name_token.is(Token::Type::Ident))
+                    return false;
+                auto attr_name = attr_name_token.token().ident();
+
+                auto attr_value = element.get_attribute(attr_name);
+                // 1. If the attr() function has a substitution value, replace the attr() function by the substitution value.
+                if (!attr_value.is_null()) {
+                    // FIXME: attr() should also accept an optional type argument, not just strings.
+                    dest.empend(Token::of_string(attr_value));
+                    continue;
+                }
+
+                // 2. Otherwise, if the attr() function has a fallback value as its last argument, replace the attr() function by the fallback value.
+                //    If there are any var() or attr() references in the fallback, substitute them as well.
+                if (attr_contents.size() > 2 && attr_contents[1].is(Token::Type::Comma)) {
+                    if (!expand_unresolved_values(element, property_name, dependencies, attr_contents, dest, 2))
+                        return false;
+                    continue;
+                }
+
+                // 3. Otherwise, the property containing the attr() function is invalid at computed-value time.
+                return false;
             }
 
             auto const& source_function = value.function();
@@ -580,9 +609,9 @@ bool StyleComputer::expand_unresolved_values(DOM::Element& element, StringView p
 
 RefPtr<StyleValue> StyleComputer::resolve_unresolved_style_value(DOM::Element& element, PropertyID property_id, UnresolvedStyleValue const& unresolved) 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() or attr(), unless it is a custom property's value, in which case we shouldn't be trying
     // to produce a different StyleValue from it.
-    VERIFY(unresolved.contains_var());
+    VERIFY(unresolved.contains_var_or_attr());
 
     Vector<StyleComponentValueRule> expanded_values;
     HashMap<FlyString, NonnullRefPtr<PropertyDependencyNode>> dependencies;

+ 6 - 6
Userland/Libraries/LibWeb/CSS/StyleValue.h

@@ -1625,27 +1625,27 @@ private:
 
 class UnresolvedStyleValue final : public StyleValue {
 public:
-    static NonnullRefPtr<UnresolvedStyleValue> create(Vector<StyleComponentValueRule>&& values, bool contains_var)
+    static NonnullRefPtr<UnresolvedStyleValue> create(Vector<StyleComponentValueRule>&& values, bool contains_var_or_attr)
     {
-        return adopt_ref(*new UnresolvedStyleValue(move(values), contains_var));
+        return adopt_ref(*new UnresolvedStyleValue(move(values), contains_var_or_attr));
     }
     virtual ~UnresolvedStyleValue() override = default;
 
     virtual String to_string() const override;
 
     Vector<StyleComponentValueRule> const& values() const { return m_values; }
-    bool contains_var() const { return m_contains_var; }
+    bool contains_var_or_attr() const { return m_contains_var_or_attr; }
 
 private:
-    UnresolvedStyleValue(Vector<StyleComponentValueRule>&& values, bool contains_var)
+    UnresolvedStyleValue(Vector<StyleComponentValueRule>&& values, bool contains_var_or_attr)
         : StyleValue(Type::Unresolved)
         , m_values(move(values))
-        , m_contains_var(contains_var)
+        , m_contains_var_or_attr(contains_var_or_attr)
     {
     }
 
     Vector<StyleComponentValueRule> m_values;
-    bool m_contains_var { false };
+    bool m_contains_var_or_attr { false };
 };
 
 class UnsetStyleValue final : public StyleValue {