LibWeb: Apply scopes from imported stylesheets

Treat a scoped `@import` rule as a scope descriptor while traversing
style-producing rules from imported stylesheets. Imported rules now
inherit an outer scope for unscoped imports and replace it when the
import itself carries scope(...).

Scope resolution was generalized to handle both CSSScopeRule and
CSSImportRule descriptors. Import-scope boundaries are matched using
the stylesheet that parsed the `@import` rule, while normal imported
selectors keep using the imported stylesheet context. This lets
implicit scopes, nested `@scope` rules, :scope, and top-level `&` behave
as the Cascade 6 model requires.
This commit is contained in:
Sam Atkins 2026-06-04 17:22:45 +01:00
parent 538dca4448
commit 85e14f5f1f
15 changed files with 148 additions and 58 deletions

View file

@ -14,6 +14,7 @@
#include <LibWeb/Bindings/Intrinsics.h>
#include <LibWeb/CSS/CSSImportRule.h>
#include <LibWeb/CSS/CSSLayerBlockRule.h>
#include <LibWeb/CSS/CSSScopeRule.h>
#include <LibWeb/CSS/Fetch.h>
#include <LibWeb/CSS/Parser/Parser.h>
#include <LibWeb/CSS/StyleComputer.h>
@ -295,6 +296,28 @@ Optional<SelectorList> const& CSSImportRule::scope_end_selectors() const
return m_scope->end_selectors;
}
Optional<SelectorList> const& CSSImportRule::scope_start_selectors_for_matching() const
{
VERIFY(m_scope.has_value());
if (!m_scope->start_selectors.has_value())
return m_scope->start_selectors;
if (!m_cached_scope_start_selectors_for_matching.has_value())
m_cached_scope_start_selectors_for_matching = absolutize_selectors_relative_to(*m_scope->start_selectors, nullptr);
return m_cached_scope_start_selectors_for_matching;
}
Optional<SelectorList> const& CSSImportRule::scope_end_selectors_for_matching() const
{
VERIFY(m_scope.has_value());
if (!m_scope->end_selectors.has_value())
return m_scope->end_selectors;
if (!m_cached_scope_end_selectors_for_matching.has_value())
m_cached_scope_end_selectors_for_matching = adapt_scope_end_selectors_for_matching(*m_scope->end_selectors);
return m_cached_scope_end_selectors_for_matching;
}
Optional<FlyString> CSSImportRule::internal_qualified_layer_name(Badge<StyleScope>) const
{
if (!m_layer.has_value())
@ -313,6 +336,13 @@ bool CSSImportRule::matches() const
return m_media->matches();
}
void CSSImportRule::clear_caches()
{
Base::clear_caches();
m_cached_scope_start_selectors_for_matching.clear();
m_cached_scope_end_selectors_for_matching.clear();
}
void CSSImportRule::dump(StringBuilder& builder, int indent_levels) const
{
Base::dump(builder, indent_levels);

View file

@ -48,6 +48,8 @@ public:
bool has_scope() const { return m_scope.has_value(); }
Optional<SelectorList> const& scope_start_selectors() const;
Optional<SelectorList> const& scope_end_selectors() const;
Optional<SelectorList> const& scope_start_selectors_for_matching() const;
Optional<SelectorList> const& scope_end_selectors_for_matching() const;
Optional<FlyString> internal_layer_name() const { return m_layer_internal; }
Optional<FlyString> internal_qualified_layer_name(Badge<StyleScope>) const;
@ -57,6 +59,7 @@ private:
virtual void initialize(JS::Realm&) override;
virtual void visit_edges(Cell::Visitor&) override;
virtual void clear_caches() override;
virtual void dump(StringBuilder&, int indent_levels) const override;
virtual void set_parent_style_sheet(CSSStyleSheet*) override;
@ -73,6 +76,8 @@ private:
Optional<FlyString> m_layer;
Optional<FlyString> m_layer_internal;
Optional<ImportScope> m_scope;
mutable Optional<SelectorList> m_cached_scope_start_selectors_for_matching;
mutable Optional<SelectorList> m_cached_scope_end_selectors_for_matching;
RefPtr<Supports> m_supports;
GC::Ref<MediaList> m_media;
GC::Ptr<CSSStyleSheet> m_style_sheet;

View file

@ -59,6 +59,7 @@ public:
void set_parent_rule(CSSRule*);
CSSStyleSheet* parent_style_sheet() { return m_parent_style_sheet.ptr(); }
CSSStyleSheet const* parent_style_sheet() const { return m_parent_style_sheet.ptr(); }
MUST_UPCALL virtual void set_parent_style_sheet(CSSStyleSheet*);
template<typename T>

View file

@ -8,7 +8,9 @@
#include <LibJS/Runtime/Realm.h>
#include <LibWeb/Bindings/CSSScopeRule.h>
#include <LibWeb/Bindings/Intrinsics.h>
#include <LibWeb/CSS/CSSImportRule.h>
#include <LibWeb/CSS/CSSRuleList.h>
#include <LibWeb/CSS/CSSStyleSheet.h>
#include <LibWeb/Dump.h>
namespace Web::CSS {
@ -65,7 +67,7 @@ Optional<SelectorList> const& CSSScopeRule::start_selectors_for_matching() const
return m_cached_start_selectors_for_matching;
}
static SelectorList adapt_end_selectors(SelectorList const& selectors)
SelectorList adapt_scope_end_selectors_for_matching(SelectorList const& selectors)
{
// NB: This is a modified version of adapt_nested_relative_selector_list().
// End selectors are implicitly prepended with `:scope` if that doesn't already appear.
@ -101,10 +103,53 @@ Optional<SelectorList> const& CSSScopeRule::end_selectors_for_matching() const
return m_end_selectors;
if (!m_cached_end_selectors_for_matching.has_value())
m_cached_end_selectors_for_matching = adapt_end_selectors(*m_end_selectors);
m_cached_end_selectors_for_matching = adapt_scope_end_selectors_for_matching(*m_end_selectors);
return m_cached_end_selectors_for_matching;
}
Optional<SelectorList> const& scope_start_selectors_for_matching(CSSRule const& scope_rule)
{
if (auto const* css_scope_rule = as_if<CSSScopeRule const>(scope_rule))
return css_scope_rule->start_selectors_for_matching();
if (auto const* import_rule = as_if<CSSImportRule const>(scope_rule)) {
VERIFY(import_rule->has_scope());
return import_rule->scope_start_selectors_for_matching();
}
VERIFY_NOT_REACHED();
}
Optional<SelectorList> const& scope_end_selectors_for_matching(CSSRule const& scope_rule)
{
if (auto const* css_scope_rule = as_if<CSSScopeRule const>(scope_rule))
return css_scope_rule->end_selectors_for_matching();
if (auto const* import_rule = as_if<CSSImportRule const>(scope_rule)) {
VERIFY(import_rule->has_scope());
return import_rule->scope_end_selectors_for_matching();
}
VERIFY_NOT_REACHED();
}
GC::Ptr<CSSImportRule const> nearest_scoped_owner_import(CSSStyleSheet const* style_sheet)
{
for (auto const* current_style_sheet = style_sheet; current_style_sheet;) {
auto owner_rule = current_style_sheet->owner_rule();
if (auto const* import_rule = as_if<CSSImportRule const>(owner_rule.ptr()); import_rule && import_rule->has_scope())
return import_rule;
current_style_sheet = owner_rule ? owner_rule->parent_style_sheet() : nullptr;
}
return nullptr;
}
GC::Ptr<CSSRule const> nearest_ancestor_scope_rule_for_matching(CSSRule const& scope_rule)
{
for (auto const* parent_rule = scope_rule.parent_rule(); parent_rule; parent_rule = parent_rule->parent_rule()) {
if (is<CSSScopeRule>(*parent_rule))
return parent_rule;
}
return nearest_scoped_owner_import(scope_rule.parent_style_sheet());
}
GC::Ptr<CSSScopeRule const> CSSScopeRule::nearest_ancestor_scope_rule() const
{
if (m_cached_nearest_ancestor_scope_rule.has_value())

View file

@ -51,4 +51,10 @@ private:
template<>
inline bool CSSRule::fast_is<CSSScopeRule>() const { return type() == CSSRule::Type::Scope; }
SelectorList adapt_scope_end_selectors_for_matching(SelectorList const&);
Optional<SelectorList> const& scope_start_selectors_for_matching(CSSRule const&);
Optional<SelectorList> const& scope_end_selectors_for_matching(CSSRule const&);
GC::Ptr<CSSImportRule const> nearest_scoped_owner_import(CSSStyleSheet const*);
GC::Ptr<CSSRule const> nearest_ancestor_scope_rule_for_matching(CSSRule const&);
}

View file

@ -252,10 +252,10 @@ Vector<HasInvalidationMetadata> const* StyleComputer::has_invalidation_metadata_
return nullptr;
}
static bool scope_selector_matches(Selector const& selector, DOM::Element const& element, DOM::Element const& subject, MatchingRule const& rule, GC::Ptr<DOM::Element const> shadow_host, GC::Ptr<DOM::ShadowRoot const> rule_root, GC::Ptr<DOM::ParentNode const> scope)
static bool scope_selector_matches(Selector const& selector, DOM::Element const& element, DOM::Element const& subject, CSSStyleSheet const& scope_style_sheet, GC::Ptr<DOM::Element const> shadow_host, GC::Ptr<DOM::ShadowRoot const> rule_root, GC::Ptr<DOM::ParentNode const> scope)
{
SelectorEngine::MatchContext context {
.style_sheet_for_rule = *rule.sheet,
.style_sheet_for_rule = scope_style_sheet,
.subject = subject,
.rule_shadow_root = rule_root,
.collect_per_element_selector_involvement_metadata = true,
@ -268,20 +268,22 @@ struct ResolvedScope {
size_t proximity { NumericLimits<size_t>::max() };
};
static Optional<ResolvedScope> resolve_single_scope(DOM::AbstractElement abstract_element, MatchingRule const& rule, GC::Ptr<DOM::Element const> shadow_host, GC::Ptr<DOM::ShadowRoot const> rule_root, CSSScopeRule const& scope_rule, GC::Ptr<DOM::Element const> outer_root)
static Optional<ResolvedScope> resolve_single_scope(DOM::AbstractElement abstract_element, GC::Ptr<DOM::Element const> shadow_host, GC::Ptr<DOM::ShadowRoot const> rule_root, CSSRule const& scope_rule, GC::Ptr<DOM::Element const> outer_root)
{
GC::Ptr<DOM::Element const> root;
size_t proximity = 0;
auto const* owner_style_sheet = scope_rule.parent_style_sheet();
VERIFY(owner_style_sheet);
// https://drafts.csswg.org/css-cascade-6/#scope-limits
// Finding the scoping root(s)
// For each element matched by <scope-start>, create a scope using that element as the scoping root.
if (scope_rule.start_selectors_for_matching().has_value()) {
if (scope_start_selectors_for_matching(scope_rule).has_value()) {
for (auto const* candidate = &abstract_element.element(); candidate; candidate = candidate->parent_element().ptr(), ++proximity) {
if (outer_root && !outer_root->is_inclusive_ancestor_of(*candidate))
break;
for (auto const& selector : *scope_rule.start_selectors_for_matching()) {
if (scope_selector_matches(selector, *candidate, abstract_element.element(), rule, shadow_host, rule_root, outer_root)) {
for (auto const& selector : *scope_start_selectors_for_matching(scope_rule)) {
if (scope_selector_matches(selector, *candidate, abstract_element.element(), *owner_style_sheet, shadow_host, rule_root, outer_root)) {
root = candidate;
break;
}
@ -293,7 +295,7 @@ static Optional<ResolvedScope> resolve_single_scope(DOM::AbstractElement abstrac
root = [&] -> GC::Ptr<DOM::Element const> {
// If no <scope-start> is specified, the scoping root is the parent element of the owner node of the
// stylesheet where the @scope rule is defined.
if (auto* owner_node = const_cast<CSSStyleSheet&>(*rule.sheet).owner_node()) {
if (auto* owner_node = const_cast<CSSStyleSheet&>(*owner_style_sheet).owner_node()) {
if (auto parent = owner_node->parent_element())
return parent;
}
@ -304,7 +306,7 @@ static Optional<ResolvedScope> resolve_single_scope(DOM::AbstractElement abstrac
return rule_root->host();
// Otherwise, the scoping root is the root of the containing node tree.
if (auto document = rule.sheet->owning_document())
if (auto document = owner_style_sheet->owning_document())
return document->document_element();
return nullptr;
}();
@ -322,10 +324,10 @@ static Optional<ResolvedScope> resolve_single_scope(DOM::AbstractElement abstrac
// Finding any scoping limits
// For each scope created by a scoping root, its scoping limits are set to all elements that are descendants of
// the scoping root and that match <scope-end>, interpreting :scope and & exactly as in scoped style rules.
if (scope_rule.end_selectors_for_matching().has_value()) {
if (scope_end_selectors_for_matching(scope_rule).has_value()) {
for (auto const* candidate = &abstract_element.element(); candidate; candidate = candidate->parent_element().ptr()) {
for (auto const& selector : *scope_rule.end_selectors_for_matching()) {
if (scope_selector_matches(selector, *candidate, abstract_element.element(), rule, shadow_host, rule_root, root))
for (auto const& selector : *scope_end_selectors_for_matching(scope_rule)) {
if (scope_selector_matches(selector, *candidate, abstract_element.element(), *owner_style_sheet, shadow_host, rule_root, root))
return {};
}
if (candidate == root)
@ -336,7 +338,7 @@ static Optional<ResolvedScope> resolve_single_scope(DOM::AbstractElement abstrac
return ResolvedScope { root, proximity };
}
static Optional<ResolvedScope> resolve_scope_chain(DOM::AbstractElement abstract_element, MatchingRule const& rule, GC::Ptr<DOM::Element const> shadow_host, GC::Ptr<DOM::ShadowRoot const> rule_root, CSSScopeRule const& scope_rule)
static Optional<ResolvedScope> resolve_scope_chain(DOM::AbstractElement abstract_element, MatchingRule const& rule, GC::Ptr<DOM::Element const> shadow_host, GC::Ptr<DOM::ShadowRoot const> rule_root, CSSRule const& scope_rule)
{
// https://drafts.csswg.org/css-cascade-6/#cascade-proximity
// Scope Proximity
@ -344,7 +346,7 @@ static Optional<ResolvedScope> resolve_scope_chain(DOM::AbstractElement abstract
// the fewest generational or sibling-element hops between the scoping root and the scoped style rule subject wins.
// For this purpose, style rules without a scoping root are considered to have infinite proximity hops.
GC::Ptr<DOM::Element const> outer_root;
if (auto ancestor_scope_rule = scope_rule.nearest_ancestor_scope_rule()) {
if (auto ancestor_scope_rule = nearest_ancestor_scope_rule_for_matching(scope_rule)) {
auto resolved_ancestor_scope = resolve_scope_chain(abstract_element, rule, shadow_host, rule_root, *ancestor_scope_rule);
if (!resolved_ancestor_scope.has_value())
return {};
@ -352,7 +354,7 @@ static Optional<ResolvedScope> resolve_scope_chain(DOM::AbstractElement abstract
outer_root = resolved_ancestor_scope->root;
}
return resolve_single_scope(abstract_element, rule, shadow_host, rule_root, scope_rule, outer_root);
return resolve_single_scope(abstract_element, shadow_host, rule_root, scope_rule, outer_root);
}
static Optional<ResolvedScope> resolve_scope(DOM::AbstractElement abstract_element, MatchingRule const& rule, GC::Ptr<DOM::Element const> shadow_host, GC::Ptr<DOM::ShadowRoot const> rule_root)

View file

@ -244,7 +244,7 @@ static GC::Ptr<CSSContainerRule const> current_container_rule(Vector<GC::Ptr<CSS
return container_rule_stack.last();
}
static void collect_scope_boundary_selector_dependencies(CSSScopeRule const& scope_rule, StyleCache& style_cache)
static void collect_scope_boundary_selector_dependencies(CSSRule const& scope_rule, StyleCache& style_cache)
{
auto collect_selector_list = [&](Optional<SelectorList> const& selector_list) {
if (!selector_list.has_value())
@ -255,43 +255,49 @@ static void collect_scope_boundary_selector_dependencies(CSSScopeRule const& sco
}
};
for (auto const* current_scope_rule = &scope_rule; current_scope_rule; current_scope_rule = current_scope_rule->nearest_ancestor_scope_rule().ptr()) {
collect_selector_list(current_scope_rule->start_selectors_for_matching());
collect_selector_list(current_scope_rule->end_selectors_for_matching());
for (auto const* current_scope_rule = &scope_rule; current_scope_rule; current_scope_rule = nearest_ancestor_scope_rule_for_matching(*current_scope_rule).ptr()) {
collect_selector_list(scope_start_selectors_for_matching(*current_scope_rule));
collect_selector_list(scope_end_selectors_for_matching(*current_scope_rule));
}
}
using RuleCacheStyleRuleCallback = Function<void(CSSRule const&, GC::Ptr<CSSContainerRule const>, GC::Ptr<CSSScopeRule const>)>;
using RuleCacheStyleRuleCallback = Function<void(CSSRule const&, CSSStyleSheet const&, GC::Ptr<CSSContainerRule const>, GC::Ptr<CSSRule const>)>;
static void for_each_style_producing_rule_for_rule_cache(
CSSRuleList const& rule_list,
CSSStyleSheet const& current_style_sheet,
Vector<GC::Ptr<CSSContainerRule const>>& container_rule_stack,
GC::Ptr<CSSScopeRule const> scope_rule,
GC::Ptr<CSSRule const> scope_rule,
RuleCacheStyleRuleCallback const& callback);
static void for_each_style_producing_rule_for_rule_cache(
CSSStyleSheet const& sheet,
Vector<GC::Ptr<CSSContainerRule const>>& container_rule_stack,
GC::Ptr<CSSScopeRule const> scope_rule,
GC::Ptr<CSSRule const> scope_rule,
RuleCacheStyleRuleCallback const& callback)
{
if (!sheet.media()->matches())
return;
for_each_style_producing_rule_for_rule_cache(sheet.rules(), container_rule_stack, scope_rule, callback);
for_each_style_producing_rule_for_rule_cache(sheet.rules(), sheet, container_rule_stack, scope_rule, callback);
}
static void for_each_style_producing_rule_for_rule_cache(
CSSRuleList const& rule_list,
CSSStyleSheet const& current_style_sheet,
Vector<GC::Ptr<CSSContainerRule const>>& container_rule_stack,
GC::Ptr<CSSScopeRule const> scope_rule,
GC::Ptr<CSSRule const> scope_rule,
RuleCacheStyleRuleCallback const& callback)
{
for (auto const& rule : rule_list) {
switch (rule->type()) {
case CSSRule::Type::Import: {
auto const& import_rule = as<CSSImportRule>(*rule);
if (import_rule.loaded_style_sheet())
for_each_style_producing_rule_for_rule_cache(*import_rule.loaded_style_sheet(), container_rule_stack, scope_rule, callback);
if (import_rule.loaded_style_sheet()) {
GC::Ptr<CSSRule const> import_scope_rule = scope_rule;
if (import_rule.has_scope())
import_scope_rule = &import_rule;
for_each_style_producing_rule_for_rule_cache(*import_rule.loaded_style_sheet(), container_rule_stack, import_scope_rule, callback);
}
break;
}
@ -299,35 +305,35 @@ static void for_each_style_producing_rule_for_rule_cache(
auto const& container_rule = as<CSSContainerRule>(*rule);
// @container conditions are element-dependent, so keep their style rules and evaluate the container later.
container_rule_stack.append(&container_rule);
for_each_style_producing_rule_for_rule_cache(container_rule.css_rules(), container_rule_stack, scope_rule, callback);
for_each_style_producing_rule_for_rule_cache(container_rule.css_rules(), current_style_sheet, container_rule_stack, scope_rule, callback);
container_rule_stack.take_last();
break;
}
case CSSRule::Type::Scope: {
auto const& nested_scope_rule = as<CSSScopeRule>(*rule);
for_each_style_producing_rule_for_rule_cache(nested_scope_rule.css_rules(), container_rule_stack, &nested_scope_rule, callback);
for_each_style_producing_rule_for_rule_cache(nested_scope_rule.css_rules(), current_style_sheet, container_rule_stack, &nested_scope_rule, callback);
break;
}
case CSSRule::Type::Media:
case CSSRule::Type::Supports:
if (as<CSSConditionRule>(*rule).condition_matches())
for_each_style_producing_rule_for_rule_cache(as<CSSGroupingRule>(*rule).css_rules(), container_rule_stack, scope_rule, callback);
for_each_style_producing_rule_for_rule_cache(as<CSSGroupingRule>(*rule).css_rules(), current_style_sheet, container_rule_stack, scope_rule, callback);
break;
case CSSRule::Type::LayerBlock:
case CSSRule::Type::Page:
for_each_style_producing_rule_for_rule_cache(as<CSSGroupingRule>(*rule).css_rules(), container_rule_stack, scope_rule, callback);
for_each_style_producing_rule_for_rule_cache(as<CSSGroupingRule>(*rule).css_rules(), current_style_sheet, container_rule_stack, scope_rule, callback);
break;
case CSSRule::Type::Style:
callback(*rule, current_container_rule(container_rule_stack), scope_rule);
for_each_style_producing_rule_for_rule_cache(as<CSSGroupingRule>(*rule).css_rules(), container_rule_stack, scope_rule, callback);
callback(*rule, current_style_sheet, current_container_rule(container_rule_stack), scope_rule);
for_each_style_producing_rule_for_rule_cache(as<CSSGroupingRule>(*rule).css_rules(), current_style_sheet, container_rule_stack, scope_rule, callback);
break;
case CSSRule::Type::NestedDeclarations:
callback(*rule, current_container_rule(container_rule_stack), scope_rule);
callback(*rule, current_style_sheet, current_container_rule(container_rule_stack), scope_rule);
break;
case CSSRule::Type::CounterStyle:
@ -386,7 +392,7 @@ void StyleScope::make_rule_cache_for_cascade_origin(CascadeOrigin cascade_origin
size_t rule_index = 0;
Vector<GC::Ptr<CSSContainerRule const>> container_rule_stack;
for_each_style_producing_rule_for_rule_cache(sheet, container_rule_stack, nullptr, [&](auto const& rule, auto container_rule, auto scope_rule) {
for_each_style_producing_rule_for_rule_cache(sheet, container_rule_stack, nullptr, [&](auto const& rule, auto const& current_style_sheet, auto container_rule, auto scope_rule) {
if (container_rule && container_rule->contains_size_feature())
style_cache.has_size_container_queries = true;
@ -407,10 +413,10 @@ void StyleScope::make_rule_cache_for_cascade_origin(CascadeOrigin cascade_origin
for (CSS::Selector const& selector : absolutized_selectors) {
MatchingRule matching_rule {
.rule = &rule,
.sheet = sheet,
.sheet = current_style_sheet,
.container_rule = container_rule,
.scope_rule = scope_rule,
.default_namespace = sheet.default_namespace(),
.default_namespace = current_style_sheet.default_namespace(),
.selector = selector,
.style_sheet_index = style_sheet_index,
.rule_index = rule_index,

View file

@ -32,7 +32,7 @@ struct MatchingRule {
GC::Ptr<CSSRule const> rule; // Either CSSStyleRule or CSSNestedDeclarations
GC::Ptr<CSSStyleSheet const> sheet;
GC::Ptr<CSSContainerRule const> container_rule;
GC::Ptr<CSSScopeRule const> scope_rule;
GC::Ptr<CSSRule const> scope_rule; // Either CSSScopeRule or CSSImportRule
Optional<FlyString> default_namespace;
Selector const& selector;
size_t style_sheet_index { 0 };

View file

@ -2,7 +2,6 @@ Harness status: OK
Found 2 tests
1 Pass
1 Fail
2 Pass
Pass Scope-imported rule applies within implicit scope
Fail Scope-imported rule does not apply outside of implicit scope
Pass Scope-imported rule does not apply outside of implicit scope

View file

@ -2,7 +2,6 @@ Harness status: OK
Found 2 tests
1 Pass
1 Fail
2 Pass
Pass @scope within scope-imported stylesheet matches
Fail @scope within scope-imported does not ignore import scope
Pass @scope within scope-imported does not ignore import scope

View file

@ -2,7 +2,6 @@ Harness status: OK
Found 2 tests
1 Pass
1 Fail
2 Pass
Pass A stylesheet may be imported multiple times, and scoped differently
Fail Scope-imported rule does not apply outside of scope
Pass Scope-imported rule does not apply outside of scope

View file

@ -2,6 +2,6 @@ Harness status: OK
Found 2 tests
2 Fail
Fail The & selector matches the scoping root
Fail The & selector behaves like :scope
2 Pass
Pass The & selector matches the scoping root
Pass The & selector behaves like :scope

View file

@ -2,7 +2,6 @@ Harness status: OK
Found 2 tests
1 Pass
1 Fail
2 Pass
Pass Scope-imported rule applies within scope, above limit
Fail Scope-imported rule does not apply below limit
Pass Scope-imported rule does not apply below limit

View file

@ -2,5 +2,5 @@ Harness status: OK
Found 1 tests
1 Fail
Fail The :scope pseudo-class works in imported stylesheets
1 Pass
Pass The :scope pseudo-class works in imported stylesheets

View file

@ -2,7 +2,6 @@ Harness status: OK
Found 2 tests
1 Pass
1 Fail
2 Pass
Pass Scope-imported rule applies within scope
Fail Scope-imported rule does not apply outside of scope
Pass Scope-imported rule does not apply outside of scope