diff options
-rw-r--r-- | Userland/Libraries/LibWeb/CSS/Parser/Parser.cpp | 20 | ||||
-rw-r--r-- | Userland/Libraries/LibWeb/CSS/Parser/Token.h | 8 | ||||
-rw-r--r-- | Userland/Libraries/LibWeb/CSS/StyleComputer.cpp | 35 | ||||
-rw-r--r-- | Userland/Libraries/LibWeb/CSS/StyleValue.h | 12 |
4 files changed, 56 insertions, 19 deletions
diff --git a/Userland/Libraries/LibWeb/CSS/Parser/Parser.cpp b/Userland/Libraries/LibWeb/CSS/Parser/Parser.cpp index 1ead40652e..cb0e850c4f 100644 --- a/Userland/Libraries/LibWeb/CSS/Parser/Parser.cpp +++ b/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; diff --git a/Userland/Libraries/LibWeb/CSS/Parser/Token.h b/Userland/Libraries/LibWeb/CSS/Parser/Token.h index a5c6e7b156..f80d7604eb 100644 --- a/Userland/Libraries/LibWeb/CSS/Parser/Token.h +++ b/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 }; diff --git a/Userland/Libraries/LibWeb/CSS/StyleComputer.cpp b/Userland/Libraries/LibWeb/CSS/StyleComputer.cpp index c7fd6898cb..0ed30cc942 100644 --- a/Userland/Libraries/LibWeb/CSS/StyleComputer.cpp +++ b/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; diff --git a/Userland/Libraries/LibWeb/CSS/StyleValue.h b/Userland/Libraries/LibWeb/CSS/StyleValue.h index 1a8c69241c..b90fe67ba0 100644 --- a/Userland/Libraries/LibWeb/CSS/StyleValue.h +++ b/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 { |