mirror of
https://github.com/RGBCube/serenity
synced 2025-07-26 02:57:36 +00:00
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.
This commit is contained in:
parent
000fb5a70d
commit
23dc0dac88
6 changed files with 138 additions and 6 deletions
|
@ -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 {
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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; }
|
||||
|
|
|
@ -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() { }
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue