LibWeb: Use document's viewport when resolving lengths in media queries

Previously we would always use the window's viewport which was incorrect
if we were within an iframe.

This is likely applicable to all uses of
`Length::ResolutionContext::for_window`.
This commit is contained in:
Callum Law 2025-10-07 00:54:19 +13:00 committed by Sam Atkins
commit 05c336ea4e
Notes: github-actions[bot] 2025-10-07 09:34:36 +00:00
23 changed files with 198 additions and 83 deletions

View file

@ -9,9 +9,9 @@
namespace Web::CSS { namespace Web::CSS {
bool BooleanExpression::evaluate_to_boolean(HTML::Window const* window) const bool BooleanExpression::evaluate_to_boolean(DOM::Document const* document) const
{ {
return evaluate(window) == MatchResult::True; return evaluate(document) == MatchResult::True;
} }
void BooleanExpression::indent(StringBuilder& builder, int levels) void BooleanExpression::indent(StringBuilder& builder, int levels)
@ -25,11 +25,11 @@ void GeneralEnclosed::dump(StringBuilder& builder, int indent_levels) const
builder.appendff("GeneralEnclosed: {}\n", to_string()); builder.appendff("GeneralEnclosed: {}\n", to_string());
} }
MatchResult BooleanNotExpression::evaluate(HTML::Window const* window) const MatchResult BooleanNotExpression::evaluate(DOM::Document const* document) const
{ {
// https://drafts.csswg.org/css-values-5/#boolean-logic // https://drafts.csswg.org/css-values-5/#boolean-logic
// `not test` evaluates to true if its contained test is false, false if its true, and unknown if its unknown. // `not test` evaluates to true if its contained test is false, false if its true, and unknown if its unknown.
switch (m_child->evaluate(window)) { switch (m_child->evaluate(document)) {
case MatchResult::False: case MatchResult::False:
return MatchResult::True; return MatchResult::True;
case MatchResult::True: case MatchResult::True:
@ -52,9 +52,9 @@ void BooleanNotExpression::dump(StringBuilder& builder, int indent_levels) const
m_child->dump(builder, indent_levels + 1); m_child->dump(builder, indent_levels + 1);
} }
MatchResult BooleanExpressionInParens::evaluate(HTML::Window const* window) const MatchResult BooleanExpressionInParens::evaluate(DOM::Document const* document) const
{ {
return m_child->evaluate(window); return m_child->evaluate(document);
} }
String BooleanExpressionInParens::to_string() const String BooleanExpressionInParens::to_string() const
@ -71,14 +71,14 @@ void BooleanExpressionInParens::dump(StringBuilder& builder, int indent_levels)
builder.append(")\n"sv); builder.append(")\n"sv);
} }
MatchResult BooleanAndExpression::evaluate(HTML::Window const* window) const MatchResult BooleanAndExpression::evaluate(DOM::Document const* document) const
{ {
// https://drafts.csswg.org/css-values-5/#boolean-logic // 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 // 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). // false, and unknown otherwise (i.e. if at least one unknown, but no false).
size_t true_results = 0; size_t true_results = 0;
for (auto const& child : m_children) { for (auto const& child : m_children) {
auto child_match = child->evaluate(window); auto child_match = child->evaluate(document);
if (child_match == MatchResult::False) if (child_match == MatchResult::False)
return MatchResult::False; return MatchResult::False;
if (child_match == MatchResult::True) if (child_match == MatchResult::True)
@ -102,14 +102,14 @@ void BooleanAndExpression::dump(StringBuilder& builder, int indent_levels) const
child->dump(builder, indent_levels + 1); child->dump(builder, indent_levels + 1);
} }
MatchResult BooleanOrExpression::evaluate(HTML::Window const* window) const MatchResult BooleanOrExpression::evaluate(DOM::Document const* document) const
{ {
// https://drafts.csswg.org/css-values-5/#boolean-logic // 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 // 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). // false, and unknown otherwise (i.e. at least one unknown, but no true).
size_t false_results = 0; size_t false_results = 0;
for (auto const& child : m_children) { for (auto const& child : m_children) {
auto child_match = child->evaluate(window); auto child_match = child->evaluate(document);
if (child_match == MatchResult::True) if (child_match == MatchResult::True)
return MatchResult::True; return MatchResult::True;
if (child_match == MatchResult::False) if (child_match == MatchResult::False)

View file

@ -81,10 +81,10 @@ class BooleanExpression {
public: public:
virtual ~BooleanExpression() = default; virtual ~BooleanExpression() = default;
bool evaluate_to_boolean(HTML::Window const*) const; bool evaluate_to_boolean(DOM::Document const*) const;
static void indent(StringBuilder& builder, int levels); static void indent(StringBuilder& builder, int levels);
virtual MatchResult evaluate(HTML::Window const*) const = 0; virtual MatchResult evaluate(DOM::Document const*) const = 0;
virtual String to_string() const = 0; virtual String to_string() const = 0;
virtual void dump(StringBuilder&, int indent_levels = 0) const = 0; virtual void dump(StringBuilder&, int indent_levels = 0) const = 0;
}; };
@ -98,7 +98,7 @@ public:
} }
virtual ~GeneralEnclosed() override = default; virtual ~GeneralEnclosed() override = default;
virtual MatchResult evaluate(HTML::Window const*) const override { return m_matches; } virtual MatchResult evaluate(DOM::Document const*) const override { return m_matches; }
virtual String to_string() const override { return m_serialized_contents; } virtual String to_string() const override { return m_serialized_contents; }
virtual void dump(StringBuilder&, int indent_levels = 0) const override; virtual void dump(StringBuilder&, int indent_levels = 0) const override;
@ -121,7 +121,7 @@ public:
} }
virtual ~BooleanNotExpression() override = default; virtual ~BooleanNotExpression() override = default;
virtual MatchResult evaluate(HTML::Window const*) const override; virtual MatchResult evaluate(DOM::Document const*) const override;
virtual String to_string() const override; virtual String to_string() const override;
virtual void dump(StringBuilder&, int indent_levels = 0) const override; virtual void dump(StringBuilder&, int indent_levels = 0) const override;
@ -142,7 +142,7 @@ public:
} }
virtual ~BooleanExpressionInParens() override = default; virtual ~BooleanExpressionInParens() override = default;
virtual MatchResult evaluate(HTML::Window const*) const override; virtual MatchResult evaluate(DOM::Document const*) const override;
virtual String to_string() const override; virtual String to_string() const override;
virtual void dump(StringBuilder&, int indent_levels = 0) const override; virtual void dump(StringBuilder&, int indent_levels = 0) const override;
@ -163,7 +163,7 @@ public:
} }
virtual ~BooleanAndExpression() override = default; virtual ~BooleanAndExpression() override = default;
virtual MatchResult evaluate(HTML::Window const*) const override; virtual MatchResult evaluate(DOM::Document const*) const override;
virtual String to_string() const override; virtual String to_string() const override;
virtual void dump(StringBuilder&, int indent_levels = 0) const override; virtual void dump(StringBuilder&, int indent_levels = 0) const override;
@ -184,7 +184,7 @@ public:
} }
virtual ~BooleanOrExpression() override = default; virtual ~BooleanOrExpression() override = default;
virtual MatchResult evaluate(HTML::Window const*) const override; virtual MatchResult evaluate(DOM::Document const*) const override;
virtual String to_string() const override; virtual String to_string() const override;
virtual void dump(StringBuilder&, int indent_levels = 0) const override; virtual void dump(StringBuilder&, int indent_levels = 0) const override;

