LibWeb/CSS: Evaluate Supports query components during parsing

Instead of parsing the parts of a `@supports` query, then only
evaluating them when constructing the Supports itself, we can instead
evaluate them as we parse them. This simplifies things as we no longer
need to pass a Realm around, and don't have to re-parse the conditions
again with a new Parser instance.
This commit is contained in:
Sam Atkins 2025-03-13 16:04:48 +00:00
commit 84a695c958
Notes: github-actions[bot] 2025-03-17 10:01:49 +00:00
5 changed files with 32 additions and 48 deletions

View file

@ -87,11 +87,4 @@ RefPtr<CSS::Supports> parse_css_supports(CSS::Parser::ParsingParams const& conte
return CSS::Parser::Parser::create(context, string).parse_as_supports(); return CSS::Parser::Parser::create(context, string).parse_as_supports();
} }
Optional<CSS::StyleProperty> parse_css_supports_condition(CSS::Parser::ParsingParams const& context, StringView string)
{
if (string.is_empty())
return {};
return CSS::Parser::Parser::create(context, string).parse_as_supports_condition();
}
} }

View file

@ -154,7 +154,7 @@ RefPtr<Supports> Parser::parse_a_supports(TokenStream<T>& tokens)
m_rule_context.take_last(); m_rule_context.take_last();
token_stream.discard_whitespace(); token_stream.discard_whitespace();
if (maybe_condition && !token_stream.has_next_token()) if (maybe_condition && !token_stream.has_next_token())
return Supports::create(realm(), maybe_condition.release_nonnull()); return Supports::create(maybe_condition.release_nonnull());
return {}; return {};
} }
@ -279,7 +279,9 @@ Optional<Supports::Feature> Parser::parse_supports_feature(TokenStream<Component
if (auto declaration = consume_a_declaration(block_tokens); declaration.has_value()) { if (auto declaration = consume_a_declaration(block_tokens); declaration.has_value()) {
transaction.commit(); transaction.commit();
return Supports::Feature { return Supports::Feature {
Supports::Declaration { declaration->to_string() } Supports::Declaration {
declaration->to_string(),
convert_to_style_property(*declaration).has_value() }
}; };
} }
} }
@ -291,8 +293,11 @@ Optional<Supports::Feature> Parser::parse_supports_feature(TokenStream<Component
for (auto const& item : first_token.function().value) for (auto const& item : first_token.function().value)
builder.append(item.to_string()); builder.append(item.to_string());
transaction.commit(); transaction.commit();
TokenStream selector_tokens { first_token.function().value };
return Supports::Feature { return Supports::Feature {
Supports::Selector { builder.to_string().release_value_but_fixme_should_propagate_errors() } Supports::Selector {
builder.to_string_without_validation(),
!parse_a_selector_list(selector_tokens, SelectorType::Standalone).is_error() }
}; };
} }

View file

@ -518,6 +518,5 @@ CSS::CSSRule* parse_css_rule(CSS::Parser::ParsingParams const&, StringView);
RefPtr<CSS::MediaQuery> parse_media_query(CSS::Parser::ParsingParams const&, StringView); RefPtr<CSS::MediaQuery> parse_media_query(CSS::Parser::ParsingParams const&, StringView);
Vector<NonnullRefPtr<CSS::MediaQuery>> parse_media_query_list(CSS::Parser::ParsingParams const&, StringView); Vector<NonnullRefPtr<CSS::MediaQuery>> parse_media_query_list(CSS::Parser::ParsingParams const&, StringView);
RefPtr<CSS::Supports> parse_css_supports(CSS::Parser::ParsingParams const&, StringView); RefPtr<CSS::Supports> parse_css_supports(CSS::Parser::ParsingParams const&, StringView);
Optional<CSS::StyleProperty> parse_css_supports_condition(CSS::Parser::ParsingParams const&, StringView);
} }

View file

