mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2025-04-21 03:55:24 +00:00
LibWeb: Implement generic boolean logic for media/supports queries
CSS Values 5 now defines a `<boolean-expr[]>` type that is used in place of the bespoke grammar that previously existed for `@media` and `@supports` queries. This commit implements some BooleanExpression types to represent the nodes in a `<boolean-expr[]>`, and reimplements `@media` and `@supports` queries using this. The one part of this implementation I'm not convinced on is that the `evaluate()` methods take a `HTML::Window*`. This is a compromise because `@media` requires a Window, and `@supports` does not require anything at all. As more users of `<boolean-expr[]>` get implemented in the future, it will become clear if this is sufficient, or if we need to do something smarter. As a bonus, this actually improves our serialization of media queries!
This commit is contained in:
parent
84a695c958
commit
0f5e054f97
Notes:
github-actions[bot]
2025-03-17 10:01:42 +00:00
Author: https://github.com/AtkinsSJ Commit: https://github.com/LadybirdBrowser/ladybird/commit/0f5e054f979 Pull-request: https://github.com/LadybirdBrowser/ladybird/pull/3941
13 changed files with 526 additions and 663 deletions
|
@ -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
|
||||
|
|
136
Libraries/LibWeb/CSS/BooleanExpression.cpp
Normal file
136
Libraries/LibWeb/CSS/BooleanExpression.cpp
Normal file
|
@ -0,0 +1,136 @@
|
|||
/*
|
||||
* Copyright (c) 2021-2025, Sam Atkins <sam@ladybird.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#include <AK/StringBuilder.h>
|
||||
#include <LibWeb/CSS/BooleanExpression.h>
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
}
|
171
Libraries/LibWeb/CSS/BooleanExpression.h
Normal file
171
Libraries/LibWeb/CSS/BooleanExpression.h
Normal file
|
@ -0,0 +1,171 @@
|
|||
/*
|
||||
* Copyright (c) 2021-2025, Sam Atkins <sam@ladybird.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <AK/NonnullOwnPtr.h>
|
||||
#include <AK/String.h>
|
||||
#include <AK/Vector.h>
|
||||
#include <LibWeb/Forward.h>
|
||||
|
||||
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 `<boolean-expr>` 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<GeneralEnclosed> 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<BooleanNotExpression> create(NonnullOwnPtr<BooleanExpression>&& 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<BooleanExpression>&& child)
|
||||
: m_child(move(child))
|
||||
{
|
||||
}
|
||||
|
||||
NonnullOwnPtr<BooleanExpression> m_child;
|
||||
};
|
||||
|
||||
class BooleanExpressionInParens final : public BooleanExpression {
|
||||
public:
|
||||
static NonnullOwnPtr<BooleanExpressionInParens> create(NonnullOwnPtr<BooleanExpression>&& 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<BooleanExpression>&& child)
|
||||
: m_child(move(child))
|
||||
{
|
||||
}
|
||||
|
||||
NonnullOwnPtr<BooleanExpression> m_child;
|
||||
};
|
||||
|
||||
class BooleanAndExpression final : public BooleanExpression {
|
||||
public:
|
||||
static NonnullOwnPtr<BooleanAndExpression> create(Vector<NonnullOwnPtr<BooleanExpression>>&& 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<NonnullOwnPtr<BooleanExpression>>&& children)
|
||||
: m_children(move(children))
|
||||
{
|
||||
}
|
||||
|
||||
Vector<NonnullOwnPtr<BooleanExpression>> m_children;
|
||||
};
|
||||
|
||||
class BooleanOrExpression final : public BooleanExpression {
|
||||
public:
|
||||
static NonnullOwnPtr<BooleanOrExpression> create(Vector<NonnullOwnPtr<BooleanExpression>>&& 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<NonnullOwnPtr<BooleanExpression>>&& children)
|
||||
: m_children(move(children))
|
||||
{
|
||||
}
|
||||
|
||||
Vector<NonnullOwnPtr<BooleanExpression>> m_children;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
template<>
|
||||
struct AK::Formatter<Web::CSS::BooleanExpression> : AK::Formatter<StringView> {
|
||||
ErrorOr<void> format(FormatBuilder& builder, Web::CSS::BooleanExpression const& expression)
|
||||
{
|
||||
return Formatter<StringView>::format(builder, expression.to_string());
|
||||
}
|
||||
};
|
|
@ -1,85 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2021, Sam Atkins <atkinssj@serenityos.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <AK/String.h>
|
||||
|
||||
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<typename Collection, typename Evaluate>
|
||||
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<typename Collection, typename Evaluate>
|
||||
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;
|
||||
};
|
||||
}
|
|
@ -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
|
||||
*/
|
||||
|
@ -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> 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> MediaCondition::from_feature(MediaFeature&& feature)
|
||||
{
|
||||
auto result = new MediaCondition;
|
||||
result->type = Type::Single;
|
||||
result->feature = move(feature);
|
||||
|
||||
return adopt_own(*result);
|
||||
}
|
||||
|
||||
NonnullOwnPtr<MediaCondition> MediaCondition::from_not(NonnullOwnPtr<MediaCondition>&& condition)
|
||||
{
|
||||
auto result = new MediaCondition;
|
||||
result->type = Type::Not;
|
||||
result->conditions.append(move(condition));
|
||||
|
||||
return adopt_own(*result);
|
||||
}
|
||||
|
||||
NonnullOwnPtr<MediaCondition> MediaCondition::from_and_list(Vector<NonnullOwnPtr<MediaCondition>>&& conditions)
|
||||
{
|
||||
auto result = new MediaCondition;
|
||||
result->type = Type::And;
|
||||
result->conditions = move(conditions);
|
||||
|
||||
return adopt_own(*result);
|
||||
}
|
||||
|
||||
NonnullOwnPtr<MediaCondition> MediaCondition::from_or_list(Vector<NonnullOwnPtr<MediaCondition>>&& 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);
|
||||
|
|
|
@ -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
|
||||
*/
|
||||
|
@ -10,8 +10,8 @@
|
|||
#include <AK/Optional.h>
|
||||
#include <AK/OwnPtr.h>
|
||||
#include <AK/RefCounted.h>
|
||||
#include <LibWeb/CSS/BooleanExpression.h>
|
||||
#include <LibWeb/CSS/CalculatedOr.h>
|
||||
#include <LibWeb/CSS/GeneralEnclosed.h>
|
||||
#include <LibWeb/CSS/MediaFeatureID.h>
|
||||
#include <LibWeb/CSS/Ratio.h>
|
||||
|
||||
|
@ -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 `<mf-boolean>` grammar
|
||||
static MediaFeature boolean(MediaFeatureID id)
|
||||
static NonnullOwnPtr<MediaFeature> boolean(MediaFeatureID id)
|
||||
{
|
||||
return MediaFeature(Type::IsTrue, id);
|
||||
return adopt_own(*new MediaFeature(Type::IsTrue, id));
|
||||
}
|
||||
|
||||
// Corresponds to `<mf-plain>` grammar
|
||||
static MediaFeature plain(MediaFeatureID id, MediaFeatureValue value)
|
||||
static NonnullOwnPtr<MediaFeature> 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<MediaFeature> 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<MediaFeature> max(MediaFeatureID id, MediaFeatureValue value)
|
||||
{
|
||||
return MediaFeature(Type::MaxValue, id, move(value));
|
||||
return adopt_own(*new MediaFeature(Type::MaxValue, id, move(value)));
|
||||
}
|
||||
|
||||
// Corresponds to `<mf-range>` grammar, with a single comparison
|
||||
static MediaFeature half_range(MediaFeatureValue value, Comparison comparison, MediaFeatureID id)
|
||||
static NonnullOwnPtr<MediaFeature> 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 `<mf-range>` grammar, with two comparisons
|
||||
static MediaFeature range(MediaFeatureValue left_value, Comparison left_comparison, MediaFeatureID id, Comparison right_comparison, MediaFeatureValue right_value)
|
||||
static NonnullOwnPtr<MediaFeature> 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<Range> 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<MediaCondition> from_general_enclosed(GeneralEnclosed&&);
|
||||
static NonnullOwnPtr<MediaCondition> from_feature(MediaFeature&&);
|
||||
static NonnullOwnPtr<MediaCondition> from_not(NonnullOwnPtr<MediaCondition>&&);
|
||||
static NonnullOwnPtr<MediaCondition> from_and_list(Vector<NonnullOwnPtr<MediaCondition>>&&);
|
||||
static NonnullOwnPtr<MediaCondition> from_or_list(Vector<NonnullOwnPtr<MediaCondition>>&&);
|
||||
|
||||
MatchResult evaluate(HTML::Window const&) const;
|
||||
String to_string() const;
|
||||
|
||||
private:
|
||||
MediaCondition() = default;
|
||||
Type type;
|
||||
Optional<MediaFeature> feature;
|
||||
Vector<NonnullOwnPtr<MediaCondition>> conditions;
|
||||
Optional<GeneralEnclosed> general_enclosed;
|
||||
};
|
||||
|
||||
class MediaQuery : public RefCounted<MediaQuery> {
|
||||
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<MediaCondition> m_media_condition { nullptr };
|
||||
OwnPtr<BooleanExpression> m_media_condition { nullptr };
|
||||
|
||||
// Cached value, updated by evaluate()
|
||||
bool m_matches { false };
|
||||
|
@ -275,14 +243,6 @@ struct Formatter<Web::CSS::MediaFeature> : Formatter<StringView> {
|
|||
}
|
||||
};
|
||||
|
||||
template<>
|
||||
struct Formatter<Web::CSS::MediaCondition> : Formatter<StringView> {
|
||||
ErrorOr<void> format(FormatBuilder& builder, Web::CSS::MediaCondition const& media_condition)
|
||||
{
|
||||
return Formatter<StringView>::format(builder, media_condition.to_string());
|
||||
}
|
||||
};
|
||||
|
||||
template<>
|
||||
struct Formatter<Web::CSS::MediaQuery> : Formatter<StringView> {
|
||||
ErrorOr<void> format(FormatBuilder& builder, Web::CSS::MediaQuery const& media_query)
|
||||
|
|
|
@ -96,11 +96,11 @@ NonnullRefPtr<MediaQuery> Parser::parse_media_query(TokenStream<ComponentValue>&
|
|||
tokens.discard_whitespace();
|
||||
|
||||
// `<media-condition>`
|
||||
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<MediaQuery> Parser::parse_media_query(TokenStream<ComponentValue>&
|
|||
|
||||
// `[ and <media-condition-without-or> ]?`
|
||||
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<BooleanOrExpression>(*media_condition))
|
||||
return invalid_media_query();
|
||||
|
||||
tokens.discard_whitespace();
|
||||
if (tokens.has_next_token())
|
||||
return invalid_media_query();
|
||||
|
@ -141,115 +145,15 @@ NonnullRefPtr<MediaQuery> Parser::parse_media_query(TokenStream<ComponentValue>&
|
|||
}
|
||||
|
||||
// `<media-condition>`, https://www.w3.org/TR/mediaqueries-4/#typedef-media-condition
|
||||
// `<media-condition-widthout-or>`, https://www.w3.org/TR/mediaqueries-4/#typedef-media-condition-without-or
|
||||
// (We distinguish between these two with the `allow_or` parameter.)
|
||||
OwnPtr<MediaCondition> Parser::parse_media_condition(TokenStream<ComponentValue>& tokens, MediaCondition::AllowOr allow_or)
|
||||
OwnPtr<BooleanExpression> Parser::parse_media_condition(TokenStream<ComponentValue>& tokens)
|
||||
{
|
||||
// `<media-not> | <media-in-parens> [ <media-and>* | <media-or>* ]`
|
||||
auto transaction = tokens.begin_transaction();
|
||||
tokens.discard_whitespace();
|
||||
|
||||
// `<media-not> = not <media-in-parens>`
|
||||
auto parse_media_not = [&](auto& tokens) -> OwnPtr<MediaCondition> {
|
||||
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<MediaCondition> {
|
||||
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 {};
|
||||
};
|
||||
|
||||
// `<media-and> = and <media-in-parens>`
|
||||
auto parse_media_and = [&](auto& tokens) { return parse_media_with_combinator(tokens, "and"sv); };
|
||||
// `<media-or> = or <media-in-parens>`
|
||||
auto parse_media_or = [&](auto& tokens) { return parse_media_with_combinator(tokens, "or"sv); };
|
||||
|
||||
// `<media-not>`
|
||||
if (auto maybe_media_not = parse_media_not(tokens)) {
|
||||
transaction.commit();
|
||||
return maybe_media_not.release_nonnull();
|
||||
}
|
||||
|
||||
// `<media-in-parens> [ <media-and>* | <media-or>* ]`
|
||||
if (auto maybe_media_in_parens = parse_media_in_parens(tokens)) {
|
||||
tokens.discard_whitespace();
|
||||
// Only `<media-in-parens>`
|
||||
if (!tokens.has_next_token()) {
|
||||
transaction.commit();
|
||||
return maybe_media_in_parens.release_nonnull();
|
||||
}
|
||||
|
||||
Vector<NonnullOwnPtr<MediaCondition>> child_conditions;
|
||||
child_conditions.append(maybe_media_in_parens.release_nonnull());
|
||||
|
||||
// `<media-and>*`
|
||||
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));
|
||||
}
|
||||
|
||||
// `<media-or>*`
|
||||
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<ComponentValue>& tokens) -> OwnPtr<BooleanExpression> {
|
||||
return parse_media_feature(tokens);
|
||||
});
|
||||
}
|
||||
|
||||
// `<media-feature>`, https://www.w3.org/TR/mediaqueries-4/#typedef-media-feature
|
||||
Optional<MediaFeature> Parser::parse_media_feature(TokenStream<ComponentValue>& tokens)
|
||||
OwnPtr<MediaFeature> Parser::parse_media_feature(TokenStream<ComponentValue>& tokens)
|
||||
{
|
||||
// `[ <mf-plain> | <mf-boolean> | <mf-range> ]`
|
||||
tokens.discard_whitespace();
|
||||
|
@ -288,7 +192,7 @@ Optional<MediaFeature> Parser::parse_media_feature(TokenStream<ComponentValue>&
|
|||
};
|
||||
|
||||
// `<mf-boolean> = <mf-name>`
|
||||
auto parse_mf_boolean = [&](auto& tokens) -> Optional<MediaFeature> {
|
||||
auto parse_mf_boolean = [&](auto& tokens) -> OwnPtr<MediaFeature> {
|
||||
auto transaction = tokens.begin_transaction();
|
||||
tokens.discard_whitespace();
|
||||
|
||||
|
@ -304,7 +208,7 @@ Optional<MediaFeature> Parser::parse_media_feature(TokenStream<ComponentValue>&
|
|||
};
|
||||
|
||||
// `<mf-plain> = <mf-name> : <mf-value>`
|
||||
auto parse_mf_plain = [&](auto& tokens) -> Optional<MediaFeature> {
|
||||
auto parse_mf_plain = [&](auto& tokens) -> OwnPtr<MediaFeature> {
|
||||
auto transaction = tokens.begin_transaction();
|
||||
tokens.discard_whitespace();
|
||||
|
||||
|
@ -406,7 +310,7 @@ Optional<MediaFeature> Parser::parse_media_feature(TokenStream<ComponentValue>&
|
|||
// | <mf-value> <mf-comparison> <mf-name>
|
||||
// | <mf-value> <mf-lt> <mf-name> <mf-lt> <mf-value>
|
||||
// | <mf-value> <mf-gt> <mf-name> <mf-gt> <mf-value>`
|
||||
auto parse_mf_range = [&](auto& tokens) -> Optional<MediaFeature> {
|
||||
auto parse_mf_range = [&](auto& tokens) -> OwnPtr<MediaFeature> {
|
||||
auto transaction = tokens.begin_transaction();
|
||||
tokens.discard_whitespace();
|
||||
|
||||
|
@ -490,14 +394,14 @@ Optional<MediaFeature> Parser::parse_media_feature(TokenStream<ComponentValue>&
|
|||
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<MediaQuery::MediaType> Parser::parse_media_type(TokenStream<ComponentVa
|
|||
return media_type_from_string(ident);
|
||||
}
|
||||
|
||||
// `<media-in-parens>`, https://www.w3.org/TR/mediaqueries-4/#typedef-media-in-parens
|
||||
OwnPtr<MediaCondition> Parser::parse_media_in_parens(TokenStream<ComponentValue>& tokens)
|
||||
{
|
||||
// `<media-in-parens> = ( <media-condition> ) | ( <media-feature> ) | <general-enclosed>`
|
||||
auto transaction = tokens.begin_transaction();
|
||||
tokens.discard_whitespace();
|
||||
|
||||
// `( <media-condition> ) | ( <media-feature> )`
|
||||
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());
|
||||
}
|
||||
}
|
||||
|
||||
// `<general-enclosed>`
|
||||
// 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 {};
|
||||
}
|
||||
|
||||
// `<mf-value>`, https://www.w3.org/TR/mediaqueries-4/#typedef-mf-value
|
||||
Optional<MediaFeatureValue> Parser::parse_media_feature_value(MediaFeatureID media_feature, TokenStream<ComponentValue>& tokens)
|
||||
{
|
||||
|
|
|
@ -150,7 +150,7 @@ RefPtr<Supports> Parser::parse_a_supports(TokenStream<T>& tokens)
|
|||
auto component_values = parse_a_list_of_component_values(tokens);
|
||||
TokenStream<ComponentValue> 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<Supports> Parser::parse_a_supports(TokenStream<T>& tokens)
|
|||
return {};
|
||||
}
|
||||
|
||||
OwnPtr<Supports::Condition> Parser::parse_supports_condition(TokenStream<ComponentValue>& tokens)
|
||||
// https://drafts.csswg.org/css-values-5/#typedef-boolean-expr
|
||||
OwnPtr<BooleanExpression> Parser::parse_boolean_expression(TokenStream<ComponentValue>& tokens, MatchResult result_for_general_enclosed, ParseTest parse_test)
|
||||
{
|
||||
// <boolean-expr[ <test> ]> = not <boolean-expr-group> | <boolean-expr-group>
|
||||
// [ [ and <boolean-expr-group> ]*
|
||||
// | [ or <boolean-expr-group> ]* ]
|
||||
|
||||
auto transaction = tokens.begin_transaction();
|
||||
tokens.discard_whitespace();
|
||||
|
||||
auto const& peeked_token = tokens.next_token();
|
||||
// `not <supports-in-parens>`
|
||||
// `not <boolean-expr-group>`
|
||||
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<Supports::Condition>();
|
||||
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 {};
|
||||
}
|
||||
|
||||
// ` <supports-in-parens> [ and <supports-in-parens> ]*
|
||||
// | <supports-in-parens> [ or <supports-in-parens> ]*`
|
||||
Vector<Supports::InParens> children;
|
||||
Optional<Supports::Condition::Type> condition_type {};
|
||||
auto as_condition_type = [](auto& token) -> Optional<Supports::Condition::Type> {
|
||||
// `<boolean-expr-group>
|
||||
// [ [ and <boolean-expr-group> ]*
|
||||
// | [ or <boolean-expr-group> ]* ]`
|
||||
Vector<NonnullOwnPtr<BooleanExpression>> children;
|
||||
enum class Combinator : u8 {
|
||||
And,
|
||||
Or,
|
||||
};
|
||||
Optional<Combinator> combinator;
|
||||
auto as_combinator = [](auto& token) -> Optional<Combinator> {
|
||||
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<Supports::Condition> Parser::parse_supports_condition(TokenStream<Compone
|
|||
return {};
|
||||
|
||||
transaction.commit();
|
||||
auto condition = make<Supports::Condition>();
|
||||
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<Supports::InParens> Parser::parse_supports_in_parens(TokenStream<ComponentValue>& tokens)
|
||||
OwnPtr<BooleanExpression> Parser::parse_boolean_expression_group(TokenStream<ComponentValue>& tokens, MatchResult result_for_general_enclosed, ParseTest parse_test)
|
||||
{
|
||||
// `( <supports-condition> )`
|
||||
// <boolean-expr-group> = <test> | ( <boolean-expr[ <test> ]> ) | <general-enclosed>
|
||||
|
||||
// `( <boolean-expr[ <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<Supports::InParens> Parser::parse_supports_in_parens(TokenStream<Compon
|
|||
tokens.discard_whitespace();
|
||||
|
||||
TokenStream child_tokens { first_token.block().value };
|
||||
if (auto condition = parse_supports_condition(child_tokens)) {
|
||||
if (auto expression = parse_boolean_expression(child_tokens, result_for_general_enclosed, parse_test)) {
|
||||
if (child_tokens.has_next_token())
|
||||
return {};
|
||||
transaction.commit();
|
||||
return Supports::InParens {
|
||||
.value = { condition.release_nonnull() }
|
||||
};
|
||||
return BooleanExpressionInParens::create(expression.release_nonnull());
|
||||
}
|
||||
}
|
||||
|
||||
// `<supports-feature>`
|
||||
if (auto feature = parse_supports_feature(tokens); feature.has_value()) {
|
||||
return Supports::InParens {
|
||||
.value = { feature.release_value() }
|
||||
};
|
||||
}
|
||||
// `<test>`
|
||||
if (auto test = parse_test(tokens))
|
||||
return test.release_nonnull();
|
||||
|
||||
// `<general-enclosed>`
|
||||
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<Supports::Feature> Parser::parse_supports_feature(TokenStream<ComponentValue>& tokens)
|
||||
// https://drafts.csswg.org/css-conditional-4/#typedef-supports-feature
|
||||
OwnPtr<BooleanExpression> Parser::parse_supports_feature(TokenStream<ComponentValue>& tokens)
|
||||
{
|
||||
// <supports-feature> = <supports-selector-fn> | <supports-decl>
|
||||
auto transaction = tokens.begin_transaction();
|
||||
tokens.discard_whitespace();
|
||||
auto const& first_token = tokens.consume_a_token();
|
||||
|
||||
// `<supports-decl>`
|
||||
// `<supports-decl> = ( <declaration> )`
|
||||
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());
|
||||
}
|
||||
}
|
||||
|
||||
// `<supports-selector-fn>`
|
||||
// `<supports-selector-fn> = selector( <complex-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<Supports::Feature> Parser::parse_supports_feature(TokenStream<Component
|
|||
builder.append(item.to_string());
|
||||
transaction.commit();
|
||||
TokenStream selector_tokens { first_token.function().value };
|
||||
return Supports::Feature {
|
||||
Supports::Selector {
|
||||
builder.to_string_without_validation(),
|
||||
!parse_a_selector_list(selector_tokens, SelectorType::Standalone).is_error() }
|
||||
};
|
||||
return Supports::Selector::create(
|
||||
builder.to_string_without_validation(),
|
||||
!parse_a_selector_list(selector_tokens, SelectorType::Standalone).is_error());
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
// https://www.w3.org/TR/mediaqueries-4/#typedef-general-enclosed
|
||||
Optional<GeneralEnclosed> Parser::parse_general_enclosed(TokenStream<ComponentValue>& tokens)
|
||||
OwnPtr<GeneralEnclosed> Parser::parse_general_enclosed(TokenStream<ComponentValue>& tokens, MatchResult result)
|
||||
{
|
||||
// FIXME: <general-enclosed> 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<GeneralEnclosed> Parser::parse_general_enclosed(TokenStream<ComponentVa
|
|||
// `[ <function-token> <any-value>? ) ]`
|
||||
if (first_token.is_function()) {
|
||||
transaction.commit();
|
||||
return GeneralEnclosed { first_token.to_string() };
|
||||
return GeneralEnclosed::create(first_token.to_string(), result);
|
||||
}
|
||||
|
||||
// `( <any-value>? )`
|
||||
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 <media-condition>.
|
||||
// If it does not parse correctly, or it does parse correctly but the <media-condition> evaluates to false, continue.
|
||||
TokenStream<ComponentValue> 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;
|
||||
}
|
||||
|
||||
|
|
|
@ -13,9 +13,9 @@
|
|||
#include <AK/Vector.h>
|
||||
#include <LibGC/Ptr.h>
|
||||
#include <LibGfx/Font/UnicodeRange.h>
|
||||
#include <LibWeb/CSS/BooleanExpression.h>
|
||||
#include <LibWeb/CSS/CSSStyleDeclaration.h>
|
||||
#include <LibWeb/CSS/CSSStyleValue.h>
|
||||
#include <LibWeb/CSS/GeneralEnclosed.h>
|
||||
#include <LibWeb/CSS/MediaQuery.h>
|
||||
#include <LibWeb/CSS/ParsedFontFace.h>
|
||||
#include <LibWeb/CSS/Parser/ComponentValue.h>
|
||||
|
@ -215,7 +215,7 @@ private:
|
|||
void consume_a_function_and_do_nothing(TokenStream<Token>&);
|
||||
// TODO: consume_a_unicode_range_value()
|
||||
|
||||
Optional<GeneralEnclosed> parse_general_enclosed(TokenStream<ComponentValue>&);
|
||||
OwnPtr<GeneralEnclosed> parse_general_enclosed(TokenStream<ComponentValue>&, MatchResult);
|
||||
|
||||
template<typename T>
|
||||
Vector<ParsedFontFace::Source> parse_font_face_src(TokenStream<T>&);
|
||||
|
@ -434,15 +434,16 @@ private:
|
|||
ParseErrorOr<Optional<Selector::SimpleSelector>> parse_simple_selector(TokenStream<ComponentValue>&);
|
||||
|
||||
NonnullRefPtr<MediaQuery> parse_media_query(TokenStream<ComponentValue>&);
|
||||
OwnPtr<MediaCondition> parse_media_condition(TokenStream<ComponentValue>&, MediaCondition::AllowOr allow_or);
|
||||
Optional<MediaFeature> parse_media_feature(TokenStream<ComponentValue>&);
|
||||
OwnPtr<BooleanExpression> parse_media_condition(TokenStream<ComponentValue>&);
|
||||
OwnPtr<MediaFeature> parse_media_feature(TokenStream<ComponentValue>&);
|
||||
Optional<MediaQuery::MediaType> parse_media_type(TokenStream<ComponentValue>&);
|
||||
OwnPtr<MediaCondition> parse_media_in_parens(TokenStream<ComponentValue>&);
|
||||
Optional<MediaFeatureValue> parse_media_feature_value(MediaFeatureID, TokenStream<ComponentValue>&);
|
||||
|
||||
OwnPtr<Supports::Condition> parse_supports_condition(TokenStream<ComponentValue>&);
|
||||
Optional<Supports::InParens> parse_supports_in_parens(TokenStream<ComponentValue>&);
|
||||
Optional<Supports::Feature> parse_supports_feature(TokenStream<ComponentValue>&);
|
||||
using ParseTest = AK::Function<OwnPtr<BooleanExpression>(TokenStream<ComponentValue>&)> const&;
|
||||
OwnPtr<BooleanExpression> parse_boolean_expression(TokenStream<ComponentValue>&, MatchResult result_for_general_enclosed, ParseTest parse_test);
|
||||
OwnPtr<BooleanExpression> parse_boolean_expression_group(TokenStream<ComponentValue>&, MatchResult result_for_general_enclosed, ParseTest parse_test);
|
||||
|
||||
OwnPtr<BooleanExpression> parse_supports_feature(TokenStream<ComponentValue>&);
|
||||
|
||||
NonnullRefPtr<CSSStyleValue> resolve_unresolved_style_value(DOM::Element&, Optional<Selector::PseudoElement::Type>, PropertyID, UnresolvedStyleValue const&);
|
||||
bool expand_variables(DOM::Element&, Optional<Selector::PseudoElement::Type>, FlyString const& property_name, HashMap<FlyString, NonnullRefPtr<PropertyDependencyNode>>& dependencies, TokenStream<ComponentValue>& source, Vector<ComponentValue>& dest);
|
||||
|
|
|
@ -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>&& condition)
|
||||
Supports::Supports(NonnullOwnPtr<BooleanExpression>&& 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<Condition> 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<Condition> 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<Condition> 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
|
||||
|
|
|
@ -9,62 +9,58 @@
|
|||
#include <AK/NonnullOwnPtr.h>
|
||||
#include <AK/RefCounted.h>
|
||||
#include <AK/String.h>
|
||||
#include <AK/Variant.h>
|
||||
#include <AK/Vector.h>
|
||||
#include <LibWeb/CSS/GeneralEnclosed.h>
|
||||
#include <LibWeb/CSS/BooleanExpression.h>
|
||||
|
||||
namespace Web::CSS {
|
||||
|
||||
// https://www.w3.org/TR/css-conditional-3/#at-supports
|
||||
class Supports final : public RefCounted<Supports> {
|
||||
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<Declaration> 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<Selector> 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<Declaration, Selector> value;
|
||||
[[nodiscard]] bool evaluate() const;
|
||||
String to_string() const;
|
||||
void dump(StringBuilder&, int indent_levels = 0) const;
|
||||
};
|
||||
|
||||
struct Condition;
|
||||
struct InParens {
|
||||
Variant<NonnullOwnPtr<Condition>, 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<InParens> children;
|
||||
|
||||
[[nodiscard]] bool evaluate() const;
|
||||
String to_string() const;
|
||||
void dump(StringBuilder&, int indent_levels = 0) const;
|
||||
};
|
||||
|
||||
static NonnullRefPtr<Supports> create(NonnullOwnPtr<Condition>&& condition)
|
||||
static NonnullRefPtr<Supports> create(NonnullOwnPtr<BooleanExpression>&& condition)
|
||||
{
|
||||
return adopt_ref(*new Supports(move(condition)));
|
||||
}
|
||||
|
@ -75,18 +71,10 @@ public:
|
|||
void dump(StringBuilder&, int indent_levels = 0) const;
|
||||
|
||||
private:
|
||||
Supports(NonnullOwnPtr<Condition>&&);
|
||||
Supports(NonnullOwnPtr<BooleanExpression>&&);
|
||||
|
||||
NonnullOwnPtr<Condition> m_condition;
|
||||
NonnullOwnPtr<BooleanExpression> m_condition;
|
||||
bool m_matches { false };
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
template<>
|
||||
struct AK::Formatter<Web::CSS::Supports::InParens> : AK::Formatter<StringView> {
|
||||
ErrorOr<void> format(FormatBuilder& builder, Web::CSS::Supports::InParens const& in_parens)
|
||||
{
|
||||
return Formatter<StringView>::format(builder, in_parens.to_string());
|
||||
}
|
||||
};
|
||||
|
|
|
@ -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) {
|
||||
}
|
||||
|
|
|
@ -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
|
Loading…
Add table
Reference in a new issue