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();
}
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();
token_stream.discard_whitespace();
if (maybe_condition && !token_stream.has_next_token())
return Supports::create(realm(), maybe_condition.release_nonnull());
return Supports::create(maybe_condition.release_nonnull());
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()) {
transaction.commit();
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)
builder.append(item.to_string());
transaction.commit();
TokenStream selector_tokens { first_token.function().value };
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);
Vector<NonnullRefPtr<CSS::MediaQuery>> parse_media_query_list(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
*/
#include <LibJS/Runtime/Realm.h>
#include <LibWeb/CSS/Parser/Parser.h>
#include <LibWeb/CSS/Supports.h>
namespace Web::CSS {
@ -16,26 +15,26 @@ static void indent(StringBuilder& builder, int levels)
builder.append(" "sv);
}
Supports::Supports(JS::Realm& realm, NonnullOwnPtr<Condition>&& condition)
Supports::Supports(NonnullOwnPtr<Condition>&& 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) {
case Type::Not:
return !children.first().evaluate(realm);
return !children.first().evaluate();
case Type::And:
for (auto& child : children) {
if (!child.evaluate(realm))
if (!child.evaluate())
return false;
}
return true;
case Type::Or:
for (auto& child : children) {
if (child.evaluate(realm))
if (child.evaluate())
return true;
}
return false;
@ -43,40 +42,28 @@ bool Supports::Condition::evaluate(JS::Realm& realm) const
VERIFY_NOT_REACHED();
}
bool Supports::InParens::evaluate(JS::Realm& realm) const
bool Supports::InParens::evaluate() const
{
return value.visit(
[&](NonnullOwnPtr<Condition> const& condition) {
return condition->evaluate(realm);
return condition->evaluate();
},
[&](Feature const& feature) {
return feature.evaluate(realm);
return feature.evaluate();
},
[&](GeneralEnclosed const&) {
return false;
});
}
bool Supports::Declaration::evaluate(JS::Realm& realm) 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
bool Supports::Feature::evaluate() const
{
return value.visit(
[&](Declaration const& declaration) {
return declaration.evaluate(realm);
return declaration.evaluate();
},
[&](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
*/
@ -17,26 +17,26 @@ namespace Web::CSS {
// https://www.w3.org/TR/css-conditional-3/#at-supports
class Supports final : public RefCounted<Supports> {
friend class Parser::Parser;
public:
struct Declaration {
String declaration;
[[nodiscard]] bool evaluate(JS::Realm&) const;
bool matches;
[[nodiscard]] bool evaluate() const { return matches; }
String to_string() const;
void dump(StringBuilder&, int indent_levels = 0) const;
};
struct Selector {
String selector;
[[nodiscard]] bool evaluate(JS::Realm&) const;
bool matches;
[[nodiscard]] bool evaluate() const { return matches; }
String to_string() const;
void dump(StringBuilder&, int indent_levels = 0) const;
};
struct Feature {
Variant<Declaration, Selector> value;
[[nodiscard]] bool evaluate(JS::Realm&) const;
[[nodiscard]] bool evaluate() const;
String to_string() const;
void dump(StringBuilder&, int indent_levels = 0) const;
};
@ -45,7 +45,7 @@ public:
struct InParens {
Variant<NonnullOwnPtr<Condition>, Feature, GeneralEnclosed> value;
[[nodiscard]] bool evaluate(JS::Realm&) const;
[[nodiscard]] bool evaluate() const;
String to_string() const;
void dump(StringBuilder&, int indent_levels = 0) const;
};
@ -59,14 +59,14 @@ public:
Type type;
Vector<InParens> children;
[[nodiscard]] bool evaluate(JS::Realm&) const;
[[nodiscard]] bool evaluate() const;
String to_string() 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; }
@ -75,7 +75,7 @@ public:
void dump(StringBuilder&, int indent_levels = 0) const;
private:
Supports(JS::Realm&, NonnullOwnPtr<Condition>&&);
Supports(NonnullOwnPtr<Condition>&&);
NonnullOwnPtr<Condition> m_condition;
bool m_matches { false };