View file

@ -30,7 +30,7 @@ public:
MediaList* media() const { return m_media; } MediaList* media() const { return m_media; }
bool evaluate(HTML::Window const& window) { return m_media->evaluate(window); } bool evaluate(DOM::Document const& document) { return m_media->evaluate(document); }
private: private:
CSSMediaRule(JS::Realm&, MediaList&, CSSRuleList&); CSSMediaRule(JS::Realm&, MediaList&, CSSRuleList&);

View file

@ -224,7 +224,7 @@ void CSSRuleList::for_each_effective_rule(TraversalOrder order, Function<void(We
} }
} }
bool CSSRuleList::evaluate_media_queries(HTML::Window const& window) bool CSSRuleList::evaluate_media_queries(DOM::Document const& document)
{ {
bool any_media_queries_changed_match_state = false; bool any_media_queries_changed_match_state = false;
@ -232,35 +232,35 @@ bool CSSRuleList::evaluate_media_queries(HTML::Window const& window)
switch (rule->type()) { switch (rule->type()) {
case CSSRule::Type::Import: { case CSSRule::Type::Import: {
auto& import_rule = as<CSSImportRule>(*rule); auto& import_rule = as<CSSImportRule>(*rule);
if (import_rule.loaded_style_sheet() && import_rule.loaded_style_sheet()->evaluate_media_queries(window)) if (import_rule.loaded_style_sheet() && import_rule.loaded_style_sheet()->evaluate_media_queries(document))
any_media_queries_changed_match_state = true; any_media_queries_changed_match_state = true;
break; break;
} }
case CSSRule::Type::LayerBlock: { case CSSRule::Type::LayerBlock: {
auto& layer_rule = as<CSSLayerBlockRule>(*rule); auto& layer_rule = as<CSSLayerBlockRule>(*rule);
if (layer_rule.css_rules().evaluate_media_queries(window)) if (layer_rule.css_rules().evaluate_media_queries(document))
any_media_queries_changed_match_state = true; any_media_queries_changed_match_state = true;
break; break;
} }
case CSSRule::Type::Media: { case CSSRule::Type::Media: {
auto& media_rule = as<CSSMediaRule>(*rule); auto& media_rule = as<CSSMediaRule>(*rule);
bool did_match = media_rule.condition_matches(); bool did_match = media_rule.condition_matches();
bool now_matches = media_rule.evaluate(window); bool now_matches = media_rule.evaluate(document);
if (did_match != now_matches) if (did_match != now_matches)
any_media_queries_changed_match_state = true; any_media_queries_changed_match_state = true;
if (now_matches && media_rule.css_rules().evaluate_media_queries(window)) if (now_matches && media_rule.css_rules().evaluate_media_queries(document))
any_media_queries_changed_match_state = true; any_media_queries_changed_match_state = true;
break; break;
} }
case CSSRule::Type::Supports: { case CSSRule::Type::Supports: {
auto& supports_rule = as<CSSSupportsRule>(*rule); auto& supports_rule = as<CSSSupportsRule>(*rule);
if (supports_rule.condition_matches() && supports_rule.css_rules().evaluate_media_queries(window)) if (supports_rule.condition_matches() && supports_rule.css_rules().evaluate_media_queries(document))
any_media_queries_changed_match_state = true; any_media_queries_changed_match_state = true;
break; break;
} }
case CSSRule::Type::Style: { case CSSRule::Type::Style: {
auto& style_rule = as<CSSStyleRule>(*rule); auto& style_rule = as<CSSStyleRule>(*rule);
if (style_rule.css_rules().evaluate_media_queries(window)) if (style_rule.css_rules().evaluate_media_queries(document))
any_media_queries_changed_match_state = true; any_media_queries_changed_match_state = true;
break; break;
} }

View file

@ -62,7 +62,7 @@ public:
void for_each_effective_rule(TraversalOrder, Function<void(CSSRule const&)> const& callback) const; void for_each_effective_rule(TraversalOrder, Function<void(CSSRule const&)> const& callback) const;
// Returns whether the match state of any media queries changed after evaluation. // Returns whether the match state of any media queries changed after evaluation.
bool evaluate_media_queries(HTML::Window const&); bool evaluate_media_queries(DOM::Document const&);
void set_owner_rule(GC::Ref<CSSRule> owner_rule) { m_owner_rule = owner_rule; } void set_owner_rule(GC::Ref<CSSRule> owner_rule) { m_owner_rule = owner_rule; }
void set_rules(Badge<CSSStyleSheet>, Vector<GC::Ref<CSSRule>> rules) { m_rules = move(rules); } void set_rules(Badge<CSSStyleSheet>, Vector<GC::Ref<CSSRule>> rules) { m_rules = move(rules); }

View file

@ -353,14 +353,14 @@ GC::Ptr<DOM::Document> CSSStyleSheet::owning_document() const
return nullptr; return nullptr;
} }
bool CSSStyleSheet::evaluate_media_queries(HTML::Window const& window) bool CSSStyleSheet::evaluate_media_queries(DOM::Document const& document)
{ {
bool any_media_queries_changed_match_state = false; bool any_media_queries_changed_match_state = false;
bool now_matches = m_media->evaluate(window); bool now_matches = m_media->evaluate(document);
if (!m_did_match.has_value() || m_did_match.value() != now_matches) if (!m_did_match.has_value() || m_did_match.value() != now_matches)
any_media_queries_changed_match_state = true; any_media_queries_changed_match_state = true;
if (now_matches && m_rules->evaluate_media_queries(window)) if (now_matches && m_rules->evaluate_media_queries(document))
any_media_queries_changed_match_state = true; any_media_queries_changed_match_state = true;
m_did_match = now_matches; m_did_match = now_matches;

View file

@ -63,7 +63,7 @@ public:
void for_each_effective_rule(TraversalOrder, Function<void(CSSRule const&)> const& callback) const; void for_each_effective_rule(TraversalOrder, Function<void(CSSRule const&)> const& callback) const;
void for_each_effective_style_producing_rule(Function<void(CSSRule const&)> const& callback) const; void for_each_effective_style_producing_rule(Function<void(CSSRule const&)> const& callback) const;
// Returns whether the match state of any media queries changed after evaluation. // Returns whether the match state of any media queries changed after evaluation.
bool evaluate_media_queries(HTML::Window const&); bool evaluate_media_queries(DOM::Document const&);
void for_each_effective_keyframes_at_rule(Function<void(CSSKeyframesRule const&)> const& callback) const; void for_each_effective_keyframes_at_rule(Function<void(CSSKeyframesRule const&)> const& callback) const;
void add_owning_document_or_shadow_root(DOM::Node& document_or_shadow_root); void add_owning_document_or_shadow_root(DOM::Node& document_or_shadow_root);

View file

@ -137,6 +137,18 @@ Length::ResolutionContext Length::ResolutionContext::for_window(HTML::Window con
}; };
} }
Length::ResolutionContext Length::ResolutionContext::for_document(DOM::Document const& document)
{
auto const& initial_font = document.style_computer().initial_font();
Gfx::FontPixelMetrics const& initial_font_metrics = initial_font.pixel_metrics();
Length::FontMetrics font_metrics { CSSPixels { initial_font.pixel_size() }, initial_font_metrics, InitialValues::line_height() };
return Length::ResolutionContext {
.viewport_rect = document.navigable()->viewport_rect(),
.font_metrics = font_metrics,
.root_font_metrics = font_metrics,
};
}
Length::ResolutionContext Length::ResolutionContext::for_layout_node(Layout::Node const& node) Length::ResolutionContext Length::ResolutionContext::for_layout_node(Layout::Node const& node)
{ {
Layout::Node const* root_layout_node; Layout::Node const* root_layout_node;

View file

@ -55,7 +55,10 @@ public:
FlyString unit_name() const { return CSS::to_string(m_unit); } FlyString unit_name() const { return CSS::to_string(m_unit); }
struct ResolutionContext { struct ResolutionContext {
[[nodiscard]] static ResolutionContext for_document(DOM::Document const&);
[[nodiscard]] static ResolutionContext for_element(DOM::AbstractElement const&); [[nodiscard]] static ResolutionContext for_element(DOM::AbstractElement const&);
// FIXME: Anywhere we use this we probably want to use `for_document` instead since this uses the window's
// viewport rather than the documents which can differ e.g. with iframes.
[[nodiscard]] static ResolutionContext for_window(HTML::Window const&); [[nodiscard]] static ResolutionContext for_window(HTML::Window const&);
[[nodiscard]] static ResolutionContext for_layout_node(Layout::Node const&); [[nodiscard]] static ResolutionContext for_layout_node(Layout::Node const&);

View file

@ -109,10 +109,10 @@ void MediaList::delete_medium(StringView medium)
// FIXME: If nothing was removed, then throw a NotFoundError exception. // FIXME: If nothing was removed, then throw a NotFoundError exception.
} }
bool MediaList::evaluate(HTML::Window const& window) bool MediaList::evaluate(DOM::Document const& document)
{ {
for (auto& media : m_media) for (auto& media : m_media)
media->evaluate(window); media->evaluate(document);
return matches(); return matches();
} }

View file

@ -33,7 +33,7 @@ public:
virtual Optional<JS::Value> item_value(size_t index) const override; virtual Optional<JS::Value> item_value(size_t index) const override;
bool evaluate(HTML::Window const&); bool evaluate(DOM::Document const&);
bool matches() const; bool matches() const;
void set_associated_style_sheet(GC::Ref<StyleSheet> style_sheet) { m_associated_style_sheet = style_sheet; } void set_associated_style_sheet(GC::Ref<StyleSheet> style_sheet) { m_associated_style_sheet = style_sheet; }

View file

@ -96,16 +96,17 @@ String MediaFeature::to_string() const
VERIFY_NOT_REACHED(); VERIFY_NOT_REACHED();
} }
MatchResult MediaFeature::evaluate(HTML::Window const* window) const MatchResult MediaFeature::evaluate(DOM::Document const* document) const
{ {
VERIFY(window); VERIFY(document);
auto maybe_queried_value = window->query_media_feature(m_id); VERIFY(document->window());
auto maybe_queried_value = document->window()->query_media_feature(m_id);
if (!maybe_queried_value.has_value()) if (!maybe_queried_value.has_value())
return MatchResult::False; return MatchResult::False;
auto queried_value = maybe_queried_value.release_value(); auto queried_value = maybe_queried_value.release_value();
CalculationResolutionContext calculation_context { CalculationResolutionContext calculation_context {
.length_resolution_context = Length::ResolutionContext::for_window(*window), .length_resolution_context = Length::ResolutionContext::for_document(*document),
}; };
switch (m_type) { switch (m_type) {
case Type::IsTrue: case Type::IsTrue:
@ -128,23 +129,23 @@ MatchResult MediaFeature::evaluate(HTML::Window const* window) const
return MatchResult::False; return MatchResult::False;
case Type::ExactValue: case Type::ExactValue:
return compare(*window, value(), Comparison::Equal, queried_value); return compare(*document, value(), Comparison::Equal, queried_value);
case Type::MinValue: case Type::MinValue:
return compare(*window, queried_value, Comparison::GreaterThanOrEqual, value()); return compare(*document, queried_value, Comparison::GreaterThanOrEqual, value());
case Type::MaxValue: case Type::MaxValue:
return compare(*window, queried_value, Comparison::LessThanOrEqual, value()); return compare(*document, queried_value, Comparison::LessThanOrEqual, value());
case Type::Range: { case Type::Range: {
auto const& range = this->range(); auto const& range = this->range();
if (range.left_comparison.has_value()) { if (range.left_comparison.has_value()) {
if (auto const left_result = compare(*window, *range.left_value, *range.left_comparison, queried_value); left_result != MatchResult::True) if (auto const left_result = compare(*document, *range.left_value, *range.left_comparison, queried_value); left_result != MatchResult::True)
return left_result; return left_result;
} }
if (range.right_comparison.has_value()) { if (range.right_comparison.has_value()) {
if (auto const right_result = compare(*window, queried_value, *range.right_comparison, *range.right_value); right_result != MatchResult::True) if (auto const right_result = compare(*document, queried_value, *range.right_comparison, *range.right_value); right_result != MatchResult::True)
return right_result; return right_result;
} }
@ -155,7 +156,7 @@ MatchResult MediaFeature::evaluate(HTML::Window const* window) const
VERIFY_NOT_REACHED(); VERIFY_NOT_REACHED();
} }
MatchResult MediaFeature::compare(HTML::Window const& window, MediaFeatureValue const& left, Comparison comparison, MediaFeatureValue const& right) MatchResult MediaFeature::compare(DOM::Document const& document, MediaFeatureValue const& left, Comparison comparison, MediaFeatureValue const& right)
{ {
if (left.is_unknown() || right.is_unknown()) if (left.is_unknown() || right.is_unknown())
return MatchResult::Unknown; return MatchResult::Unknown;
@ -169,7 +170,7 @@ MatchResult MediaFeature::compare(HTML::Window const& window, MediaFeatureValue
return MatchResult::False; return MatchResult::False;
} }
auto length_resolution_context = Length::ResolutionContext::for_window(window); auto length_resolution_context = Length::ResolutionContext::for_document(document);
CalculationResolutionContext calculation_context { CalculationResolutionContext calculation_context {
.length_resolution_context = length_resolution_context, .length_resolution_context = length_resolution_context,
@ -282,7 +283,7 @@ String MediaQuery::to_string() const
return MUST(builder.to_string()); return MUST(builder.to_string());
} }
bool MediaQuery::evaluate(HTML::Window const& window) bool MediaQuery::evaluate(DOM::Document const& document)
{ {
auto matches_media = [](MediaType const& media) -> MatchResult { auto matches_media = [](MediaType const& media) -> MatchResult {
if (!media.known_type.has_value()) if (!media.known_type.has_value())
@ -303,7 +304,7 @@ bool MediaQuery::evaluate(HTML::Window const& window)
MatchResult result = matches_media(m_media_type); MatchResult result = matches_media(m_media_type);
if ((result != MatchResult::False) && m_media_condition) if ((result != MatchResult::False) && m_media_condition)
result = result && m_media_condition->evaluate(&window); result = result && m_media_condition->evaluate(&document);
if (m_negated) if (m_negated)
result = negate(result); result = negate(result);

View file

@ -161,7 +161,7 @@ public:
})); }));
} }
virtual MatchResult evaluate(HTML::Window const*) const override; virtual MatchResult evaluate(DOM::Document const*) const override;
virtual String to_string() const override; virtual String to_string() const override;
virtual void dump(StringBuilder&, int indent_levels = 0) const override; virtual void dump(StringBuilder&, int indent_levels = 0) const override;
@ -188,7 +188,7 @@ private:
{ {
} }
static MatchResult compare(HTML::Window const& window, MediaFeatureValue const& left, Comparison comparison, MediaFeatureValue const& right); static MatchResult compare(DOM::Document const& document, MediaFeatureValue const& left, Comparison comparison, MediaFeatureValue const& right);
MediaFeatureValue const& value() const { return m_value.get<MediaFeatureValue>(); } MediaFeatureValue const& value() const { return m_value.get<MediaFeatureValue>(); }
Range const& range() const { return m_value.get<Range>(); } Range const& range() const { return m_value.get<Range>(); }
@ -218,7 +218,7 @@ public:
static NonnullRefPtr<MediaQuery> create() { return adopt_ref(*new MediaQuery); } static NonnullRefPtr<MediaQuery> create() { return adopt_ref(*new MediaQuery); }
bool matches() const { return m_matches; } bool matches() const { return m_matches; }
bool evaluate(HTML::Window const&); bool evaluate(DOM::Document const&);
String to_string() const; String to_string() const;
private: private:

View file

@ -85,16 +85,12 @@ bool MediaQueryList::matches() const
bool MediaQueryList::evaluate() bool MediaQueryList::evaluate()
{ {
auto window = m_document->window();
if (!window)
return false;
if (m_media.is_empty()) if (m_media.is_empty())
return true; return true;
bool now_matches = false; bool now_matches = false;
for (auto& media : m_media) { for (auto& media : m_media) {
now_matches = now_matches || media->evaluate(*window); now_matches = now_matches || media->evaluate(m_document);
} }
return now_matches; return now_matches;

View file

@ -1814,8 +1814,7 @@ LengthOrCalculated Parser::parse_as_sizes_attribute(DOM::Element const& element,
// If it does not parse correctly, or it does parse correctly but the <media-condition> evaluates to false, continue. // If it does not parse correctly, or it does parse correctly but the <media-condition> evaluates to false, continue.
TokenStream token_stream { unparsed_size }; TokenStream token_stream { unparsed_size };
auto media_condition = parse_media_condition(token_stream); auto media_condition = parse_media_condition(token_stream);
auto const* context_window = window(); if (!media_condition || (m_document && media_condition->evaluate(m_document) == MatchResult::False)) {
if (!media_condition || (context_window && media_condition->evaluate(context_window) == MatchResult::False)) {
continue; continue;
} }

View file

@ -109,7 +109,7 @@ void StyleSheetList::add_sheet(CSSStyleSheet& sheet)
// NOTE: We evaluate media queries immediately when adding a new sheet. // NOTE: We evaluate media queries immediately when adding a new sheet.
// This coalesces the full document style invalidations. // This coalesces the full document style invalidations.
// If we don't do this, we invalidate now, and then again when Document updates media rules. // If we don't do this, we invalidate now, and then again when Document updates media rules.
sheet.evaluate_media_queries(as<HTML::Window>(HTML::relevant_global_object(*this))); sheet.evaluate_media_queries(document());
if (sheet.rules().length() == 0) { if (sheet.rules().length() == 0) {
// NOTE: If the added sheet has no rules, we don't have to invalidate anything. // NOTE: If the added sheet has no rules, we don't have to invalidate anything.

View file

@ -15,7 +15,7 @@ Supports::Supports(NonnullOwnPtr<BooleanExpression>&& condition)
m_matches = m_condition->evaluate_to_boolean(nullptr); m_matches = m_condition->evaluate_to_boolean(nullptr);
} }
MatchResult Supports::Declaration::evaluate(HTML::Window const*) const MatchResult Supports::Declaration::evaluate(DOM::Document const*) const
{ {
return as_match_result(m_matches); return as_match_result(m_matches);
} }
@ -31,7 +31,7 @@ void Supports::Declaration::dump(StringBuilder& builder, int indent_levels) cons
builder.appendff("Declaration: `{}`, matches={}\n", m_declaration, m_matches); builder.appendff("Declaration: `{}`, matches={}\n", m_declaration, m_matches);
} }
MatchResult Supports::Selector::evaluate(HTML::Window const*) const MatchResult Supports::Selector::evaluate(DOM::Document const*) const
{ {
return as_match_result(m_matches); return as_match_result(m_matches);
} }
@ -47,7 +47,7 @@ void Supports::Selector::dump(StringBuilder& builder, int indent_levels) const
builder.appendff("Selector: `{}` matches={}\n", m_selector, m_matches); builder.appendff("Selector: `{}` matches={}\n", m_selector, m_matches);
} }
MatchResult Supports::FontTech::evaluate(HTML::Window const*) const MatchResult Supports::FontTech::evaluate(DOM::Document const*) const
{ {
return as_match_result(m_matches); return as_match_result(m_matches);
} }
@ -63,7 +63,7 @@ void Supports::FontTech::dump(StringBuilder& builder, int indent_levels) const
builder.appendff("FontTech: `{}` matches={}\n", m_tech, m_matches); builder.appendff("FontTech: `{}` matches={}\n", m_tech, m_matches);
} }
MatchResult Supports::FontFormat::evaluate(HTML::Window const*) const MatchResult Supports::FontFormat::evaluate(DOM::Document const*) const
{ {
return as_match_result(m_matches); return as_match_result(m_matches);
} }

