diff options
author | Sam Atkins <atkinssj@serenityos.org> | 2021-12-03 12:32:12 +0000 |
---|---|---|
committer | Andreas Kling <kling@serenityos.org> | 2021-12-09 21:30:31 +0100 |
commit | 23dc0dac88a248d4ac9b1be36212d065c7112629 (patch) | |
tree | f6757d6b2d52cc4770e49c37c520071a5c16e865 /Userland/Libraries/LibWeb | |
parent | 000fb5a70d0a7683d232cdec90884224931a12c2 (diff) | |
download | serenity-23dc0dac88a248d4ac9b1be36212d065c7112629.zip |
LibWeb: Parse and resolve UnresolvedStyleValues
If a property is custom or contains a `var()` reference, it cannot be
parsed into a proper StyleValue immediately, so we store it as an
UnresolvedStyleValue until the property is compute. Then, at compute
time, we resolve them by expanding out any `var()` references, and
parsing the result.
The implementation here is very naive, and involves copying the
UnresolvedStyleValue's tree of StyleComponentValueRules while copying
the contents of any `var()`s it finds along the way. This is quite an
expensive operation to do every time that the style is computed.
Diffstat (limited to 'Userland/Libraries/LibWeb')
-rw-r--r-- | Userland/Libraries/LibWeb/CSS/Parser/Parser.cpp | 44 | ||||
-rw-r--r-- | Userland/Libraries/LibWeb/CSS/Parser/Parser.h | 2 | ||||
-rw-r--r-- | Userland/Libraries/LibWeb/CSS/Parser/StyleFunctionRule.h | 3 | ||||
-rw-r--r-- | Userland/Libraries/LibWeb/CSS/Parser/StyleRules.cpp | 8 | ||||
-rw-r--r-- | Userland/Libraries/LibWeb/CSS/StyleComputer.cpp | 83 | ||||
-rw-r--r-- | Userland/Libraries/LibWeb/CSS/StyleComputer.h | 4 |
6 files changed, 138 insertions, 6 deletions
diff --git a/Userland/Libraries/LibWeb/CSS/Parser/Parser.cpp b/Userland/Libraries/LibWeb/CSS/Parser/Parser.cpp index f684647903..61a12a0849 100644 --- a/Userland/Libraries/LibWeb/CSS/Parser/Parser.cpp +++ b/Userland/Libraries/LibWeb/CSS/Parser/Parser.cpp @@ -3435,8 +3435,19 @@ 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 { + for (auto const& token : block.values()) { + if (token.is_function() && token.function().name().equals_ignoring_case("var"sv)) + return true; + if (token.is_block() && recurse(token.block(), recurse)) + return true; + } + return false; + }; + m_context.set_current_property_id(property_id); Vector<StyleComponentValueRule> component_values; + bool contains_var = false; while (tokens.has_next_token()) { auto& token = tokens.next_token(); @@ -3446,15 +3457,27 @@ Result<NonnullRefPtr<StyleValue>, Parser::ParsingResult> Parser::parse_css_value break; } - if (token.is(Token::Type::Whitespace)) - continue; + if (property_id != PropertyID::Custom) { + if (token.is(Token::Type::Whitespace)) + continue; - if (token.is(Token::Type::Ident) && has_ignored_vendor_prefix(token.token().ident())) - return ParsingResult::IncludesIgnoredVendorPrefix; + if (token.is(Token::Type::Ident) && has_ignored_vendor_prefix(token.token().ident())) + 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; + } component_values.append(token); } + if (property_id == PropertyID::Custom || contains_var) + return { UnresolvedStyleValue::create(move(component_values), contains_var) }; + if (component_values.is_empty()) return ParsingResult::SyntaxError; @@ -4167,6 +4190,19 @@ bool Parser::has_ignored_vendor_prefix(StringView string) return true; } +RefPtr<StyleValue> Parser::parse_css_value(Badge<StyleComputer>, ParsingContext const& context, PropertyID property_id, Vector<StyleComponentValueRule> const& tokens) +{ + if (tokens.is_empty() || property_id == CSS::PropertyID::Invalid || property_id == CSS::PropertyID::Custom) + return {}; + + CSS::Parser parser(context, ""); + CSS::TokenStream<CSS::StyleComponentValueRule> token_stream { tokens }; + auto result = parser.parse_css_value(property_id, token_stream); + if (result.is_error()) + return {}; + return result.release_value(); +} + } namespace Web { diff --git a/Userland/Libraries/LibWeb/CSS/Parser/Parser.h b/Userland/Libraries/LibWeb/CSS/Parser/Parser.h index 7229a6bf40..5842370f7b 100644 --- a/Userland/Libraries/LibWeb/CSS/Parser/Parser.h +++ b/Userland/Libraries/LibWeb/CSS/Parser/Parser.h @@ -113,6 +113,8 @@ public: RefPtr<StyleValue> parse_as_css_value(PropertyID); + static RefPtr<StyleValue> parse_css_value(Badge<StyleComputer>, ParsingContext const&, PropertyID, Vector<StyleComponentValueRule> const&); + private: enum class ParsingResult { Done, diff --git a/Userland/Libraries/LibWeb/CSS/Parser/StyleFunctionRule.h b/Userland/Libraries/LibWeb/CSS/Parser/StyleFunctionRule.h index 661292268f..3ed89ded67 100644 --- a/Userland/Libraries/LibWeb/CSS/Parser/StyleFunctionRule.h +++ b/Userland/Libraries/LibWeb/CSS/Parser/StyleFunctionRule.h @@ -20,7 +20,8 @@ class StyleFunctionRule : public RefCounted<StyleFunctionRule> { friend class Parser; public: - StyleFunctionRule(String name); + explicit StyleFunctionRule(String name); + StyleFunctionRule(String name, Vector<StyleComponentValueRule>&& values); ~StyleFunctionRule(); String const& name() const { return m_name; } diff --git a/Userland/Libraries/LibWeb/CSS/Parser/StyleRules.cpp b/Userland/Libraries/LibWeb/CSS/Parser/StyleRules.cpp index 50269c73f6..9b67b058de 100644 --- a/Userland/Libraries/LibWeb/CSS/Parser/StyleRules.cpp +++ b/Userland/Libraries/LibWeb/CSS/Parser/StyleRules.cpp @@ -57,7 +57,13 @@ StyleDeclarationRule::StyleDeclarationRule() { } StyleDeclarationRule::~StyleDeclarationRule() { } StyleFunctionRule::StyleFunctionRule(String name) - : m_name(name) + : m_name(move(name)) +{ +} + +StyleFunctionRule::StyleFunctionRule(String name, Vector<StyleComponentValueRule>&& values) + : m_name(move(name)) + , m_values(move(values)) { } StyleFunctionRule::~StyleFunctionRule() { } diff --git a/Userland/Libraries/LibWeb/CSS/StyleComputer.cpp b/Userland/Libraries/LibWeb/CSS/StyleComputer.cpp index 3124b0572d..9b0e4afa33 100644 --- a/Userland/Libraries/LibWeb/CSS/StyleComputer.cpp +++ b/Userland/Libraries/LibWeb/CSS/StyleComputer.cpp @@ -452,6 +452,85 @@ struct MatchingDeclarations { Vector<MatchingRule> author_rules; }; +bool StyleComputer::expand_unresolved_values(DOM::Element& element, Vector<StyleComponentValueRule> const& source, Vector<StyleComponentValueRule>& dest) const +{ + // FIXME: Do this better! + // We build a copy of the tree of StyleComponentValueRules, with all var()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. + + // FIXME: Handle dependency cycles. https://www.w3.org/TR/css-variables-1/#cycles + // FIXME: Handle overly-long variables. https://www.w3.org/TR/css-variables-1/#long-variables + + 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; + return nullptr; + }; + + for (auto& value : source) { + if (value.is_function()) { + if (value.function().name().equals_ignoring_case("var"sv)) { + auto& var_contents = value.function().values(); + if (var_contents.is_empty()) + return false; + + auto& custom_property_name_token = var_contents.first(); + if (!custom_property_name_token.is(Token::Type::Ident)) + return false; + auto custom_property_name = custom_property_name_token.token().ident(); + if (!custom_property_name.starts_with("--")) + return false; + + if (auto custom_property_value = get_custom_property(custom_property_name)) { + VERIFY(custom_property_value->is_unresolved()); + if (!expand_unresolved_values(element, custom_property_value->as_unresolved().values(), dest)) + return false; + continue; + } + + // TODO: Handle fallback value + } + + auto& source_function = value.function(); + Vector<StyleComponentValueRule> function_values; + if (!expand_unresolved_values(element, source_function.values(), function_values)) + return false; + NonnullRefPtr<StyleFunctionRule> function = adopt_ref(*new StyleFunctionRule(source_function.name(), move(function_values))); + dest.empend(function); + continue; + } + if (value.is_block()) { + auto& source_block = value.block(); + Vector<StyleComponentValueRule> block_values; + if (!expand_unresolved_values(element, source_block.values(), block_values)) + return false; + NonnullRefPtr<StyleBlockRule> block = adopt_ref(*new StyleBlockRule(source_block.token(), move(block_values))); + dest.empend(block); + continue; + } + dest.empend(value.token()); + } + + return true; +} + +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 + // to produce a different StyleValue from it. + VERIFY(unresolved.contains_var()); + + Vector<StyleComponentValueRule> expanded_values; + if (!expand_unresolved_values(element, unresolved.values(), expanded_values)) + return {}; + + if (auto parsed_value = Parser::parse_css_value({}, ParsingContext { document() }, property_id, expanded_values)) + return parsed_value.release_nonnull(); + + return {}; +} + void StyleComputer::cascade_declarations(StyleProperties& style, DOM::Element& element, Vector<MatchingRule> const& matching_rules, CascadeOrigin cascade_origin, bool important) const { for (auto& match : matching_rules) { @@ -466,6 +545,10 @@ void StyleComputer::cascade_declarations(StyleProperties& style, DOM::Element& e property_value = resolved.value().value; } } + if (property.value->is_unresolved()) { + if (auto resolved = resolve_unresolved_style_value(element, property.property_id, property.value->as_unresolved())) + property_value = resolved.release_nonnull(); + } set_property_expanding_shorthands(style, property.property_id, property_value, m_document); } } diff --git a/Userland/Libraries/LibWeb/CSS/StyleComputer.h b/Userland/Libraries/LibWeb/CSS/StyleComputer.h index a301b243c0..3df5eff4d1 100644 --- a/Userland/Libraries/LibWeb/CSS/StyleComputer.h +++ b/Userland/Libraries/LibWeb/CSS/StyleComputer.h @@ -9,6 +9,7 @@ #include <AK/NonnullRefPtrVector.h> #include <AK/OwnPtr.h> #include <LibWeb/CSS/CSSStyleDeclaration.h> +#include <LibWeb/CSS/Parser/StyleComponentValueRule.h> #include <LibWeb/CSS/StyleProperties.h> #include <LibWeb/Forward.h> @@ -60,6 +61,9 @@ private: 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&, Vector<StyleComponentValueRule> const& source, Vector<StyleComponentValueRule>& dest) const; + template<typename Callback> void for_each_stylesheet(CascadeOrigin, Callback) const; |