@ -1,11 +1,10 @@
/* /*
* Copyright (c) 2021-2023, Sam Atkins <atkinssj@serenityos.org> * Copyright (c) 2021-2025, Sam Atkins <sam@ladybird.org>
* *
* SPDX-License-Identifier: BSD-2-Clause * SPDX-License-Identifier: BSD-2-Clause
*/ */
#include <LibJS/Runtime/Realm.h> #include <LibJS/Runtime/Realm.h>
#include <LibWeb/CSS/Parser/Parser.h>
#include <LibWeb/CSS/Supports.h> #include <LibWeb/CSS/Supports.h>
namespace Web::CSS { namespace Web::CSS {
@ -16,26 +15,26 @@ static void indent(StringBuilder& builder, int levels)
builder.append(" "sv); builder.append(" "sv);
} }
Supports::Supports(JS::Realm& realm, NonnullOwnPtr<Condition>&& condition) Supports::Supports(NonnullOwnPtr<Condition>&& condition)
: m_condition(move(condition)) : m_condition(move(condition))
{ {
m_matches = m_condition->evaluate(realm); m_matches = m_condition->evaluate();
} }
bool Supports::Condition::evaluate(JS::Realm& realm) const bool Supports::Condition::evaluate() const
{ {
switch (type) { switch (type) {
case Type::Not: case Type::Not:
return !children.first().evaluate(realm); return !children.first().evaluate();
case Type::And: case Type::And:
for (auto& child : children) { for (auto& child : children) {
if (!child.evaluate(realm)) if (!child.evaluate())
return false; return false;
} }
return true; return true;
case Type::Or: case Type::Or:
for (auto& child : children) { for (auto& child : children) {
if (child.evaluate(realm)) if (child.evaluate())
return true; return true;
} }
return false; return false;
@ -43,40 +42,28 @@ bool Supports::Condition::evaluate(JS::Realm& realm) const
VERIFY_NOT_REACHED(); VERIFY_NOT_REACHED();
} }
bool Supports::InParens::evaluate(JS::Realm& realm) const bool Supports::InParens::evaluate() const
{ {
return value.visit( return value.visit(
[&](NonnullOwnPtr<Condition> const& condition) { [&](NonnullOwnPtr<Condition> const& condition) {
return condition->evaluate(realm); return condition->evaluate();
}, },
[&](Feature const& feature) { [&](Feature const& feature) {
return feature.evaluate(realm); return feature.evaluate();
}, },
[&](GeneralEnclosed const&) { [&](GeneralEnclosed const&) {
return false; return false;
}); });
} }
bool Supports::Declaration::evaluate(JS::Realm& realm) const bool Supports::Feature::evaluate() const
{
auto style_property = parse_css_supports_condition(Parser::ParsingParams { realm }, declaration);
return style_property.has_value();
}
bool Supports::Selector::evaluate(JS::Realm& realm) const
{
auto style_property = parse_selector(Parser::ParsingParams { realm }, selector);
return style_property.has_value();
}
bool Supports::Feature::evaluate(JS::Realm& realm) const
{ {
return value.visit( return value.visit(
[&](Declaration const& declaration) { [&](Declaration const& declaration) {
return declaration.evaluate(realm); return declaration.evaluate();
}, },
[&](Selector const& selector) { [&](Selector const& selector) {
return selector.evaluate(realm); return selector.evaluate();
}); });
} }

View file

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2021-2023, Sam Atkins <atkinssj@serenityos.org> * Copyright (c) 2021-2025, Sam Atkins <sam@ladybird.org>
* *
* SPDX-License-Identifier: BSD-2-Clause * SPDX-License-Identifier: BSD-2-Clause
*/ */
@ -17,26 +17,26 @@ namespace Web::CSS {
// https://www.w3.org/TR/css-conditional-3/#at-supports // https://www.w3.org/TR/css-conditional-3/#at-supports
class Supports final : public RefCounted<Supports> { class Supports final : public RefCounted<Supports> {
friend class Parser::Parser;
public: public:
struct Declaration { struct Declaration {
String declaration; String declaration;
[[nodiscard]] bool evaluate(JS::Realm&) const; bool matches;
[[nodiscard]] bool evaluate() const { return matches; }
String to_string() const; String to_string() const;
void dump(StringBuilder&, int indent_levels = 0) const; void dump(StringBuilder&, int indent_levels = 0) const;
}; };
struct Selector { struct Selector {
String selector; String selector;
[[nodiscard]] bool evaluate(JS::Realm&) const; bool matches;
[[nodiscard]] bool evaluate() const { return matches; }
String to_string() const; String to_string() const;
void dump(StringBuilder&, int indent_levels = 0) const; void dump(StringBuilder&, int indent_levels = 0) const;
}; };
struct Feature { struct Feature {
Variant<Declaration, Selector> value; Variant<Declaration, Selector> value;
[[nodiscard]] bool evaluate(JS::Realm&) const; [[nodiscard]] bool evaluate() const;
String to_string() const; String to_string() const;
void dump(StringBuilder&, int indent_levels = 0) const; void dump(StringBuilder&, int indent_levels = 0) const;
}; };
@ -45,7 +45,7 @@ public:
struct InParens { struct InParens {
Variant<NonnullOwnPtr<Condition>, Feature, GeneralEnclosed> value; Variant<NonnullOwnPtr<Condition>, Feature, GeneralEnclosed> value;
[[nodiscard]] bool evaluate(JS::Realm&) const; [[nodiscard]] bool evaluate() const;
String to_string() const; String to_string() const;
void dump(StringBuilder&, int indent_levels = 0) const; void dump(StringBuilder&, int indent_levels = 0) const;
}; };
@ -59,14 +59,14 @@ public:
Type type; Type type;
Vector<InParens> children; Vector<InParens> children;
[[nodiscard]] bool evaluate(JS::Realm&) const; [[nodiscard]] bool evaluate() const;
String to_string() const; String to_string() const;
void dump(StringBuilder&, int indent_levels = 0) const; void dump(StringBuilder&, int indent_levels = 0) const;
}; };
static NonnullRefPtr<Supports> create(JS::Realm& realm, NonnullOwnPtr<Condition>&& condition) static NonnullRefPtr<Supports> create(NonnullOwnPtr<Condition>&& condition)
{ {
return adopt_ref(*new Supports(realm, move(condition))); return adopt_ref(*new Supports(move(condition)));
} }
bool matches() const { return m_matches; } bool matches() const { return m_matches; }
@ -75,7 +75,7 @@ public:
void dump(StringBuilder&, int indent_levels = 0) const; void dump(StringBuilder&, int indent_levels = 0) const;
private: private:
Supports(JS::Realm&, NonnullOwnPtr<Condition>&&); Supports(NonnullOwnPtr<Condition>&&);
NonnullOwnPtr<Condition> m_condition; NonnullOwnPtr<Condition> m_condition;
bool m_matches { false }; bool m_matches { false };