View file

@ -26,7 +26,7 @@ public:
} }
virtual ~Declaration() override = default; virtual ~Declaration() override = default;
virtual MatchResult evaluate(HTML::Window const*) const override; virtual MatchResult evaluate(DOM::Document const*) const override;
virtual String to_string() const override; virtual String to_string() const override;
virtual void dump(StringBuilder&, int indent_levels = 0) const override; virtual void dump(StringBuilder&, int indent_levels = 0) const override;
@ -48,7 +48,7 @@ public:
} }
virtual ~Selector() override = default; virtual ~Selector() override = default;
virtual MatchResult evaluate(HTML::Window const*) const override; virtual MatchResult evaluate(DOM::Document const*) const override;
virtual String to_string() const override; virtual String to_string() const override;
virtual void dump(StringBuilder&, int indent_levels = 0) const override; virtual void dump(StringBuilder&, int indent_levels = 0) const override;
@ -70,7 +70,7 @@ public:
} }
virtual ~FontTech() override = default; virtual ~FontTech() override = default;
virtual MatchResult evaluate(HTML::Window const*) const override; virtual MatchResult evaluate(DOM::Document const*) const override;
virtual String to_string() const override; virtual String to_string() const override;
virtual void dump(StringBuilder&, int indent_levels = 0) const override; virtual void dump(StringBuilder&, int indent_levels = 0) const override;
@ -92,7 +92,7 @@ public:
} }
virtual ~FontFormat() override = default; virtual ~FontFormat() override = default;
virtual MatchResult evaluate(HTML::Window const*) const override; virtual MatchResult evaluate(DOM::Document const*) const override;
virtual String to_string() const override; virtual String to_string() const override;
virtual void dump(StringBuilder&, int indent_levels = 0) const override; virtual void dump(StringBuilder&, int indent_levels = 0) const override;

