diff --git a/Libraries/LibWeb/CMakeLists.txt b/Libraries/LibWeb/CMakeLists.txt index 1f7b520878b..ab4e1a6cc79 100644 --- a/Libraries/LibWeb/CMakeLists.txt +++ b/Libraries/LibWeb/CMakeLists.txt @@ -55,6 +55,7 @@ set(SOURCES Crypto/SubtleCrypto.cpp CSS/Angle.cpp CSS/AnimationEvent.cpp + CSS/BooleanExpression.cpp CSS/CalculatedOr.cpp CSS/Clip.cpp CSS/CountersSet.cpp diff --git a/Libraries/LibWeb/CSS/BooleanExpression.cpp b/Libraries/LibWeb/CSS/BooleanExpression.cpp new file mode 100644 index 00000000000..d8b73d9fe9c --- /dev/null +++ b/Libraries/LibWeb/CSS/BooleanExpression.cpp @@ -0,0 +1,136 @@ +/* + * Copyright (c) 2021-2025, Sam Atkins + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include +#include + +namespace Web::CSS { + +bool BooleanExpression::evaluate_to_boolean(HTML::Window const* window) const +{ + return evaluate(window) == MatchResult::True; +} + +void BooleanExpression::indent(StringBuilder& builder, int levels) +{ + builder.append_repeated(" "sv, levels); +} + +void GeneralEnclosed::dump(StringBuilder& builder, int indent_levels) const +{ + indent(builder, indent_levels); + builder.appendff("GeneralEnclosed: {}\n", to_string()); +} + +MatchResult BooleanNotExpression::evaluate(HTML::Window const* window) const +{ + // https://drafts.csswg.org/css-values-5/#boolean-logic + // `not test` evaluates to true if its contained test is false, false if it’s true, and unknown if it’s unknown. + switch (m_child->evaluate(window)) { + case MatchResult::False: + return MatchResult::True; + case MatchResult::True: + return MatchResult::False; + case MatchResult::Unknown: + return MatchResult::Unknown; + } + VERIFY_NOT_REACHED(); +} + +String BooleanNotExpression::to_string() const +{ + return MUST(String::formatted("not {}", m_child->to_string())); +} + +void BooleanNotExpression::dump(StringBuilder& builder, int indent_levels) const +{ + indent(builder, indent_levels); + builder.append("NOT:\n"sv); + m_child->dump(builder, indent_levels + 1); +} + +MatchResult BooleanExpressionInParens::evaluate(HTML::Window const* window) const +{ + return m_child->evaluate(window); +} + +String BooleanExpressionInParens::to_string() const +{ + return MUST(String::formatted("({})", m_child->to_string())); +} + +void BooleanExpressionInParens::dump(StringBuilder& builder, int indent_levels) const +{ + indent(builder, indent_levels); + builder.append("(\n"sv); + m_child->dump(builder, indent_levels + 1); + indent(builder, indent_levels); + builder.append(")\n"sv); +} + +MatchResult BooleanAndExpression::evaluate(HTML::Window const* window) const +{ + // https://drafts.csswg.org/css-values-5/#boolean-logic + // Multiple tests connected with `and` evaluate to true if all of those tests are true, false if any of them are + // false, and unknown otherwise (i.e. if at least one unknown, but no false). + size_t true_results = 0; + for (auto const& child : m_children) { + auto child_match = child->evaluate(window); + if (child_match == MatchResult::False) + return MatchResult::False; + if (child_match == MatchResult::True) + true_results++; + } + if (true_results == m_children.size()) + return MatchResult::True; + return MatchResult::Unknown; +} + +String BooleanAndExpression::to_string() const +{ + return MUST(String::join(" and "sv, m_children)); +} + +void BooleanAndExpression::dump(StringBuilder& builder, int indent_levels) const +{ + indent(builder, indent_levels); + builder.append("AND:\n"sv); + for (auto const& child : m_children) + child->dump(builder, indent_levels + 1); +} + +MatchResult BooleanOrExpression::evaluate(HTML::Window const* window) const +{ + // https://drafts.csswg.org/css-values-5/#boolean-logic + // Multiple tests connected with `or` evaluate to true if any of those tests are true, false if all of them are + // false, and unknown otherwise (i.e. at least one unknown, but no true). + size_t false_results = 0; + for (auto const& child : m_children) { + auto child_match = child->evaluate(window); + if (child_match == MatchResult::True) + return MatchResult::True; + if (child_match == MatchResult::False) + false_results++; + } + if (false_results == m_children.size()) + return MatchResult::False; + return MatchResult::Unknown; +} + +String BooleanOrExpression::to_string() const +{ + return MUST(String::join(" or "sv, m_children)); +} + +void BooleanOrExpression::dump(StringBuilder& builder, int indent_levels) const +{ + indent(builder, indent_levels); + builder.append("OR:\n"sv); + for (auto const& child : m_children) + child->dump(builder, indent_levels + 1); +} + +} diff --git a/Libraries/LibWeb/CSS/BooleanExpression.h b/Libraries/LibWeb/CSS/BooleanExpression.h new file mode 100644 index 00000000000..85b86ae032d --- /dev/null +++ b/Libraries/LibWeb/CSS/BooleanExpression.h @@ -0,0 +1,171 @@ +/* + * Copyright (c) 2021-2025, Sam Atkins + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include +#include +#include +#include + +namespace Web::CSS { + +// Corresponds to Kleene 3-valued logic. +enum class MatchResult { + False, + True, + Unknown, +}; + +inline MatchResult as_match_result(bool value) +{ + return value ? MatchResult::True : MatchResult::False; +} + +inline MatchResult negate(MatchResult value) +{ + switch (value) { + case MatchResult::False: + return MatchResult::True; + case MatchResult::True: + return MatchResult::False; + case MatchResult::Unknown: + return MatchResult::Unknown; + } + VERIFY_NOT_REACHED(); +} + +// The contents of this file implement the `` concept. +// https://drafts.csswg.org/css-values-5/#typedef-boolean-expr +class BooleanExpression { +public: + virtual ~BooleanExpression() = default; + + bool evaluate_to_boolean(HTML::Window const*) const; + static void indent(StringBuilder& builder, int levels); + + virtual MatchResult evaluate(HTML::Window const*) const = 0; + virtual String to_string() const = 0; + virtual void dump(StringBuilder&, int indent_levels = 0) const = 0; +}; + +// https://www.w3.org/TR/mediaqueries-4/#typedef-general-enclosed +class GeneralEnclosed final : public BooleanExpression { +public: + static NonnullOwnPtr create(String serialized_contents, MatchResult matches = MatchResult::Unknown) + { + return adopt_own(*new GeneralEnclosed(move(serialized_contents), matches)); + } + virtual ~GeneralEnclosed() override = default; + + virtual MatchResult evaluate(HTML::Window const*) const override { return m_matches; } + virtual String to_string() const override { return m_serialized_contents; } + virtual void dump(StringBuilder&, int indent_levels = 0) const override; + +private: + GeneralEnclosed(String serialized_contents, MatchResult matches) + : m_serialized_contents(move(serialized_contents)) + , m_matches(matches) + { + } + + String m_serialized_contents; + MatchResult m_matches; +}; + +class BooleanNotExpression final : public BooleanExpression { +public: + static NonnullOwnPtr create(NonnullOwnPtr&& child) + { + return adopt_own(*new BooleanNotExpression(move(child))); + } + virtual ~BooleanNotExpression() override = default; + + virtual MatchResult evaluate(HTML::Window const*) const override; + virtual String to_string() const override; + virtual void dump(StringBuilder&, int indent_levels = 0) const override; + +private: + BooleanNotExpression(NonnullOwnPtr&& child) + : m_child(move(child)) + { + } + + NonnullOwnPtr m_child; +}; + +class BooleanExpressionInParens final : public BooleanExpression { +public: + static NonnullOwnPtr create(NonnullOwnPtr&& child) + { + return adopt_own(*new BooleanExpressionInParens(move(child))); + } + virtual ~BooleanExpressionInParens() override = default; + + virtual MatchResult evaluate(HTML::Window const*) const override; + virtual String to_string() const override; + virtual void dump(StringBuilder&, int indent_levels = 0) const override; + +private: + BooleanExpressionInParens(NonnullOwnPtr&& child) + : m_child(move(child)) + { + } + + NonnullOwnPtr m_child; +}; + +class BooleanAndExpression final : public BooleanExpression { +public: + static NonnullOwnPtr create(Vector>&& children) + { + return adopt_own(*new BooleanAndExpression(move(children))); + } + virtual ~BooleanAndExpression() override = default; + + virtual MatchResult evaluate(HTML::Window const*) const override; + virtual String to_string() const override; + virtual void dump(StringBuilder&, int indent_levels = 0) const override; + +private: + BooleanAndExpression(Vector>&& children) + : m_children(move(children)) + { + } + + Vector> m_children; +}; + +class BooleanOrExpression final : public BooleanExpression { +public: + static NonnullOwnPtr create(Vector>&& children) + { + return adopt_own(*new BooleanOrExpression(move(children))); + } + virtual ~BooleanOrExpression() override = default; + + virtual MatchResult evaluate(HTML::Window const*) const override; + virtual String to_string() const override; + virtual void dump(StringBuilder&, int indent_levels = 0) const override; + +private: + BooleanOrExpression(Vector>&& children) + : m_children(move(children)) + { + } + + Vector> m_children; +}; + +} + +template<> +struct AK::Formatter : AK::Formatter { + ErrorOr format(FormatBuilder& builder, Web::CSS::BooleanExpression const& expression) + { + return Formatter::format(builder, expression.to_string()); + } +}; diff --git a/Libraries/LibWeb/CSS/GeneralEnclosed.h b/Libraries/LibWeb/CSS/GeneralEnclosed.h deleted file mode 100644 index 71e3c6eab16..00000000000 --- a/Libraries/LibWeb/CSS/GeneralEnclosed.h +++ /dev/null @@ -1,85 +0,0 @@ -/* - * Copyright (c) 2021, Sam Atkins - * - * SPDX-License-Identifier: BSD-2-Clause - */ - -#pragma once - -#include - -namespace Web::CSS { - -// Corresponds to Kleene 3-valued logic. -// https://www.w3.org/TR/mediaqueries-4/#evaluating -enum class MatchResult { - False, - True, - Unknown, -}; - -inline MatchResult as_match_result(bool value) -{ - return value ? MatchResult::True : MatchResult::False; -} - -inline MatchResult negate(MatchResult value) -{ - switch (value) { - case MatchResult::False: - return MatchResult::True; - case MatchResult::True: - return MatchResult::False; - case MatchResult::Unknown: - return MatchResult::Unknown; - } - VERIFY_NOT_REACHED(); -} - -template -inline MatchResult evaluate_and(Collection& collection, Evaluate evaluate) -{ - size_t true_results = 0; - for (auto& item : collection) { - auto item_match = evaluate(item); - if (item_match == MatchResult::False) - return MatchResult::False; - if (item_match == MatchResult::True) - true_results++; - } - if (true_results == collection.size()) - return MatchResult::True; - return MatchResult::Unknown; -} - -template -inline MatchResult evaluate_or(Collection& collection, Evaluate evaluate) -{ - size_t false_results = 0; - for (auto& item : collection) { - auto item_match = evaluate(item); - if (item_match == MatchResult::True) - return MatchResult::True; - if (item_match == MatchResult::False) - false_results++; - } - if (false_results == collection.size()) - return MatchResult::False; - return MatchResult::Unknown; -} - -// https://www.w3.org/TR/mediaqueries-4/#typedef-general-enclosed -class GeneralEnclosed { -public: - GeneralEnclosed(String serialized_contents) - : m_serialized_contents(move(serialized_contents)) - { - } - - MatchResult evaluate() const { return MatchResult::Unknown; } - String const& to_string() const { return m_serialized_contents; } - -private: - String m_serialized_contents; -}; -} diff --git a/Libraries/LibWeb/CSS/MediaQuery.cpp b/Libraries/LibWeb/CSS/MediaQuery.cpp index bd0b957cd9a..1b36971a34e 100644 --- a/Libraries/LibWeb/CSS/MediaQuery.cpp +++ b/Libraries/LibWeb/CSS/MediaQuery.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021-2023, Sam Atkins + * Copyright (c) 2021-2025, Sam Atkins * * SPDX-License-Identifier: BSD-2-Clause */ @@ -83,57 +83,58 @@ String MediaFeature::to_string() const VERIFY_NOT_REACHED(); } -bool MediaFeature::evaluate(HTML::Window const& window) const +MatchResult MediaFeature::evaluate(HTML::Window const* window) const { - auto maybe_queried_value = window.query_media_feature(m_id); + VERIFY(window); + auto maybe_queried_value = window->query_media_feature(m_id); if (!maybe_queried_value.has_value()) - return false; + return MatchResult::False; auto queried_value = maybe_queried_value.release_value(); CalculationResolutionContext calculation_context { - .length_resolution_context = Length::ResolutionContext::for_window(window), + .length_resolution_context = Length::ResolutionContext::for_window(*window), }; switch (m_type) { case Type::IsTrue: if (queried_value.is_integer()) - return queried_value.integer().resolved(calculation_context) != 0; + return as_match_result(queried_value.integer().resolved(calculation_context) != 0); if (queried_value.is_length()) { auto length = queried_value.length().resolved(calculation_context); - return length->raw_value() != 0; + return as_match_result(length->raw_value() != 0); } // FIXME: I couldn't figure out from the spec how ratios should be evaluated in a boolean context. if (queried_value.is_ratio()) - return !queried_value.ratio().is_degenerate(); + return as_match_result(!queried_value.ratio().is_degenerate()); if (queried_value.is_resolution()) - return queried_value.resolution().resolved(calculation_context).map([](auto& it) { return it.to_dots_per_pixel(); }).value_or(0) != 0; + return as_match_result(queried_value.resolution().resolved(calculation_context).map([](auto& it) { return it.to_dots_per_pixel(); }).value_or(0) != 0); if (queried_value.is_ident()) { // NOTE: It is not technically correct to always treat `no-preference` as false, but every // media-feature that accepts it as a value treats it as false, so good enough. :^) // If other features gain this property for other keywords in the future, we can // add more robust handling for them then. - return queried_value.ident() != Keyword::None - && queried_value.ident() != Keyword::NoPreference; + return as_match_result(queried_value.ident() != Keyword::None + && queried_value.ident() != Keyword::NoPreference); } - return false; + return MatchResult::False; case Type::ExactValue: - return compare(window, *m_value, Comparison::Equal, queried_value); + return as_match_result(compare(*window, *m_value, Comparison::Equal, queried_value)); case Type::MinValue: - return compare(window, queried_value, Comparison::GreaterThanOrEqual, *m_value); + return as_match_result(compare(*window, queried_value, Comparison::GreaterThanOrEqual, *m_value)); case Type::MaxValue: - return compare(window, queried_value, Comparison::LessThanOrEqual, *m_value); + return as_match_result(compare(*window, queried_value, Comparison::LessThanOrEqual, *m_value)); case Type::Range: - if (!compare(window, m_range->left_value, m_range->left_comparison, queried_value)) - return false; + if (!compare(*window, m_range->left_value, m_range->left_comparison, queried_value)) + return MatchResult::False; if (m_range->right_comparison.has_value()) - if (!compare(window, queried_value, *m_range->right_comparison, *m_range->right_value)) - return false; + if (!compare(*window, queried_value, *m_range->right_comparison, *m_range->right_value)) + return MatchResult::False; - return true; + return MatchResult::True; } VERIFY_NOT_REACHED(); @@ -247,92 +248,10 @@ bool MediaFeature::compare(HTML::Window const& window, MediaFeatureValue left, C VERIFY_NOT_REACHED(); } -NonnullOwnPtr MediaCondition::from_general_enclosed(GeneralEnclosed&& general_enclosed) +void MediaFeature::dump(StringBuilder& builder, int indent_levels) const { - auto result = new MediaCondition; - result->type = Type::GeneralEnclosed; - result->general_enclosed = move(general_enclosed); - - return adopt_own(*result); -} - -NonnullOwnPtr MediaCondition::from_feature(MediaFeature&& feature) -{ - auto result = new MediaCondition; - result->type = Type::Single; - result->feature = move(feature); - - return adopt_own(*result); -} - -NonnullOwnPtr MediaCondition::from_not(NonnullOwnPtr&& condition) -{ - auto result = new MediaCondition; - result->type = Type::Not; - result->conditions.append(move(condition)); - - return adopt_own(*result); -} - -NonnullOwnPtr MediaCondition::from_and_list(Vector>&& conditions) -{ - auto result = new MediaCondition; - result->type = Type::And; - result->conditions = move(conditions); - - return adopt_own(*result); -} - -NonnullOwnPtr MediaCondition::from_or_list(Vector>&& conditions) -{ - auto result = new MediaCondition; - result->type = Type::Or; - result->conditions = move(conditions); - - return adopt_own(*result); -} - -String MediaCondition::to_string() const -{ - StringBuilder builder; - builder.append('('); - switch (type) { - case Type::Single: - builder.append(feature->to_string()); - break; - case Type::Not: - builder.append("not "sv); - builder.append(conditions.first()->to_string()); - break; - case Type::And: - builder.join(" and "sv, conditions); - break; - case Type::Or: - builder.join(" or "sv, conditions); - break; - case Type::GeneralEnclosed: - builder.append(general_enclosed->to_string()); - break; - } - builder.append(')'); - return MUST(builder.to_string()); -} - -MatchResult MediaCondition::evaluate(HTML::Window const& window) const -{ - switch (type) { - case Type::Single: - return as_match_result(feature->evaluate(window)); - case Type::Not: - return negate(conditions.first()->evaluate(window)); - case Type::And: - return evaluate_and(conditions, [&](auto& child) { return child->evaluate(window); }); - case Type::Or: - return evaluate_or(conditions, [&](auto& child) { return child->evaluate(window); }); - case Type::GeneralEnclosed: - return general_enclosed->evaluate(); - } - VERIFY_NOT_REACHED(); + indent(builder, indent_levels); + builder.appendff("MediaFeature: {}", to_string()); } String MediaQuery::to_string() const @@ -386,7 +305,7 @@ bool MediaQuery::evaluate(HTML::Window const& window) MatchResult result = matches_media(m_media_type); if ((result == MatchResult::True) && m_media_condition) - result = m_media_condition->evaluate(window); + result = m_media_condition->evaluate(&window); if (m_negated) result = negate(result); diff --git a/Libraries/LibWeb/CSS/MediaQuery.h b/Libraries/LibWeb/CSS/MediaQuery.h index 72b5f0a8949..0e2e84e43b1 100644 --- a/Libraries/LibWeb/CSS/MediaQuery.h +++ b/Libraries/LibWeb/CSS/MediaQuery.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021-2023, Sam Atkins + * Copyright (c) 2021-2025, Sam Atkins * * SPDX-License-Identifier: BSD-2-Clause */ @@ -10,8 +10,8 @@ #include #include #include +#include #include -#include #include #include @@ -94,7 +94,7 @@ private: }; // https://www.w3.org/TR/mediaqueries-4/#mq-features -class MediaFeature { +class MediaFeature final : public BooleanExpression { public: enum class Comparison { Equal, @@ -105,51 +105,52 @@ public: }; // Corresponds to `` grammar - static MediaFeature boolean(MediaFeatureID id) + static NonnullOwnPtr boolean(MediaFeatureID id) { - return MediaFeature(Type::IsTrue, id); + return adopt_own(*new MediaFeature(Type::IsTrue, id)); } // Corresponds to `` grammar - static MediaFeature plain(MediaFeatureID id, MediaFeatureValue value) + static NonnullOwnPtr plain(MediaFeatureID id, MediaFeatureValue value) { - return MediaFeature(Type::ExactValue, move(id), move(value)); + return adopt_own(*new MediaFeature(Type::ExactValue, move(id), move(value))); } - static MediaFeature min(MediaFeatureID id, MediaFeatureValue value) + static NonnullOwnPtr min(MediaFeatureID id, MediaFeatureValue value) { - return MediaFeature(Type::MinValue, id, move(value)); + return adopt_own(*new MediaFeature(Type::MinValue, id, move(value))); } - static MediaFeature max(MediaFeatureID id, MediaFeatureValue value) + static NonnullOwnPtr max(MediaFeatureID id, MediaFeatureValue value) { - return MediaFeature(Type::MaxValue, id, move(value)); + return adopt_own(*new MediaFeature(Type::MaxValue, id, move(value))); } // Corresponds to `` grammar, with a single comparison - static MediaFeature half_range(MediaFeatureValue value, Comparison comparison, MediaFeatureID id) + static NonnullOwnPtr half_range(MediaFeatureValue value, Comparison comparison, MediaFeatureID id) { - MediaFeature feature { Type::Range, id }; - feature.m_range = Range { - .left_value = value, + auto feature = adopt_own(*new MediaFeature(Type::Range, id)); + feature->m_range = Range { + .left_value = move(value), .left_comparison = comparison, }; return feature; } // Corresponds to `` grammar, with two comparisons - static MediaFeature range(MediaFeatureValue left_value, Comparison left_comparison, MediaFeatureID id, Comparison right_comparison, MediaFeatureValue right_value) + static NonnullOwnPtr range(MediaFeatureValue left_value, Comparison left_comparison, MediaFeatureID id, Comparison right_comparison, MediaFeatureValue right_value) { - MediaFeature feature { Type::Range, id }; - feature.m_range = Range { - .left_value = left_value, + auto feature = adopt_own(*new MediaFeature(Type::Range, id)); + feature->m_range = Range { + .left_value = move(left_value), .left_comparison = left_comparison, .right_comparison = right_comparison, - .right_value = right_value, + .right_value = move(right_value), }; return feature; } - bool evaluate(HTML::Window const&) const; - String to_string() const; + virtual MatchResult evaluate(HTML::Window const*) const override; + virtual String to_string() const override; + virtual void dump(StringBuilder&, int indent_levels = 0) const override; private: enum class Type { @@ -182,39 +183,6 @@ private: Optional m_range {}; }; -// https://www.w3.org/TR/mediaqueries-4/#media-conditions -struct MediaCondition { - enum class Type { - Single, - And, - Or, - Not, - GeneralEnclosed, - }; - - // Only used in parsing - enum class AllowOr { - No = 0, - Yes = 1, - }; - - static NonnullOwnPtr from_general_enclosed(GeneralEnclosed&&); - static NonnullOwnPtr from_feature(MediaFeature&&); - static NonnullOwnPtr from_not(NonnullOwnPtr&&); - static NonnullOwnPtr from_and_list(Vector>&&); - static NonnullOwnPtr from_or_list(Vector>&&); - - MatchResult evaluate(HTML::Window const&) const; - String to_string() const; - -private: - MediaCondition() = default; - Type type; - Optional feature; - Vector> conditions; - Optional general_enclosed; -}; - class MediaQuery : public RefCounted { friend class Parser::Parser; @@ -252,7 +220,7 @@ private: // https://www.w3.org/TR/mediaqueries-4/#mq-not bool m_negated { false }; MediaType m_media_type { MediaType::All }; - OwnPtr m_media_condition { nullptr }; + OwnPtr m_media_condition { nullptr }; // Cached value, updated by evaluate() bool m_matches { false }; @@ -275,14 +243,6 @@ struct Formatter : Formatter { } }; -template<> -struct Formatter : Formatter { - ErrorOr format(FormatBuilder& builder, Web::CSS::MediaCondition const& media_condition) - { - return Formatter::format(builder, media_condition.to_string()); - } -}; - template<> struct Formatter : Formatter { ErrorOr format(FormatBuilder& builder, Web::CSS::MediaQuery const& media_query) diff --git a/Libraries/LibWeb/CSS/Parser/MediaParsing.cpp b/Libraries/LibWeb/CSS/Parser/MediaParsing.cpp index 3a017d7768d..579e83ad564 100644 --- a/Libraries/LibWeb/CSS/Parser/MediaParsing.cpp +++ b/Libraries/LibWeb/CSS/Parser/MediaParsing.cpp @@ -96,11 +96,11 @@ NonnullRefPtr Parser::parse_media_query(TokenStream& tokens.discard_whitespace(); // `` - if (auto media_condition = parse_media_condition(tokens, MediaCondition::AllowOr::Yes)) { + if (auto media_condition = parse_media_condition(tokens)) { tokens.discard_whitespace(); if (tokens.has_next_token()) return invalid_media_query(); - media_query->m_media_condition = move(media_condition); + media_query->m_media_condition = media_condition.release_nonnull(); return media_query; } @@ -127,7 +127,11 @@ NonnullRefPtr Parser::parse_media_query(TokenStream& // `[ and ]?` if (auto const& maybe_and = tokens.consume_a_token(); maybe_and.is_ident("and"sv)) { - if (auto media_condition = parse_media_condition(tokens, MediaCondition::AllowOr::No)) { + if (auto media_condition = parse_media_condition(tokens)) { + // "or" is disallowed at the top level + if (is(*media_condition)) + return invalid_media_query(); + tokens.discard_whitespace(); if (tokens.has_next_token()) return invalid_media_query(); @@ -141,115 +145,15 @@ NonnullRefPtr Parser::parse_media_query(TokenStream& } // ``, https://www.w3.org/TR/mediaqueries-4/#typedef-media-condition -// ``, https://www.w3.org/TR/mediaqueries-4/#typedef-media-condition-without-or -// (We distinguish between these two with the `allow_or` parameter.) -OwnPtr Parser::parse_media_condition(TokenStream& tokens, MediaCondition::AllowOr allow_or) +OwnPtr Parser::parse_media_condition(TokenStream& tokens) { - // ` | [ * | * ]` - auto transaction = tokens.begin_transaction(); - tokens.discard_whitespace(); - - // ` = not ` - auto parse_media_not = [&](auto& tokens) -> OwnPtr { - auto local_transaction = tokens.begin_transaction(); - tokens.discard_whitespace(); - - auto& first_token = tokens.consume_a_token(); - if (first_token.is_ident("not"sv)) { - if (auto child_condition = parse_media_condition(tokens, MediaCondition::AllowOr::Yes)) { - local_transaction.commit(); - return MediaCondition::from_not(child_condition.release_nonnull()); - } - } - - return {}; - }; - - auto parse_media_with_combinator = [&](auto& tokens, StringView combinator) -> OwnPtr { - auto local_transaction = tokens.begin_transaction(); - tokens.discard_whitespace(); - - auto& first = tokens.consume_a_token(); - if (first.is_ident(combinator)) { - tokens.discard_whitespace(); - if (auto media_in_parens = parse_media_in_parens(tokens)) { - local_transaction.commit(); - return media_in_parens; - } - } - - return {}; - }; - - // ` = and ` - auto parse_media_and = [&](auto& tokens) { return parse_media_with_combinator(tokens, "and"sv); }; - // ` = or ` - auto parse_media_or = [&](auto& tokens) { return parse_media_with_combinator(tokens, "or"sv); }; - - // `` - if (auto maybe_media_not = parse_media_not(tokens)) { - transaction.commit(); - return maybe_media_not.release_nonnull(); - } - - // ` [ * | * ]` - if (auto maybe_media_in_parens = parse_media_in_parens(tokens)) { - tokens.discard_whitespace(); - // Only `` - if (!tokens.has_next_token()) { - transaction.commit(); - return maybe_media_in_parens.release_nonnull(); - } - - Vector> child_conditions; - child_conditions.append(maybe_media_in_parens.release_nonnull()); - - // `*` - if (auto media_and = parse_media_and(tokens)) { - child_conditions.append(media_and.release_nonnull()); - - tokens.discard_whitespace(); - while (tokens.has_next_token()) { - if (auto next_media_and = parse_media_and(tokens)) { - child_conditions.append(next_media_and.release_nonnull()); - tokens.discard_whitespace(); - continue; - } - // We failed - invalid syntax! - return {}; - } - - transaction.commit(); - return MediaCondition::from_and_list(move(child_conditions)); - } - - // `*` - if (allow_or == MediaCondition::AllowOr::Yes) { - if (auto media_or = parse_media_or(tokens)) { - child_conditions.append(media_or.release_nonnull()); - - tokens.discard_whitespace(); - while (tokens.has_next_token()) { - if (auto next_media_or = parse_media_or(tokens)) { - child_conditions.append(next_media_or.release_nonnull()); - tokens.discard_whitespace(); - continue; - } - // We failed - invalid syntax! - return {}; - } - - transaction.commit(); - return MediaCondition::from_or_list(move(child_conditions)); - } - } - } - - return {}; + return parse_boolean_expression(tokens, MatchResult::Unknown, [this](TokenStream& tokens) -> OwnPtr { + return parse_media_feature(tokens); + }); } // ``, https://www.w3.org/TR/mediaqueries-4/#typedef-media-feature -Optional Parser::parse_media_feature(TokenStream& tokens) +OwnPtr Parser::parse_media_feature(TokenStream& tokens) { // `[ | | ]` tokens.discard_whitespace(); @@ -288,7 +192,7 @@ Optional Parser::parse_media_feature(TokenStream& }; // ` = ` - auto parse_mf_boolean = [&](auto& tokens) -> Optional { + auto parse_mf_boolean = [&](auto& tokens) -> OwnPtr { auto transaction = tokens.begin_transaction(); tokens.discard_whitespace(); @@ -304,7 +208,7 @@ Optional Parser::parse_media_feature(TokenStream& }; // ` = : ` - auto parse_mf_plain = [&](auto& tokens) -> Optional { + auto parse_mf_plain = [&](auto& tokens) -> OwnPtr { auto transaction = tokens.begin_transaction(); tokens.discard_whitespace(); @@ -406,7 +310,7 @@ Optional Parser::parse_media_feature(TokenStream& // | // | // | ` - auto parse_mf_range = [&](auto& tokens) -> Optional { + auto parse_mf_range = [&](auto& tokens) -> OwnPtr { auto transaction = tokens.begin_transaction(); tokens.discard_whitespace(); @@ -490,14 +394,14 @@ Optional Parser::parse_media_feature(TokenStream& return {}; }; - if (auto maybe_mf_boolean = parse_mf_boolean(tokens); maybe_mf_boolean.has_value()) - return maybe_mf_boolean.release_value(); + if (auto maybe_mf_boolean = parse_mf_boolean(tokens)) + return maybe_mf_boolean.release_nonnull(); - if (auto maybe_mf_plain = parse_mf_plain(tokens); maybe_mf_plain.has_value()) - return maybe_mf_plain.release_value(); + if (auto maybe_mf_plain = parse_mf_plain(tokens)) + return maybe_mf_plain.release_nonnull(); - if (auto maybe_mf_range = parse_mf_range(tokens); maybe_mf_range.has_value()) - return maybe_mf_range.release_value(); + if (auto maybe_mf_range = parse_mf_range(tokens)) + return maybe_mf_range.release_nonnull(); return {}; } @@ -517,41 +421,6 @@ Optional Parser::parse_media_type(TokenStream`, https://www.w3.org/TR/mediaqueries-4/#typedef-media-in-parens -OwnPtr Parser::parse_media_in_parens(TokenStream& tokens) -{ - // ` = ( ) | ( ) | ` - auto transaction = tokens.begin_transaction(); - tokens.discard_whitespace(); - - // `( ) | ( )` - auto const& first_token = tokens.next_token(); - if (first_token.is_block() && first_token.block().is_paren()) { - TokenStream inner_token_stream { first_token.block().value }; - if (auto maybe_media_condition = parse_media_condition(inner_token_stream, MediaCondition::AllowOr::Yes)) { - tokens.discard_a_token(); - transaction.commit(); - return maybe_media_condition.release_nonnull(); - } - if (auto maybe_media_feature = parse_media_feature(inner_token_stream); maybe_media_feature.has_value()) { - tokens.discard_a_token(); - transaction.commit(); - return MediaCondition::from_feature(maybe_media_feature.release_value()); - } - } - - // `` - // FIXME: We should only be taking this branch if the grammar doesn't match the above options. - // Currently we take it if the above fail to parse, which is different. - // eg, `@media (min-width: 76yaks)` is valid grammar, but does not parse because `yaks` isn't a unit. - if (auto maybe_general_enclosed = parse_general_enclosed(tokens); maybe_general_enclosed.has_value()) { - transaction.commit(); - return MediaCondition::from_general_enclosed(maybe_general_enclosed.release_value()); - } - - return {}; -} - // ``, https://www.w3.org/TR/mediaqueries-4/#typedef-mf-value Optional Parser::parse_media_feature_value(MediaFeatureID media_feature, TokenStream& tokens) { diff --git a/Libraries/LibWeb/CSS/Parser/Parser.cpp b/Libraries/LibWeb/CSS/Parser/Parser.cpp index aee37269670..dd095b919c2 100644 --- a/Libraries/LibWeb/CSS/Parser/Parser.cpp +++ b/Libraries/LibWeb/CSS/Parser/Parser.cpp @@ -150,7 +150,7 @@ RefPtr Parser::parse_a_supports(TokenStream& tokens) auto component_values = parse_a_list_of_component_values(tokens); TokenStream token_stream { component_values }; m_rule_context.append(ContextType::SupportsCondition); - auto maybe_condition = parse_supports_condition(token_stream); + auto maybe_condition = parse_boolean_expression(token_stream, MatchResult::False, [this](auto& tokens) { return parse_supports_feature(tokens); }); m_rule_context.take_last(); token_stream.discard_whitespace(); if (maybe_condition && !token_stream.has_next_token()) @@ -159,59 +159,66 @@ RefPtr Parser::parse_a_supports(TokenStream& tokens) return {}; } -OwnPtr Parser::parse_supports_condition(TokenStream& tokens) +// https://drafts.csswg.org/css-values-5/#typedef-boolean-expr +OwnPtr Parser::parse_boolean_expression(TokenStream& tokens, MatchResult result_for_general_enclosed, ParseTest parse_test) { + // ]> = not | + // [ [ and ]* + // | [ or ]* ] + auto transaction = tokens.begin_transaction(); tokens.discard_whitespace(); auto const& peeked_token = tokens.next_token(); - // `not ` + // `not ` if (peeked_token.is_ident("not"sv)) { tokens.discard_a_token(); tokens.discard_whitespace(); - auto child = parse_supports_in_parens(tokens); - if (!child.has_value()) - return {}; - transaction.commit(); - auto condition = make(); - condition->type = Supports::Condition::Type::Not; - condition->children.append(child.release_value()); - return condition; + if (auto child = parse_boolean_expression_group(tokens, result_for_general_enclosed, parse_test)) { + transaction.commit(); + return BooleanNotExpression::create(child.release_nonnull()); + } + return {}; } - // ` [ and ]* - // | [ or ]*` - Vector children; - Optional condition_type {}; - auto as_condition_type = [](auto& token) -> Optional { + // ` + // [ [ and ]* + // | [ or ]* ]` + Vector> children; + enum class Combinator : u8 { + And, + Or, + }; + Optional combinator; + auto as_combinator = [](auto& token) -> Optional { if (!token.is(Token::Type::Ident)) return {}; auto ident = token.token().ident(); if (ident.equals_ignoring_ascii_case("and"sv)) - return Supports::Condition::Type::And; + return Combinator::And; if (ident.equals_ignoring_ascii_case("or"sv)) - return Supports::Condition::Type::Or; + return Combinator::Or; return {}; }; while (tokens.has_next_token()) { if (!children.is_empty()) { // Expect `and` or `or` here - auto maybe_combination = as_condition_type(tokens.consume_a_token()); - if (!maybe_combination.has_value()) + auto maybe_combinator = as_combinator(tokens.consume_a_token()); + if (!maybe_combinator.has_value()) return {}; - if (!condition_type.has_value()) { - condition_type = maybe_combination.value(); - } else if (maybe_combination != condition_type) { + if (!combinator.has_value()) { + combinator = maybe_combinator.value(); + } else if (maybe_combinator != combinator) { return {}; } } tokens.discard_whitespace(); - if (auto in_parens = parse_supports_in_parens(tokens); in_parens.has_value()) { - children.append(in_parens.release_value()); + if (auto child = parse_boolean_expression_group(tokens, result_for_general_enclosed, parse_test)) { + children.append(child.release_nonnull()); } else { return {}; } @@ -223,15 +230,24 @@ OwnPtr Parser::parse_supports_condition(TokenStream(); - condition->type = condition_type.value_or(Supports::Condition::Type::Or); - condition->children = move(children); - return condition; + if (children.size() == 1) + return children.take_first(); + + VERIFY(combinator.has_value()); + switch (*combinator) { + case Combinator::And: + return BooleanAndExpression::create(move(children)); + case Combinator::Or: + return BooleanOrExpression::create(move(children)); + } + VERIFY_NOT_REACHED(); } -Optional Parser::parse_supports_in_parens(TokenStream& tokens) +OwnPtr Parser::parse_boolean_expression_group(TokenStream& tokens, MatchResult result_for_general_enclosed, ParseTest parse_test) { - // `( )` + // = | ( ]> ) | + + // `( ]> )` auto const& first_token = tokens.next_token(); if (first_token.is_block() && first_token.block().is_paren()) { auto transaction = tokens.begin_transaction(); @@ -239,54 +255,46 @@ Optional Parser::parse_supports_in_parens(TokenStream` - if (auto feature = parse_supports_feature(tokens); feature.has_value()) { - return Supports::InParens { - .value = { feature.release_value() } - }; - } + // `` + if (auto test = parse_test(tokens)) + return test.release_nonnull(); // `` - if (auto general_enclosed = parse_general_enclosed(tokens); general_enclosed.has_value()) { - return Supports::InParens { - .value = general_enclosed.release_value() - }; - } + if (auto general_enclosed = parse_general_enclosed(tokens, result_for_general_enclosed)) + return general_enclosed.release_nonnull(); return {}; } -Optional Parser::parse_supports_feature(TokenStream& tokens) +// https://drafts.csswg.org/css-conditional-4/#typedef-supports-feature +OwnPtr Parser::parse_supports_feature(TokenStream& tokens) { + // = | auto transaction = tokens.begin_transaction(); tokens.discard_whitespace(); auto const& first_token = tokens.consume_a_token(); - // `` + // ` = ( )` if (first_token.is_block() && first_token.block().is_paren()) { TokenStream block_tokens { first_token.block().value }; // FIXME: Parsing and then converting back to a string is weird. if (auto declaration = consume_a_declaration(block_tokens); declaration.has_value()) { transaction.commit(); - return Supports::Feature { - Supports::Declaration { - declaration->to_string(), - convert_to_style_property(*declaration).has_value() } - }; + return Supports::Declaration::create( + declaration->to_string(), + convert_to_style_property(*declaration).has_value()); } } - // `` + // ` = selector( )` if (first_token.is_function("selector"sv)) { // FIXME: Parsing and then converting back to a string is weird. StringBuilder builder; @@ -294,19 +302,18 @@ Optional Parser::parse_supports_feature(TokenStream Parser::parse_general_enclosed(TokenStream& tokens) +OwnPtr Parser::parse_general_enclosed(TokenStream& tokens, MatchResult result) { + // FIXME: syntax changed in MediaQueries-5 auto transaction = tokens.begin_transaction(); tokens.discard_whitespace(); auto const& first_token = tokens.consume_a_token(); @@ -314,13 +321,13 @@ Optional Parser::parse_general_enclosed(TokenStream ? ) ]` if (first_token.is_function()) { transaction.commit(); - return GeneralEnclosed { first_token.to_string() }; + return GeneralEnclosed::create(first_token.to_string(), result); } // `( ? )` if (first_token.is_block() && first_token.block().is_paren()) { transaction.commit(); - return GeneralEnclosed { first_token.to_string() }; + return GeneralEnclosed::create(first_token.to_string(), result); } return {}; @@ -1641,10 +1648,10 @@ LengthOrCalculated Parser::parse_as_sizes_attribute(DOM::Element const& element, // 5. Parse the remaining component values in unparsed size as a . // If it does not parse correctly, or it does parse correctly but the evaluates to false, continue. - TokenStream token_stream { unparsed_size }; - auto media_condition = parse_media_condition(token_stream, MediaCondition::AllowOr::Yes); + TokenStream token_stream { unparsed_size }; + auto media_condition = parse_media_condition(token_stream); auto const* context_window = window(); - if (!media_condition || (context_window && media_condition->evaluate(*context_window) == MatchResult::False)) { + if (!media_condition || (context_window && media_condition->evaluate(context_window) == MatchResult::False)) { continue; } diff --git a/Libraries/LibWeb/CSS/Parser/Parser.h b/Libraries/LibWeb/CSS/Parser/Parser.h index 551ae93416c..99cfa4c59ef 100644 --- a/Libraries/LibWeb/CSS/Parser/Parser.h +++ b/Libraries/LibWeb/CSS/Parser/Parser.h @@ -13,9 +13,9 @@ #include #include #include +#include #include #include -#include #include #include #include @@ -215,7 +215,7 @@ private: void consume_a_function_and_do_nothing(TokenStream&); // TODO: consume_a_unicode_range_value() - Optional parse_general_enclosed(TokenStream&); + OwnPtr parse_general_enclosed(TokenStream&, MatchResult); template Vector parse_font_face_src(TokenStream&); @@ -434,15 +434,16 @@ private: ParseErrorOr> parse_simple_selector(TokenStream&); NonnullRefPtr parse_media_query(TokenStream&); - OwnPtr parse_media_condition(TokenStream&, MediaCondition::AllowOr allow_or); - Optional parse_media_feature(TokenStream&); + OwnPtr parse_media_condition(TokenStream&); + OwnPtr parse_media_feature(TokenStream&); Optional parse_media_type(TokenStream&); - OwnPtr parse_media_in_parens(TokenStream&); Optional parse_media_feature_value(MediaFeatureID, TokenStream&); - OwnPtr parse_supports_condition(TokenStream&); - Optional parse_supports_in_parens(TokenStream&); - Optional parse_supports_feature(TokenStream&); + using ParseTest = AK::Function(TokenStream&)> const&; + OwnPtr parse_boolean_expression(TokenStream&, MatchResult result_for_general_enclosed, ParseTest parse_test); + OwnPtr parse_boolean_expression_group(TokenStream&, MatchResult result_for_general_enclosed, ParseTest parse_test); + + OwnPtr parse_supports_feature(TokenStream&); NonnullRefPtr resolve_unresolved_style_value(DOM::Element&, Optional, PropertyID, UnresolvedStyleValue const&); bool expand_variables(DOM::Element&, Optional, FlyString const& property_name, HashMap>& dependencies, TokenStream& source, Vector& dest); diff --git a/Libraries/LibWeb/CSS/Supports.cpp b/Libraries/LibWeb/CSS/Supports.cpp index 2106524c40e..583074a003f 100644 --- a/Libraries/LibWeb/CSS/Supports.cpp +++ b/Libraries/LibWeb/CSS/Supports.cpp @@ -9,150 +9,47 @@ namespace Web::CSS { -static void indent(StringBuilder& builder, int levels) -{ - for (int i = 0; i < levels; i++) - builder.append(" "sv); -} - -Supports::Supports(NonnullOwnPtr&& condition) +Supports::Supports(NonnullOwnPtr&& condition) : m_condition(move(condition)) { - m_matches = m_condition->evaluate(); + m_matches = m_condition->evaluate_to_boolean(nullptr); } -bool Supports::Condition::evaluate() const +MatchResult Supports::Declaration::evaluate(HTML::Window const*) const { - switch (type) { - case Type::Not: - return !children.first().evaluate(); - case Type::And: - for (auto& child : children) { - if (!child.evaluate()) - return false; - } - return true; - case Type::Or: - for (auto& child : children) { - if (child.evaluate()) - return true; - } - return false; - } - VERIFY_NOT_REACHED(); -} - -bool Supports::InParens::evaluate() const -{ - return value.visit( - [&](NonnullOwnPtr const& condition) { - return condition->evaluate(); - }, - [&](Feature const& feature) { - return feature.evaluate(); - }, - [&](GeneralEnclosed const&) { - return false; - }); -} - -bool Supports::Feature::evaluate() const -{ - return value.visit( - [&](Declaration const& declaration) { - return declaration.evaluate(); - }, - [&](Selector const& selector) { - return selector.evaluate(); - }); + return as_match_result(m_matches); } String Supports::Declaration::to_string() const { - return MUST(String::formatted("({})", declaration)); -} - -String Supports::Selector::to_string() const -{ - return MUST(String::formatted("selector({})", selector)); -} - -String Supports::Feature::to_string() const -{ - return value.visit([](auto& it) { return it.to_string(); }); -} - -String Supports::InParens::to_string() const -{ - return value.visit( - [](NonnullOwnPtr const& condition) { return MUST(String::formatted("({})", condition->to_string())); }, - [](Supports::Feature const& it) { return it.to_string(); }, - [](GeneralEnclosed const& it) { return it.to_string(); }); -} - -String Supports::Condition::to_string() const -{ - switch (type) { - case Type::Not: - return MUST(String::formatted("not {}", children.first().to_string())); - case Type::And: - return MUST(String::join(" and "sv, children)); - case Type::Or: - return MUST(String::join(" or "sv, children)); - } - VERIFY_NOT_REACHED(); -} - -String Supports::to_string() const -{ - return m_condition->to_string(); + return MUST(String::formatted("({})", m_declaration)); } void Supports::Declaration::dump(StringBuilder& builder, int indent_levels) const { indent(builder, indent_levels); - builder.appendff("Declaration: {}\n", declaration); + builder.appendff("Declaration: `{}`, matches={}\n", m_declaration, m_matches); +} + +MatchResult Supports::Selector::evaluate(HTML::Window const*) const +{ + return as_match_result(m_matches); +} + +String Supports::Selector::to_string() const +{ + return MUST(String::formatted("selector({})", m_selector)); } void Supports::Selector::dump(StringBuilder& builder, int indent_levels) const { indent(builder, indent_levels); - builder.appendff("Selector: {}\n", selector); + builder.appendff("Selector: `{}` matches={}\n", m_selector, m_matches); } -void Supports::Feature::dump(StringBuilder& builder, int indent_levels) const +String Supports::to_string() const { - value.visit([&](auto& it) { it.dump(builder, indent_levels); }); -} - -void Supports::InParens::dump(StringBuilder& builder, int indent_levels) const -{ - value.visit( - [&](NonnullOwnPtr const& condition) { condition->dump(builder, indent_levels); }, - [&](Supports::Feature const& it) { it.dump(builder, indent_levels); }, - [&](GeneralEnclosed const& it) { - indent(builder, indent_levels); - builder.appendff("GeneralEnclosed: {}\n", it.to_string()); - }); -} - -void Supports::Condition::dump(StringBuilder& builder, int indent_levels) const -{ - indent(builder, indent_levels); - StringView type_name = [](Type type) { - switch (type) { - case Type::And: - return "AND"sv; - case Type::Or: - return "OR"sv; - case Type::Not: - return "NOT"sv; - } - VERIFY_NOT_REACHED(); - }(type); - builder.appendff("Condition: {}\n", type_name); - for (auto const& child : children) - child.dump(builder, indent_levels + 1); + return m_condition->to_string(); } void Supports::dump(StringBuilder& builder, int indent_levels) const diff --git a/Libraries/LibWeb/CSS/Supports.h b/Libraries/LibWeb/CSS/Supports.h index 913ceb24c4b..8c9fd403df2 100644 --- a/Libraries/LibWeb/CSS/Supports.h +++ b/Libraries/LibWeb/CSS/Supports.h @@ -9,62 +9,58 @@ #include #include #include -#include -#include -#include +#include namespace Web::CSS { // https://www.w3.org/TR/css-conditional-3/#at-supports class Supports final : public RefCounted { public: - struct Declaration { - String declaration; - bool matches; - [[nodiscard]] bool evaluate() const { return matches; } - String to_string() const; - void dump(StringBuilder&, int indent_levels = 0) const; + class Declaration final : public BooleanExpression { + public: + static NonnullOwnPtr create(String declaration, bool matches) + { + return adopt_own(*new Declaration(move(declaration), matches)); + } + virtual ~Declaration() override = default; + + virtual MatchResult evaluate(HTML::Window const*) const override; + virtual String to_string() const override; + virtual void dump(StringBuilder&, int indent_levels = 0) const override; + + private: + Declaration(String declaration, bool matches) + : m_declaration(move(declaration)) + , m_matches(matches) + { + } + String m_declaration; + bool m_matches; }; - struct Selector { - String selector; - bool matches; - [[nodiscard]] bool evaluate() const { return matches; } - String to_string() const; - void dump(StringBuilder&, int indent_levels = 0) const; + class Selector final : public BooleanExpression { + public: + static NonnullOwnPtr create(String selector, bool matches) + { + return adopt_own(*new Selector(move(selector), matches)); + } + virtual ~Selector() override = default; + + virtual MatchResult evaluate(HTML::Window const*) const override; + virtual String to_string() const override; + virtual void dump(StringBuilder&, int indent_levels = 0) const override; + + private: + Selector(String selector, bool matches) + : m_selector(move(selector)) + , m_matches(matches) + { + } + String m_selector; + bool m_matches; }; - struct Feature { - Variant value; - [[nodiscard]] bool evaluate() const; - String to_string() const; - void dump(StringBuilder&, int indent_levels = 0) const; - }; - - struct Condition; - struct InParens { - Variant, Feature, GeneralEnclosed> value; - - [[nodiscard]] bool evaluate() const; - String to_string() const; - void dump(StringBuilder&, int indent_levels = 0) const; - }; - - struct Condition { - enum class Type { - Not, - And, - Or, - }; - Type type; - Vector children; - - [[nodiscard]] bool evaluate() const; - String to_string() const; - void dump(StringBuilder&, int indent_levels = 0) const; - }; - - static NonnullRefPtr create(NonnullOwnPtr&& condition) + static NonnullRefPtr create(NonnullOwnPtr&& condition) { return adopt_ref(*new Supports(move(condition))); } @@ -75,18 +71,10 @@ public: void dump(StringBuilder&, int indent_levels = 0) const; private: - Supports(NonnullOwnPtr&&); + Supports(NonnullOwnPtr&&); - NonnullOwnPtr m_condition; + NonnullOwnPtr m_condition; bool m_matches { false }; }; } - -template<> -struct AK::Formatter : AK::Formatter { - ErrorOr format(FormatBuilder& builder, Web::CSS::Supports::InParens const& in_parens) - { - return Formatter::format(builder, in_parens.to_string()); - } -}; diff --git a/Tests/LibWeb/Text/expected/css/media-query-serialization-basic.txt b/Tests/LibWeb/Text/expected/css/media-query-serialization-basic.txt index a36ee22bd83..300c90b8cd3 100644 --- a/Tests/LibWeb/Text/expected/css/media-query-serialization-basic.txt +++ b/Tests/LibWeb/Text/expected/css/media-query-serialization-basic.txt @@ -1,6 +1,6 @@ @media screen { } -@media screen and ((min-width: 20px) and (max-width: 40px)) { +@media screen and (min-width: 20px) and (max-width: 40px) { } @media screen and (min-resolution: 1dppx) { } diff --git a/Tests/LibWeb/Text/expected/wpt-import/css/cssom/serialize-media-rule.txt b/Tests/LibWeb/Text/expected/wpt-import/css/cssom/serialize-media-rule.txt index 5e1e84f8836..3b36125ba71 100644 --- a/Tests/LibWeb/Text/expected/wpt-import/css/cssom/serialize-media-rule.txt +++ b/Tests/LibWeb/Text/expected/wpt-import/css/cssom/serialize-media-rule.txt @@ -2,8 +2,7 @@ Harness status: OK Found 12 tests -10 Pass -2 Fail +12 Pass Pass empty media query list Pass type - no features Pass type - no features - negation @@ -11,8 +10,8 @@ Pass type - no features - character case normalization Pass type - omission of all Pass type - inclusion of negated all Pass features - character case normalization -Fail features - preservation of overspecified features -Fail features - no lexicographical sorting +Pass features - preservation of overspecified features +Pass features - no lexicographical sorting Pass media query list Pass one rule Pass many rules \ No newline at end of file