LibWeb: Parse scoped CSS import rules
Parse and store the optional `scope` clause in `@import` preludes. WPT doesn't directly test the serialization behaviour for this, so add a custom test for it.
This commit is contained in:
parent
bec9b35e56
commit
538dca4448
10 changed files with 325 additions and 55 deletions
|
|
@ -28,16 +28,17 @@ namespace Web::CSS {
|
|||
|
||||
GC_DEFINE_ALLOCATOR(CSSImportRule);
|
||||
|
||||
GC::Ref<CSSImportRule> CSSImportRule::create(JS::Realm& realm, URL url, GC::Ptr<DOM::Document> document, Optional<FlyString> layer, RefPtr<Supports> supports, GC::Ref<MediaList> media)
|
||||
GC::Ref<CSSImportRule> CSSImportRule::create(JS::Realm& realm, URL url, GC::Ptr<DOM::Document> document, Optional<FlyString> layer, Optional<ImportScope>&& scope, RefPtr<Supports> supports, GC::Ref<MediaList> media)
|
||||
{
|
||||
return realm.create<CSSImportRule>(realm, move(url), document, move(layer), move(supports), move(media));
|
||||
return realm.create<CSSImportRule>(realm, move(url), document, move(layer), move(scope), move(supports), move(media));
|
||||
}
|
||||
|
||||
CSSImportRule::CSSImportRule(JS::Realm& realm, URL url, GC::Ptr<DOM::Document> document, Optional<FlyString> layer, RefPtr<Supports> supports, GC::Ref<MediaList> media)
|
||||
CSSImportRule::CSSImportRule(JS::Realm& realm, URL url, GC::Ptr<DOM::Document> document, Optional<FlyString> layer, Optional<ImportScope>&& scope, RefPtr<Supports> supports, GC::Ref<MediaList> media)
|
||||
: CSSRule(realm, Type::Import)
|
||||
, m_url(move(url))
|
||||
, m_document(document)
|
||||
, m_layer(move(layer))
|
||||
, m_scope(move(scope))
|
||||
, m_supports(move(supports))
|
||||
, m_media(move(media))
|
||||
{
|
||||
|
|
@ -108,6 +109,26 @@ String CSSImportRule::serialized() const
|
|||
}
|
||||
}
|
||||
|
||||
// AD-HOC: Serialize the rule's import scope if it exists.
|
||||
if (m_scope.has_value()) {
|
||||
builder.append(" scope"sv);
|
||||
if (m_scope->start_selectors.has_value() || m_scope->end_selectors.has_value()) {
|
||||
builder.append('(');
|
||||
if (m_scope->start_selectors.has_value()) {
|
||||
if (m_scope->end_selectors.has_value())
|
||||
builder.appendff("({})", serialize_a_group_of_selectors(*m_scope->start_selectors));
|
||||
else
|
||||
builder.append(serialize_a_group_of_selectors(*m_scope->start_selectors));
|
||||
}
|
||||
if (m_scope->end_selectors.has_value()) {
|
||||
if (m_scope->start_selectors.has_value())
|
||||
builder.append(' ');
|
||||
builder.appendff("to ({})", serialize_a_group_of_selectors(*m_scope->end_selectors));
|
||||
}
|
||||
builder.append(')');
|
||||
}
|
||||
}
|
||||
|
||||
// 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)
|
||||
|
|
@ -262,6 +283,18 @@ Optional<String> CSSImportRule::supports_text() const
|
|||
return m_supports->to_string();
|
||||
}
|
||||
|
||||
Optional<SelectorList> const& CSSImportRule::scope_start_selectors() const
|
||||
{
|
||||
VERIFY(m_scope.has_value());
|
||||
return m_scope->start_selectors;
|
||||
}
|
||||
|
||||
Optional<SelectorList> const& CSSImportRule::scope_end_selectors() const
|
||||
{
|
||||
VERIFY(m_scope.has_value());
|
||||
return m_scope->end_selectors;
|
||||
}
|
||||
|
||||
Optional<FlyString> CSSImportRule::internal_qualified_layer_name(Badge<StyleScope>) const
|
||||
{
|
||||
if (!m_layer.has_value())
|
||||
|
|
@ -301,6 +334,23 @@ void CSSImportRule::dump(StringBuilder& builder, int indent_levels) const
|
|||
if (m_supports)
|
||||
m_supports->dump(builder, indent_levels + 1);
|
||||
|
||||
if (m_scope.has_value()) {
|
||||
dump_indent(builder, indent_levels + 1);
|
||||
builder.append("Scope:\n"sv);
|
||||
|
||||
dump_indent(builder, indent_levels + 2);
|
||||
if (m_scope->start_selectors.has_value())
|
||||
builder.appendff("Start selectors: {}\n", serialize_a_group_of_selectors(*m_scope->start_selectors));
|
||||
else
|
||||
builder.append("Start selectors: <none>\n"sv);
|
||||
|
||||
dump_indent(builder, indent_levels + 2);
|
||||
if (m_scope->end_selectors.has_value())
|
||||
builder.appendff("End selectors: {}\n", serialize_a_group_of_selectors(*m_scope->end_selectors));
|
||||
else
|
||||
builder.append("End selectors: <none>\n"sv);
|
||||
}
|
||||
|
||||
if (m_style_sheet) {
|
||||
dump_sheet(builder, *m_style_sheet, indent_levels + 1);
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@
|
|||
|
||||
#include <LibWeb/CSS/CSSRule.h>
|
||||
#include <LibWeb/CSS/CSSStyleSheet.h>
|
||||
#include <LibWeb/CSS/Selector.h>
|
||||
#include <LibWeb/CSS/URL.h>
|
||||
#include <LibWeb/Export.h>
|
||||
#include <LibWeb/Forward.h>
|
||||
|
|
@ -23,7 +24,12 @@ class WEB_API CSSImportRule final
|
|||
GC_DECLARE_ALLOCATOR(CSSImportRule);
|
||||
|
||||
public:
|
||||
[[nodiscard]] static GC::Ref<CSSImportRule> create(JS::Realm&, URL, GC::Ptr<DOM::Document>, Optional<FlyString> layer, RefPtr<Supports>, GC::Ref<MediaList>);
|
||||
struct ImportScope {
|
||||
Optional<SelectorList> start_selectors;
|
||||
Optional<SelectorList> end_selectors;
|
||||
};
|
||||
|
||||
[[nodiscard]] static GC::Ref<CSSImportRule> create(JS::Realm&, URL, GC::Ptr<DOM::Document>, Optional<FlyString> layer, Optional<ImportScope>&& scope, RefPtr<Supports>, GC::Ref<MediaList>);
|
||||
|
||||
virtual ~CSSImportRule() override;
|
||||
|
||||
|
|
@ -39,12 +45,15 @@ public:
|
|||
Optional<String> supports_text() const;
|
||||
|
||||
bool matches() const;
|
||||
bool has_scope() const { return m_scope.has_value(); }
|
||||
Optional<SelectorList> const& scope_start_selectors() const;
|
||||
Optional<SelectorList> const& scope_end_selectors() const;
|
||||
|
||||
Optional<FlyString> internal_layer_name() const { return m_layer_internal; }
|
||||
Optional<FlyString> internal_qualified_layer_name(Badge<StyleScope>) const;
|
||||
|
||||
private:
|
||||
CSSImportRule(JS::Realm&, URL, GC::Ptr<DOM::Document>, Optional<FlyString>, RefPtr<Supports>, GC::Ref<MediaList>);
|
||||
CSSImportRule(JS::Realm&, URL, GC::Ptr<DOM::Document>, Optional<FlyString>, Optional<ImportScope>&&, RefPtr<Supports>, GC::Ref<MediaList>);
|
||||
|
||||
virtual void initialize(JS::Realm&) override;
|
||||
virtual void visit_edges(Cell::Visitor&) override;
|
||||
|
|
@ -63,6 +72,7 @@ private:
|
|||
GC::Ptr<DOM::Document> m_document;
|
||||
Optional<FlyString> m_layer;
|
||||
Optional<FlyString> m_layer_internal;
|
||||
Optional<ImportScope> m_scope;
|
||||
RefPtr<Supports> m_supports;
|
||||
GC::Ref<MediaList> m_media;
|
||||
GC::Ptr<CSSStyleSheet> m_style_sheet;
|
||||
|
|
|
|||
|
|
@ -237,15 +237,23 @@ GC::Ptr<CSSStyleRule> Parser::convert_to_style_rule(QualifiedRule const& qualifi
|
|||
return CSSStyleRule::create(realm(), move(selectors), *declaration, *nested_rules);
|
||||
}
|
||||
|
||||
static bool selector_list_contains_pseudo_element(SelectorList const& selectors)
|
||||
{
|
||||
for (auto const& selector : selectors) {
|
||||
if (selector->target_pseudo_element().has_value())
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
GC::Ptr<CSSImportRule> Parser::convert_to_import_rule(AtRule const& rule)
|
||||
{
|
||||
// https://drafts.csswg.org/css-cascade-5/#at-import
|
||||
// https://drafts.csswg.org/css-cascade-6/#at-import
|
||||
// @import [ <url> | <string> ]
|
||||
// [ layer | layer(<layer-name>) ]?
|
||||
// <import-conditions> ;
|
||||
//
|
||||
// <import-conditions> = [ supports( [ <supports-condition> | <declaration> ] ) ]?
|
||||
// <media-query-list>?
|
||||
// [[ layer | layer(<layer-name>) ]
|
||||
// || [ scope | scope(<scope-start> | <scope-boundaries>) ]
|
||||
// || supports( [ <supports-condition> | <declaration> ] ) ]?
|
||||
// <media-import-condition> ;
|
||||
TokenStream tokens { rule.prelude };
|
||||
|
||||
if (rule.is_block_rule) {
|
||||
|
|
@ -283,43 +291,167 @@ GC::Ptr<CSSImportRule> Parser::convert_to_import_rule(AtRule const& rule)
|
|||
|
||||
tokens.discard_whitespace();
|
||||
Optional<FlyString> layer;
|
||||
// [ layer | layer(<layer-name>) ]?
|
||||
if (tokens.next_token().is_ident("layer"sv)) {
|
||||
tokens.discard_a_token(); // layer
|
||||
layer = FlyString {};
|
||||
} else if (tokens.next_token().is_function("layer"sv)) {
|
||||
Optional<CSSImportRule::ImportScope> scope;
|
||||
RefPtr<Supports> supports {};
|
||||
|
||||
auto parse_scope_selector_list = [&](TokenStream<ComponentValue>& selector_tokens, SelectorType selector_type) -> Optional<SelectorList> {
|
||||
auto maybe_selectors = parse_a_selector_list(selector_tokens, selector_type);
|
||||
selector_tokens.discard_whitespace();
|
||||
if (maybe_selectors.is_error() || maybe_selectors.value().is_empty() || selector_tokens.has_next_token())
|
||||
return {};
|
||||
|
||||
auto selectors = maybe_selectors.release_value();
|
||||
if (selector_list_contains_pseudo_element(selectors))
|
||||
return {};
|
||||
return selectors;
|
||||
};
|
||||
|
||||
auto parse_parenthesized_scope_selector_list = [&](TokenStream<ComponentValue>& selector_tokens, SelectorType selector_type) -> Optional<SelectorList> {
|
||||
if (!(selector_tokens.next_token().is_block() && selector_tokens.next_token().block().is_paren()))
|
||||
return {};
|
||||
|
||||
auto const& selector_block = selector_tokens.consume_a_token().block();
|
||||
TokenStream block_tokens { selector_block.value };
|
||||
return parse_scope_selector_list(block_tokens, selector_type);
|
||||
};
|
||||
|
||||
auto contains_unparenthesized_scope_boundary_keyword = [](Vector<ComponentValue> const& component_values) {
|
||||
ComponentValue const* previous_non_whitespace_token = nullptr;
|
||||
for (auto const& component_value : component_values) {
|
||||
if (component_value.is(Token::Type::Whitespace))
|
||||
continue;
|
||||
|
||||
if (component_value.is_ident("to"sv)) {
|
||||
if (!previous_non_whitespace_token)
|
||||
return true;
|
||||
if (!previous_non_whitespace_token->is_delim('.') && !previous_non_whitespace_token->is(Token::Type::Colon))
|
||||
return true;
|
||||
}
|
||||
|
||||
previous_non_whitespace_token = &component_value;
|
||||
}
|
||||
|
||||
return false;
|
||||
};
|
||||
|
||||
auto parse_layer = [&]() -> bool {
|
||||
if (layer.has_value())
|
||||
return false;
|
||||
|
||||
if (tokens.next_token().is_ident("layer"sv)) {
|
||||
tokens.discard_a_token(); // layer
|
||||
layer = FlyString {};
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!tokens.next_token().is_function("layer"sv))
|
||||
return false;
|
||||
|
||||
auto layer_transaction = tokens.begin_transaction();
|
||||
auto& layer_function = tokens.consume_a_token().function();
|
||||
TokenStream layer_tokens { layer_function.value };
|
||||
auto name = parse_layer_name(layer_tokens, AllowBlankLayerName::No);
|
||||
layer_tokens.discard_whitespace();
|
||||
if (!name.has_value() || layer_tokens.has_next_token()) {
|
||||
ErrorReporter::the().report(CSS::Parser::InvalidRuleError {
|
||||
.rule_name = "@import"_fly_string,
|
||||
.prelude = tokens.dump_string(),
|
||||
.description = MUST(String::formatted("Unable to parse `{}` as a valid layer.", layer_function.original_source_text())),
|
||||
});
|
||||
} else {
|
||||
layer_transaction.commit();
|
||||
layer = name.release_value();
|
||||
}
|
||||
}
|
||||
if (!name.has_value() || layer_tokens.has_next_token())
|
||||
return false;
|
||||
|
||||
// <import-conditions> = [ supports( [ <supports-condition> | <declaration> ] ) ]?
|
||||
// <media-query-list>?
|
||||
tokens.discard_whitespace();
|
||||
RefPtr<Supports> supports {};
|
||||
if (tokens.next_token().is_function("supports"sv)) {
|
||||
layer_transaction.commit();
|
||||
layer = name.release_value();
|
||||
return true;
|
||||
};
|
||||
|
||||
auto parse_scope = [&]() -> bool {
|
||||
if (scope.has_value())
|
||||
return false;
|
||||
|
||||
if (tokens.next_token().is_ident("scope"sv)) {
|
||||
tokens.discard_a_token(); // scope
|
||||
scope = CSSImportRule::ImportScope {};
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!tokens.next_token().is_function("scope"sv))
|
||||
return false;
|
||||
|
||||
auto scope_transaction = tokens.begin_transaction();
|
||||
auto& scope_function = tokens.consume_a_token().function();
|
||||
TokenStream scope_tokens { scope_function.value };
|
||||
CSSImportRule::ImportScope parsed_scope;
|
||||
|
||||
scope_tokens.discard_whitespace();
|
||||
if (scope_tokens.is_empty()) {
|
||||
scope_transaction.commit();
|
||||
scope = move(parsed_scope);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (scope_tokens.next_token().is_block() && scope_tokens.next_token().block().is_paren()) {
|
||||
auto start_selectors = parse_parenthesized_scope_selector_list(scope_tokens, SelectorType::Standalone);
|
||||
if (!start_selectors.has_value())
|
||||
return false;
|
||||
parsed_scope.start_selectors = start_selectors.release_value();
|
||||
scope_tokens.discard_whitespace();
|
||||
}
|
||||
|
||||
if (scope_tokens.next_token().is_ident("to"sv)) {
|
||||
scope_tokens.discard_a_token(); // to
|
||||
scope_tokens.discard_whitespace();
|
||||
auto end_selectors = parse_parenthesized_scope_selector_list(scope_tokens, SelectorType::Relative);
|
||||
if (!end_selectors.has_value())
|
||||
return false;
|
||||
parsed_scope.end_selectors = end_selectors.release_value();
|
||||
scope_tokens.discard_whitespace();
|
||||
}
|
||||
|
||||
if (!parsed_scope.start_selectors.has_value() && !parsed_scope.end_selectors.has_value()) {
|
||||
if (contains_unparenthesized_scope_boundary_keyword(scope_function.value))
|
||||
return false;
|
||||
|
||||
auto start_selectors = parse_scope_selector_list(scope_tokens, SelectorType::Standalone);
|
||||
if (!start_selectors.has_value())
|
||||
return false;
|
||||
parsed_scope.start_selectors = start_selectors.release_value();
|
||||
}
|
||||
|
||||
if (scope_tokens.has_next_token())
|
||||
return false;
|
||||
|
||||
scope_transaction.commit();
|
||||
scope = move(parsed_scope);
|
||||
return true;
|
||||
};
|
||||
|
||||
auto parse_supports = [&]() -> bool {
|
||||
if (supports)
|
||||
return false;
|
||||
if (!tokens.next_token().is_function("supports"sv))
|
||||
return false;
|
||||
|
||||
auto supports_transaction = tokens.begin_transaction();
|
||||
auto component_value = tokens.consume_a_token();
|
||||
TokenStream supports_tokens { component_value.function().value };
|
||||
supports = parse_a_supports(supports_tokens);
|
||||
if (!supports) {
|
||||
auto parsed_supports = parse_a_supports(supports_tokens);
|
||||
if (!parsed_supports) {
|
||||
m_rule_context.append(RuleContext::SupportsCondition);
|
||||
auto supports_declaration = parse_supports_declaration(supports_tokens);
|
||||
m_rule_context.take_last();
|
||||
if (supports_declaration)
|
||||
supports = Supports::create(supports_declaration.release_nonnull<BooleanExpression>());
|
||||
parsed_supports = Supports::create(supports_declaration.release_nonnull<BooleanExpression>());
|
||||
}
|
||||
|
||||
if (!parsed_supports)
|
||||
return false;
|
||||
|
||||
supports_transaction.commit();
|
||||
supports = move(parsed_supports);
|
||||
return true;
|
||||
};
|
||||
|
||||
while (true) {
|
||||
tokens.discard_whitespace();
|
||||
if (parse_layer() || parse_scope() || parse_supports())
|
||||
continue;
|
||||
break;
|
||||
}
|
||||
|
||||
auto media_query_list = parse_a_media_query_list(tokens);
|
||||
|
|
@ -333,7 +465,7 @@ GC::Ptr<CSSImportRule> Parser::convert_to_import_rule(AtRule const& rule)
|
|||
return {};
|
||||
}
|
||||
|
||||
return CSSImportRule::create(realm(), url.release_value(), const_cast<DOM::Document*>(m_document.ptr()), move(layer), move(supports), MediaList::create(realm(), move(media_query_list)));
|
||||
return CSSImportRule::create(realm(), url.release_value(), const_cast<DOM::Document*>(m_document.ptr()), move(layer), move(scope), move(supports), MediaList::create(realm(), move(media_query_list)));
|
||||
}
|
||||
|
||||
Optional<FlyString> Parser::parse_layer_name(TokenStream<ComponentValue>& tokens, AllowBlankLayerName allow_blank_layer_name)
|
||||
|
|
@ -885,15 +1017,6 @@ GC::Ptr<CSSPropertyRule> Parser::convert_to_property_rule(AtRule const& rule)
|
|||
return CSSPropertyRule::create(realm(), name, syntax_maybe.value(), inherits_maybe.value(), move(initial_value_maybe));
|
||||
}
|
||||
|
||||
static bool selector_list_contains_pseudo_element(SelectorList const& selectors)
|
||||
{
|
||||
for (auto const& selector : selectors) {
|
||||
if (selector->target_pseudo_element().has_value())
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// https://drafts.csswg.org/css-cascade-6/#scope-atrule
|
||||
template<typename NestedDeclarationsRule>
|
||||
GC::Ptr<CSSScopeRule> Parser::convert_to_scope_rule(AtRule const& rule, Nested nested)
|
||||
|
|
|
|||
23
Tests/LibWeb/Text/expected/css/CSSImportRule-scope.txt
Normal file
23
Tests/LibWeb/Text/expected/css/CSSImportRule-scope.txt
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
PASS all scoped imports parsed
|
||||
PASS bare scope import rule parsed
|
||||
PASS bare scope import rule serialized as scoped
|
||||
PASS empty scope() import rule parsed
|
||||
PASS empty scope() import rule serialized as scoped
|
||||
PASS start selector import rule parsed
|
||||
PASS start selector import rule serialized as scoped
|
||||
PASS start selector list import rule parsed
|
||||
PASS start selector list import rule serialized as scoped
|
||||
PASS end selector import rule parsed
|
||||
PASS end selector import rule serialized as scoped
|
||||
PASS start and end selector import rule parsed
|
||||
PASS start and end selector import rule serialized as scoped
|
||||
PASS combined modifier import rule parsed
|
||||
PASS combined modifier import rule has layer
|
||||
PASS combined modifier import rule has supports
|
||||
PASS combined modifier import rule has media
|
||||
PASS combined modifier import rule serialized as scoped
|
||||
PASS unordered modifier import rule parsed
|
||||
PASS unordered modifier import rule has layer
|
||||
PASS unordered modifier import rule has supports
|
||||
PASS unordered modifier import rule has media
|
||||
PASS unordered modifier import rule serialized as scoped
|
||||
|
|
@ -4,5 +4,5 @@ Found 2 tests
|
|||
|
||||
1 Pass
|
||||
1 Fail
|
||||
Fail Scope-imported rule applies within implicit scope
|
||||
Pass Scope-imported rule does not apply outside of implicit scope
|
||||
Pass Scope-imported rule applies within implicit scope
|
||||
Fail Scope-imported rule does not apply outside of implicit scope
|
||||
|
|
@ -4,5 +4,5 @@ Found 2 tests
|
|||
|
||||
1 Pass
|
||||
1 Fail
|
||||
Fail @scope within scope-imported stylesheet matches
|
||||
Pass @scope within scope-imported does not ignore import scope
|
||||
Pass @scope within scope-imported stylesheet matches
|
||||
Fail @scope within scope-imported does not ignore import scope
|
||||
|
|
@ -4,5 +4,5 @@ Found 2 tests
|
|||
|
||||
1 Pass
|
||||
1 Fail
|
||||
Fail A stylesheet may be imported multiple times, and scoped differently
|
||||
Pass Scope-imported rule does not apply outside of scope
|
||||
Pass A stylesheet may be imported multiple times, and scoped differently
|
||||
Fail Scope-imported rule does not apply outside of scope
|
||||
|
|
@ -4,5 +4,5 @@ Found 2 tests
|
|||
|
||||
1 Pass
|
||||
1 Fail
|
||||
Fail Scope-imported rule applies within scope, above limit
|
||||
Pass Scope-imported rule does not apply below limit
|
||||
Pass Scope-imported rule applies within scope, above limit
|
||||
Fail Scope-imported rule does not apply below limit
|
||||
|
|
@ -4,5 +4,5 @@ Found 2 tests
|
|||
|
||||
1 Pass
|
||||
1 Fail
|
||||
Fail Scope-imported rule applies within scope
|
||||
Pass Scope-imported rule does not apply outside of scope
|
||||
Pass Scope-imported rule applies within scope
|
||||
Fail Scope-imported rule does not apply outside of scope
|
||||
64
Tests/LibWeb/Text/input/css/CSSImportRule-scope.html
Normal file
64
Tests/LibWeb/Text/input/css/CSSImportRule-scope.html
Normal file
|
|
@ -0,0 +1,64 @@
|
|||
<!DOCTYPE html>
|
||||
<style>
|
||||
@import url("data:text/css,") scope;
|
||||
@import url("data:text/css,") scope();
|
||||
@import url("data:text/css,") scope(.scope);
|
||||
@import url("data:text/css,") scope(.scope, .other);
|
||||
@import url("data:text/css,") scope(to (.limit));
|
||||
@import url("data:text/css,") scope((.scope) to (.limit));
|
||||
@import url("data:text/css,") layer(foo) supports(display: block) scope(.scope) screen;
|
||||
@import url("data:text/css,") supports(display: block) scope(.scope) layer(bar) screen;
|
||||
</style>
|
||||
<script src="../include.js"></script>
|
||||
<script>
|
||||
function expect(condition, description) {
|
||||
println(`${condition ? "PASS" : "FAIL"} ${description}`);
|
||||
}
|
||||
|
||||
function expectCSSImportRule(rule, description) {
|
||||
expect(rule instanceof CSSImportRule, description);
|
||||
}
|
||||
|
||||
function expectScopedImport(rule, scopeText, description) {
|
||||
expect(rule.cssText.includes(scopeText), description);
|
||||
}
|
||||
|
||||
function expectImplicitScopedImport(rule, description) {
|
||||
expect(rule.cssText.includes(" scope"), description);
|
||||
}
|
||||
|
||||
test(() => {
|
||||
let rules = document.styleSheets[0].cssRules;
|
||||
expect(rules.length === 8, "all scoped imports parsed");
|
||||
|
||||
expectCSSImportRule(rules[0], "bare scope import rule parsed");
|
||||
expectImplicitScopedImport(rules[0], "bare scope import rule serialized as scoped");
|
||||
|
||||
expectCSSImportRule(rules[1], "empty scope() import rule parsed");
|
||||
expectImplicitScopedImport(rules[1], "empty scope() import rule serialized as scoped");
|
||||
|
||||
expectCSSImportRule(rules[2], "start selector import rule parsed");
|
||||
expectScopedImport(rules[2], "scope(.scope)", "start selector import rule serialized as scoped");
|
||||
|
||||
expectCSSImportRule(rules[3], "start selector list import rule parsed");
|
||||
expectScopedImport(rules[3], "scope(.scope, .other)", "start selector list import rule serialized as scoped");
|
||||
|
||||
expectCSSImportRule(rules[4], "end selector import rule parsed");
|
||||
expectScopedImport(rules[4], "scope(to (.limit))", "end selector import rule serialized as scoped");
|
||||
|
||||
expectCSSImportRule(rules[5], "start and end selector import rule parsed");
|
||||
expectScopedImport(rules[5], "scope((.scope) to (.limit))", "start and end selector import rule serialized as scoped");
|
||||
|
||||
expectCSSImportRule(rules[6], "combined modifier import rule parsed");
|
||||
expect(rules[6].layerName === "foo", "combined modifier import rule has layer");
|
||||
expect(rules[6].supportsText === "display: block", "combined modifier import rule has supports");
|
||||
expect(rules[6].media.mediaText === "screen", "combined modifier import rule has media");
|
||||
expectScopedImport(rules[6], "scope(.scope)", "combined modifier import rule serialized as scoped");
|
||||
|
||||
expectCSSImportRule(rules[7], "unordered modifier import rule parsed");
|
||||
expect(rules[7].layerName === "bar", "unordered modifier import rule has layer");
|
||||
expect(rules[7].supportsText === "display: block", "unordered modifier import rule has supports");
|
||||
expect(rules[7].media.mediaText === "screen", "unordered modifier import rule has media");
|
||||
expectScopedImport(rules[7], "scope(.scope)", "unordered modifier import rule serialized as scoped");
|
||||
});
|
||||
</script>
|
||||
Loading…
Reference in a new issue