View file

@ -1675,7 +1675,7 @@ void Document::obtain_theme_color()
auto media = element.attribute(HTML::AttributeNames::media); auto media = element.attribute(HTML::AttributeNames::media);
if (media.has_value()) { if (media.has_value()) {
auto query = parse_media_query(context, media.value()); auto query = parse_media_query(context, media.value());
if (query.is_null() || !window() || !query->evaluate(*window())) if (query.is_null() || !query->evaluate(*this))
return TraversalDecision::Continue; return TraversalDecision::Continue;
} }
@ -3467,13 +3467,9 @@ void Document::evaluate_media_queries_and_report_changes()
void Document::evaluate_media_rules() void Document::evaluate_media_rules()
{ {
auto window = this->window();
if (!window)
return;
bool any_media_queries_changed_match_state = false; bool any_media_queries_changed_match_state = false;
for_each_active_css_style_sheet([&](CSS::CSSStyleSheet& style_sheet, auto) { for_each_active_css_style_sheet([&](CSS::CSSStyleSheet& style_sheet, auto) {
if (style_sheet.evaluate_media_queries(*window)) if (style_sheet.evaluate_media_queries(*this))
any_media_queries_changed_match_state = true; any_media_queries_changed_match_state = true;
}); });

View file

@ -1188,7 +1188,7 @@ static void update_the_source_set(DOM::Element& element)
if (child->has_attribute(HTML::AttributeNames::media)) { if (child->has_attribute(HTML::AttributeNames::media)) {
auto media_query = parse_media_query(CSS::Parser::ParsingParams { element.document() }, auto media_query = parse_media_query(CSS::Parser::ParsingParams { element.document() },
child->get_attribute_value(HTML::AttributeNames::media)); child->get_attribute_value(HTML::AttributeNames::media));
if (!media_query || !element.document().window() || !media_query->evaluate(*element.document().window())) { if (!media_query || !media_query->evaluate(element.document())) {
continue; continue;
} }
} }

View file

@ -2,17 +2,17 @@ Harness status: OK
Found 12 tests Found 12 tests
2 Pass 11 Pass
10 Fail 1 Fail
Pass box should be orange if the calc between px-em in @media was correct Pass box should be orange if the calc between px-em in @media was correct
Fail box should be orange if the calc between vh+em in @media was correct Pass box should be orange if the calc between vh+em in @media was correct
Fail box should be orange if the calc between vw-em in @media was correct Pass box should be orange if the calc between vw-em in @media was correct
Fail box should be orange if the calc between vw+vh in @media was correct Pass box should be orange if the calc between vw+vh in @media was correct
Fail box should be orange if the calc between vh+px in @media was correct Pass box should be orange if the calc between vh+px in @media was correct
Fail box should be orange if the calc between vw+px in @media was correct Pass box should be orange if the calc between vw+px in @media was correct
Pass box should be orange if the calc between px/em*em in @media was correct Pass box should be orange if the calc between px/em*em in @media was correct
Fail box should be orange if the calc between vh*em in @media was correct Pass box should be orange if the calc between vh*em in @media was correct
Fail box should be orange if the calc between vh*vw/em*px/vh in @media was correct Fail box should be orange if the calc between vh*vw/em*px/vh in @media was correct
Fail box should be orange if the calc between vw/px*vh in @media was correct Pass box should be orange if the calc between vw/px*vh in @media was correct
Fail box should be orange if the calc between vh*vw/em*px in @media was correct Pass box should be orange if the calc between vh*vw/em*px in @media was correct
Fail box should be orange if the calc between vw*vh*px*em/px/px/px in @media was correct Pass box should be orange if the calc between vw*vh*px*em/px/px/px in @media was correct

View file

@ -0,0 +1,31 @@
Harness status: OK
Found 26 tests
26 Pass
Pass @media(width:100vw) applies
Pass @media(width:100vi) applies
Pass @media(width:100vmax) applies
Pass @media(width:100svw) applies
Pass @media(width:100svi) applies
Pass @media(width:100svmax) applies
Pass @media(width:100lvw) applies
Pass @media(width:100lvi) applies
Pass @media(width:100lvmax) applies
Pass @media(width:100dvw) applies
Pass @media(width:100dvi) applies
Pass @media(width:100dvmax) applies
Pass @media(height:100vh) applies
Pass @media(height:100vb) applies
Pass @media(height:100vmin) applies
Pass @media(height:100svh) applies
Pass @media(height:100svb) applies
Pass @media(height:100svmin) applies
Pass @media(height:100lvh) applies
Pass @media(height:100lvb) applies
Pass @media(height:100lvmin) applies
Pass @media(height:100dvh) applies
Pass @media(height:100dvb) applies
Pass @media(height:100dvmin) applies
Pass @media(width:90vw) does not apply
Pass @media(height:90vh) does not apply

View file

@ -0,0 +1,77 @@
<!DOCTYPE html>
<title>Viewport units in @media</title>
<link rel="help" href="https://drafts.csswg.org/css-values-4/#viewport-relative-lengths">
<script src="../../resources/testharness.js"></script>
<script src="../../resources/testharnessreport.js"></script>
<style>
iframe {
width: 200px;
height: 100px;
}
</style>
<iframe id=iframe></iframe>
<script>
const doc = iframe.contentDocument;
const win = iframe.contentWindow;
function test_media_query(feature, result, description) {
test((t) => {
t.add_cleanup(() => { doc.body.innerHTML = ''; })
doc.body.innerHTML = `
<style>
body {
color: red;
}
@media (${feature}) {
body {
color: green;
}
}
</style>
`;
assert_equals(win.getComputedStyle(doc.body).color, result);
}, description);
}
function test_media_query_applies(feature) {
test_media_query(feature, 'rgb(0, 128, 0)', `@media(${feature}) applies`);
}
function test_media_query_does_not_apply(feature) {
test_media_query(feature, 'rgb(255, 0, 0)', `@media(${feature}) does not apply`);
}
test_media_query_applies('width:100vw');
test_media_query_applies('width:100vi');
test_media_query_applies('width:100vmax');
test_media_query_applies('width:100svw');
test_media_query_applies('width:100svi');
test_media_query_applies('width:100svmax');
test_media_query_applies('width:100lvw');
test_media_query_applies('width:100lvi');
test_media_query_applies('width:100lvmax');
test_media_query_applies('width:100dvw');
test_media_query_applies('width:100dvi');
test_media_query_applies('width:100dvmax');
test_media_query_applies('height:100vh');
test_media_query_applies('height:100vb');
test_media_query_applies('height:100vmin');
test_media_query_applies('height:100svh');
test_media_query_applies('height:100svb');
test_media_query_applies('height:100svmin');
test_media_query_applies('height:100lvh');
test_media_query_applies('height:100lvb');
test_media_query_applies('height:100lvmin');
test_media_query_applies('height:100dvh');
test_media_query_applies('height:100dvb');
test_media_query_applies('height:100dvmin');
test_media_query_does_not_apply('width:90vw');
test_media_query_does_not_apply('height:90vh');
</script>