summaryrefslogtreecommitdiff
path: root/Userland/Libraries/LibWeb
diff options
context:
space:
mode:
authorSam Atkins <atkinssj@serenityos.org>2021-12-03 12:32:12 +0000
committerAndreas Kling <kling@serenityos.org>2021-12-09 21:30:31 +0100
commit23dc0dac88a248d4ac9b1be36212d065c7112629 (patch)
treef6757d6b2d52cc4770e49c37c520071a5c16e865 /Userland/Libraries/LibWeb
parent000fb5a70d0a7683d232cdec90884224931a12c2 (diff)
downloadserenity-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.cpp44
-rw-r--r--Userland/Libraries/LibWeb/CSS/Parser/Parser.h2
-rw-r--r--Userland/Libraries/LibWeb/CSS/Parser/StyleFunctionRule.h3
-rw-r--r--Userland/Libraries/LibWeb/CSS/Parser/StyleRules.cpp8
-rw-r--r--Userland/Libraries/LibWeb/CSS/StyleComputer.cpp83
-rw-r--r--Userland/Libraries/LibWeb/CSS/StyleComputer.h4
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;