mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2025-07-30 04:39:06 +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: 0f5e054f97
Pull-request: https://github.com/LadybirdBrowser/ladybird/pull/3941
13 changed files with 526 additions and 663 deletions
|
@ -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);
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue