LibWeb/CSS: Implement import at-rule supports conditions

This indicates the features the browser must support for the given
stylesheet to be fetched.
This commit is contained in:
Tim Ledbetter 2025-03-19 11:23:33 +00:00 committed by Jelle Raaijmakers
commit c37a47f76f
Notes: github-actions[bot] 2025-03-19 15:43:58 +00:00
7 changed files with 95 additions and 9 deletions

View file

@ -23,16 +23,17 @@ namespace Web::CSS {
GC_DEFINE_ALLOCATOR(CSSImportRule); GC_DEFINE_ALLOCATOR(CSSImportRule);
GC::Ref<CSSImportRule> CSSImportRule::create(URL::URL url, DOM::Document& document) GC::Ref<CSSImportRule> CSSImportRule::create(URL::URL url, DOM::Document& document, RefPtr<Supports> supports)
{ {
auto& realm = document.realm(); auto& realm = document.realm();
return realm.create<CSSImportRule>(move(url), document); return realm.create<CSSImportRule>(move(url), document, supports);
} }
CSSImportRule::CSSImportRule(URL::URL url, DOM::Document& document) CSSImportRule::CSSImportRule(URL::URL url, DOM::Document& document, RefPtr<Supports> supports)
: CSSRule(document.realm(), Type::Import) : CSSRule(document.realm(), Type::Import)
, m_url(move(url)) , m_url(move(url))
, m_document(document) , m_document(document)
, m_supports(supports)
{ {
} }
@ -70,6 +71,11 @@ String CSSImportRule::serialized() const
// 2. The result of performing serialize a URL on the rules location. // 2. The result of performing serialize a URL on the rules location.
serialize_a_url(builder, m_url.to_string()); serialize_a_url(builder, m_url.to_string());
// AD-HOC: Serialize the rule's supports condition if it exists.
// This isn't currently specified, but major browsers include this in their serialization of import rules
if (m_supports)
builder.appendff(" supports({})", m_supports->to_string());
// FIXME: 3. If the rules associated media list is not empty, a single SPACE (U+0020) followed by the result of performing serialize a media query list on the media list. // FIXME: 3. If the rules associated media list is not empty, a single SPACE (U+0020) followed by the result of performing serialize a media query list on the media list.
// 4. The string ";", i.e., SEMICOLON (U+003B). // 4. The string ";", i.e., SEMICOLON (U+003B).
@ -88,7 +94,10 @@ void CSSImportRule::fetch()
VERIFY(parent_style_sheet()); VERIFY(parent_style_sheet());
auto& parent_style_sheet = *this->parent_style_sheet(); auto& parent_style_sheet = *this->parent_style_sheet();
// FIXME: 2. If rule has a <supports-condition>, and that condition is not true, return. // 2. If rule has a <supports-condition>, and that condition is not true, return.
if (m_supports && !m_supports->matches()) {
return;
}
// 3. Let parsedUrl be the result of the URL parser steps with rules URL and parentStylesheets location. // 3. Let parsedUrl be the result of the URL parser steps with rules URL and parentStylesheets location.
// If the algorithm returns an error, return. [CSSOM] // If the algorithm returns an error, return. [CSSOM]

View file

@ -21,7 +21,7 @@ class CSSImportRule final
GC_DECLARE_ALLOCATOR(CSSImportRule); GC_DECLARE_ALLOCATOR(CSSImportRule);
public: public:
[[nodiscard]] static GC::Ref<CSSImportRule> create(URL::URL, DOM::Document&); [[nodiscard]] static GC::Ref<CSSImportRule> create(URL::URL, DOM::Document&, RefPtr<Supports>);
virtual ~CSSImportRule() = default; virtual ~CSSImportRule() = default;
@ -34,7 +34,7 @@ public:
CSSStyleSheet* style_sheet_for_bindings() { return m_style_sheet; } CSSStyleSheet* style_sheet_for_bindings() { return m_style_sheet; }
private: private:
CSSImportRule(URL::URL, DOM::Document&); CSSImportRule(URL::URL, DOM::Document&, RefPtr<Supports>);
virtual void initialize(JS::Realm&) override; virtual void initialize(JS::Realm&) override;
virtual void visit_edges(Cell::Visitor&) override; virtual void visit_edges(Cell::Visitor&) override;
@ -48,6 +48,7 @@ private:
URL::URL m_url; URL::URL m_url;
GC::Ptr<DOM::Document> m_document; GC::Ptr<DOM::Document> m_document;
RefPtr<Supports> m_supports;
GC::Ptr<CSSStyleSheet> m_style_sheet; GC::Ptr<CSSStyleSheet> m_style_sheet;
Optional<DOM::DocumentLoadEventDelayer> m_document_load_event_delayer; Optional<DOM::DocumentLoadEventDelayer> m_document_load_event_delayer;
}; };

View file

@ -161,16 +161,35 @@ GC::Ptr<CSSImportRule> Parser::convert_to_import_rule(AtRule const& rule)
} }
tokens.discard_whitespace(); tokens.discard_whitespace();
// TODO: Support layers and import-conditions // FIXME: Implement layer support.
RefPtr<Supports> supports {};
if (tokens.next_token().is_function("supports"sv)) {
auto component_value = tokens.consume_a_token();
TokenStream supports_tokens { component_value.function().value };
if (supports_tokens.next_token().is_block()) {
supports = parse_a_supports(supports_tokens);
} else {
m_rule_context.append(ContextType::SupportsCondition);
auto declaration = consume_a_declaration(supports_tokens);
m_rule_context.take_last();
if (declaration.has_value()) {
auto supports_declaration = Supports::Declaration::create(declaration->to_string(), convert_to_style_property(*declaration).has_value());
supports = Supports::create(supports_declaration.release_nonnull<BooleanExpression>());
}
}
}
// FIXME: Implement media query support.
if (tokens.has_next_token()) { if (tokens.has_next_token()) {
if constexpr (CSS_PARSER_DEBUG) { if constexpr (CSS_PARSER_DEBUG) {
dbgln("Failed to parse @import rule: Trailing tokens after URL are not yet supported."); dbgln("Failed to parse @import rule:");
tokens.dump_all_tokens(); tokens.dump_all_tokens();
} }
return {}; return {};
} }
return CSSImportRule::create(url.value(), const_cast<DOM::Document&>(*document())); return CSSImportRule::create(url.value(), const_cast<DOM::Document&>(*document()), supports);
} }
Optional<FlyString> Parser::parse_layer_name(TokenStream<ComponentValue>& tokens, AllowBlankLayerName allow_blank_layer_name) Optional<FlyString> Parser::parse_layer_name(TokenStream<ComponentValue>& tokens, AllowBlankLayerName allow_blank_layer_name)

View file

@ -0,0 +1,19 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>CSS Reftest Reference</title>
<link rel="author" title="Gérard Talbot" href="http://www.gtalbot.org/BrowserBugsSection/css21testsuite/" />
<style type="text/css"><![CDATA[
div
{
background-color: green;
height: 100px;
width: 100px;
}
]]></style>
</head>
<body>
<p>Test passes if there is a filled green square and <strong>no red</strong>.</p>
<div></div>
</body>
</html>

View file

@ -0,0 +1,30 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>CSS Cascade: @import with basic supports condition</title>
<link rel="help" href="https://www.w3.org/TR/css-cascade-4/#conditional-import">
<link rel="help" href="https://www.w3.org/TR/css-cascade-5/#conditional-import">
<link rel="match" href="../../../../expected/wpt-import/css/css-cascade/reference/ref-filled-green-100px-square.xht">
<meta name="assert" content="Test passes on visual UAs if @import can be combined with a supports condition.">
<style>
@import "support/test-red.css";
@import "support/test-green.css"
supports(display: block);
@import "support/test-red.css"
supports(foo: bar);
div {
box-sizing: border-box;
width: 100px;
height: 100px;
padding: 5px; /* Avoids text antialiasing issues */
background: red;
}
</style>
</head>
<body>
<p>Test passes if there is a filled green square and <strong>no red</strong>.</p>
<div class="test"></div>
</body>
</html>

View file

@ -0,0 +1,4 @@
.test {
background: green;
color: green;
}

View file

@ -0,0 +1,4 @@
.test {
background: red;
color: red;
}