зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1886441: Part 3 - Implement substitutions in `@scope`. r=firefox-style-system-reviewers,emilio
`:scope` gets implicitly added if not present, without contributing specificity (See https://github.com/w3c/csswg-drafts/issues/10196). `&` is replaced with `scope-start` selector, or `:scope` if it not specified. Differential Revision: https://phabricator.services.mozilla.com/D207780
This commit is contained in:
Родитель
336fcf3956
Коммит
caf6d830a8
|
@ -791,6 +791,7 @@ where
|
|||
Component::Root |
|
||||
Component::Empty |
|
||||
Component::Scope |
|
||||
Component::ImplicitScope |
|
||||
Component::ParentSelector |
|
||||
Component::Nth(..) |
|
||||
Component::Host(None) |
|
||||
|
|
|
@ -97,17 +97,19 @@ impl<Impl: SelectorImpl> SelectorBuilder<Impl> {
|
|||
mut spec: SpecificityAndFlags,
|
||||
parse_relative: ParseRelative,
|
||||
) -> ThinArc<SpecificityAndFlags, Component<Impl>> {
|
||||
let implicit_parent = parse_relative.needs_implicit_parent_selector() &&
|
||||
!spec.flags.contains(SelectorFlags::HAS_PARENT);
|
||||
|
||||
let parent_selector_and_combinator;
|
||||
let implicit_parent = if implicit_parent {
|
||||
spec.flags.insert(SelectorFlags::HAS_PARENT);
|
||||
parent_selector_and_combinator = [
|
||||
let implicit_addition = match parse_relative {
|
||||
ParseRelative::ForNesting if !spec.flags.intersects(SelectorFlags::HAS_PARENT) => Some((Component::ParentSelector, SelectorFlags::HAS_PARENT)),
|
||||
ParseRelative::ForScope if !spec.flags.intersects(SelectorFlags::HAS_SCOPE | SelectorFlags::HAS_PARENT) => Some((Component::ImplicitScope, SelectorFlags::HAS_SCOPE)),
|
||||
_ => None,
|
||||
};
|
||||
let implicit_selector_and_combinator;
|
||||
let implicit_selector = if let Some((component, flag)) = implicit_addition {
|
||||
spec.flags.insert(flag);
|
||||
implicit_selector_and_combinator = [
|
||||
Component::Combinator(Combinator::Descendant),
|
||||
Component::ParentSelector,
|
||||
component,
|
||||
];
|
||||
&parent_selector_and_combinator[..]
|
||||
&implicit_selector_and_combinator[..]
|
||||
} else {
|
||||
&[]
|
||||
};
|
||||
|
@ -115,11 +117,11 @@ impl<Impl: SelectorImpl> SelectorBuilder<Impl> {
|
|||
// As an optimization, for a selector without combinators, we can just keep the order
|
||||
// as-is.
|
||||
if self.last_compound_start.is_none() {
|
||||
return Arc::from_header_and_iter(spec, ExactChain(self.components.drain(..), implicit_parent.iter().cloned()));
|
||||
return Arc::from_header_and_iter(spec, ExactChain(self.components.drain(..), implicit_selector.iter().cloned()));
|
||||
}
|
||||
|
||||
self.reverse_last_compound();
|
||||
Arc::from_header_and_iter(spec, ExactChain(self.components.drain(..).rev(), implicit_parent.iter().cloned()))
|
||||
Arc::from_header_and_iter(spec, ExactChain(self.components.drain(..).rev(), implicit_selector.iter().cloned()))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -178,6 +180,7 @@ bitflags! {
|
|||
const HAS_PARENT = 1 << 3;
|
||||
const HAS_NON_FEATURELESS_COMPONENT = 1 << 4;
|
||||
const HAS_HOST = 1 << 5;
|
||||
const HAS_SCOPE = 1 << 6;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -221,6 +224,18 @@ pub(crate) struct Specificity {
|
|||
element_selectors: u32,
|
||||
}
|
||||
|
||||
impl Specificity {
|
||||
// Return the specficity of a single class-like selector.
|
||||
#[inline]
|
||||
pub fn single_class_like() -> Self {
|
||||
Specificity {
|
||||
id_selectors: 0,
|
||||
class_like_selectors: 1,
|
||||
element_selectors: 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<u32> for Specificity {
|
||||
#[inline]
|
||||
fn from(value: u32) -> Specificity {
|
||||
|
@ -311,10 +326,18 @@ where
|
|||
Component::Root |
|
||||
Component::Empty |
|
||||
Component::Scope |
|
||||
Component::ImplicitScope |
|
||||
Component::Nth(..) |
|
||||
Component::NonTSPseudoClass(..) => {
|
||||
if matches!(*simple_selector, Component::Scope | Component::ImplicitScope) {
|
||||
flags.insert(SelectorFlags::HAS_SCOPE);
|
||||
}
|
||||
flags.insert(SelectorFlags::HAS_NON_FEATURELESS_COMPONENT);
|
||||
specificity.class_like_selectors += 1;
|
||||
if !matches!(*simple_selector, Component::ImplicitScope) {
|
||||
// Implicit :scope does not add specificity. See
|
||||
// https://github.com/w3c/csswg-drafts/issues/10196
|
||||
specificity.class_like_selectors += 1;
|
||||
}
|
||||
},
|
||||
Component::NthOf(ref nth_of_data) => {
|
||||
// https://drafts.csswg.org/selectors/#specificity-rules:
|
||||
|
|
|
@ -1194,7 +1194,7 @@ where
|
|||
Component::Host(ref selector) => {
|
||||
return matches_host(element, selector.as_ref(), &mut context.shared, rightmost);
|
||||
},
|
||||
Component::ParentSelector | Component::Scope => match context.shared.scope_element {
|
||||
Component::ParentSelector | Component::Scope | Component::ImplicitScope => match context.shared.scope_element {
|
||||
Some(ref scope_element) => element.opaque() == *scope_element,
|
||||
None => element.is_root(),
|
||||
},
|
||||
|
|
|
@ -445,19 +445,17 @@ pub enum ParseRelative {
|
|||
No,
|
||||
}
|
||||
|
||||
impl ParseRelative {
|
||||
#[inline]
|
||||
pub(crate) fn needs_implicit_parent_selector(self) -> bool {
|
||||
matches!(self, Self::ForNesting)
|
||||
}
|
||||
}
|
||||
|
||||
impl<Impl: SelectorImpl> SelectorList<Impl> {
|
||||
/// Returns a selector list with a single `&`
|
||||
pub fn ampersand() -> Self {
|
||||
Self::from_one(Selector::ampersand())
|
||||
}
|
||||
|
||||
/// Returns a selector list with a single `:scope`
|
||||
pub fn scope() -> Self {
|
||||
Self::from_one(Selector::scope())
|
||||
}
|
||||
|
||||
/// Parse a comma-separated list of Selectors.
|
||||
/// <https://drafts.csswg.org/selectors/#grouping>
|
||||
///
|
||||
|
@ -760,6 +758,16 @@ impl<Impl: SelectorImpl> Selector<Impl> {
|
|||
))
|
||||
}
|
||||
|
||||
fn scope() -> Self {
|
||||
Self(ThinArc::from_header_and_iter(
|
||||
SpecificityAndFlags {
|
||||
specificity: Specificity::single_class_like().into(),
|
||||
flags: SelectorFlags::HAS_SCOPE,
|
||||
},
|
||||
std::iter::once(Component::Scope),
|
||||
))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn specificity(&self) -> u32 {
|
||||
self.0.header.specificity
|
||||
|
@ -780,6 +788,11 @@ impl<Impl: SelectorImpl> Selector<Impl> {
|
|||
self.flags().intersects(SelectorFlags::HAS_PARENT)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn has_scope_selector(&self) -> bool {
|
||||
self.flags().intersects(SelectorFlags::HAS_SCOPE)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn is_slotted(&self) -> bool {
|
||||
self.flags().intersects(SelectorFlags::HAS_SLOTTED)
|
||||
|
@ -1081,6 +1094,7 @@ impl<Impl: SelectorImpl> Selector<Impl> {
|
|||
Root |
|
||||
Empty |
|
||||
Scope |
|
||||
ImplicitScope |
|
||||
Nth(..) |
|
||||
NonTSPseudoClass(..) |
|
||||
PseudoElement(..) |
|
||||
|
@ -1924,6 +1938,14 @@ pub enum Component<Impl: SelectorImpl> {
|
|||
Root,
|
||||
Empty,
|
||||
Scope,
|
||||
/// :scope added implicitly into scoped rules (i.e. In `@scope`) not
|
||||
/// explicitly using `:scope` or `&` selectors.
|
||||
///
|
||||
/// https://drafts.csswg.org/css-cascade-6/#scoped-rules
|
||||
///
|
||||
/// Unlike the normal `:scope` selector, this does not add any specificity.
|
||||
/// See https://github.com/w3c/csswg-drafts/issues/10196
|
||||
ImplicitScope,
|
||||
ParentSelector,
|
||||
Nth(NthSelectorData),
|
||||
NthOf(NthOfSelectorData<Impl>),
|
||||
|
@ -2252,13 +2274,14 @@ impl<Impl: SelectorImpl> ToCss for Selector<Impl> {
|
|||
debug_assert!(!combinators_exhausted);
|
||||
|
||||
// https://drafts.csswg.org/cssom/#serializing-selectors
|
||||
if compound.is_empty() {
|
||||
continue;
|
||||
}
|
||||
if let Component::RelativeSelectorAnchor = compound.first().unwrap() {
|
||||
let first_compound = match compound.first() {
|
||||
None => continue,
|
||||
Some(c) => c,
|
||||
};
|
||||
if matches!(first_compound, Component::RelativeSelectorAnchor | Component::ImplicitScope) {
|
||||
debug_assert!(
|
||||
compound.len() == 1,
|
||||
"RelativeLeft should only be a simple selector"
|
||||
"RelativeSelectorAnchor/ImplicitScope should only be a simple selector"
|
||||
);
|
||||
combinators.next().unwrap().to_css_relative(dest)?;
|
||||
continue;
|
||||
|
@ -2530,7 +2553,7 @@ impl<Impl: SelectorImpl> ToCss for Component<Impl> {
|
|||
},
|
||||
NonTSPseudoClass(ref pseudo) => pseudo.to_css(dest),
|
||||
Invalid(ref css) => dest.write_str(css),
|
||||
RelativeSelectorAnchor => Ok(()),
|
||||
RelativeSelectorAnchor | ImplicitScope => Ok(()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -2613,7 +2636,10 @@ where
|
|||
let selector = match parse_relative {
|
||||
ParseRelative::ForHas | ParseRelative::No => unreachable!(),
|
||||
ParseRelative::ForNesting => Component::ParentSelector,
|
||||
ParseRelative::ForScope => Component::Scope,
|
||||
// See https://github.com/w3c/csswg-drafts/issues/10196
|
||||
// Implicitly added `:scope` does not add specificity
|
||||
// for non-relative selectors, so do the same.
|
||||
ParseRelative::ForScope => Component::ImplicitScope,
|
||||
};
|
||||
builder.push_simple_selector(selector);
|
||||
builder.push_combinator(combinator);
|
||||
|
@ -4442,6 +4468,63 @@ pub mod tests {
|
|||
assert_eq!(iter.next_sequence(), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_implicit_scope() {
|
||||
assert_eq!(
|
||||
parse_relative_expected(".foo", ParseRelative::ForScope, None).unwrap(),
|
||||
SelectorList::from_vec(vec![Selector::from_vec(
|
||||
vec![
|
||||
Component::ImplicitScope,
|
||||
Component::Combinator(Combinator::Descendant),
|
||||
Component::Class(DummyAtom::from("foo")),
|
||||
],
|
||||
specificity(0, 1, 0),
|
||||
SelectorFlags::HAS_SCOPE | SelectorFlags::HAS_NON_FEATURELESS_COMPONENT,
|
||||
)])
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
parse_relative_expected(":scope .foo", ParseRelative::ForScope, None).unwrap(),
|
||||
SelectorList::from_vec(vec![Selector::from_vec(
|
||||
vec![
|
||||
Component::Scope,
|
||||
Component::Combinator(Combinator::Descendant),
|
||||
Component::Class(DummyAtom::from("foo")),
|
||||
],
|
||||
specificity(0, 2, 0),
|
||||
SelectorFlags::HAS_SCOPE | SelectorFlags::HAS_NON_FEATURELESS_COMPONENT,
|
||||
)])
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
parse_relative_expected("> .foo", ParseRelative::ForScope, Some("> .foo")).unwrap(),
|
||||
SelectorList::from_vec(vec![Selector::from_vec(
|
||||
vec![
|
||||
Component::ImplicitScope,
|
||||
Component::Combinator(Combinator::Child),
|
||||
Component::Class(DummyAtom::from("foo")),
|
||||
],
|
||||
specificity(0, 1, 0),
|
||||
SelectorFlags::HAS_SCOPE | SelectorFlags::HAS_NON_FEATURELESS_COMPONENT,
|
||||
)])
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
parse_relative_expected(".foo :scope > .bar", ParseRelative::ForScope, None).unwrap(),
|
||||
SelectorList::from_vec(vec![Selector::from_vec(
|
||||
vec![
|
||||
Component::Class(DummyAtom::from("foo")),
|
||||
Component::Combinator(Combinator::Descendant),
|
||||
Component::Scope,
|
||||
Component::Combinator(Combinator::Child),
|
||||
Component::Class(DummyAtom::from("bar")),
|
||||
],
|
||||
specificity(0, 3, 0),
|
||||
SelectorFlags::HAS_SCOPE | SelectorFlags::HAS_NON_FEATURELESS_COMPONENT,
|
||||
)])
|
||||
);
|
||||
}
|
||||
|
||||
struct TestVisitor {
|
||||
seen: Vec<String>,
|
||||
}
|
||||
|
|
|
@ -39,6 +39,7 @@ use crate::stylesheets::container_rule::ContainerCondition;
|
|||
use crate::stylesheets::import_rule::ImportLayer;
|
||||
use crate::stylesheets::keyframes_rule::KeyframesAnimation;
|
||||
use crate::stylesheets::layer_rule::{LayerName, LayerOrder};
|
||||
use crate::stylesheets::scope_rule::ScopeBounds;
|
||||
#[cfg(feature = "gecko")]
|
||||
use crate::stylesheets::{
|
||||
CounterStyleRule, FontFaceRule, FontFeatureValuesRule, FontPaletteValuesRule,
|
||||
|
@ -578,12 +579,13 @@ impl From<StyleRuleInclusion> for RuleInclusion {
|
|||
}
|
||||
|
||||
/// A struct containing state from ancestor rules like @layer / @import /
|
||||
/// @container / nesting.
|
||||
/// @container / nesting / @scope.
|
||||
struct ContainingRuleState {
|
||||
layer_name: LayerName,
|
||||
layer_id: LayerId,
|
||||
container_condition_id: ContainerConditionId,
|
||||
in_starting_style: bool,
|
||||
scope_condition_id: ScopeConditionId,
|
||||
ancestor_selector_lists: SmallVec<[SelectorList<SelectorImpl>; 2]>,
|
||||
}
|
||||
|
||||
|
@ -595,6 +597,7 @@ impl Default for ContainingRuleState {
|
|||
container_condition_id: ContainerConditionId::none(),
|
||||
in_starting_style: false,
|
||||
ancestor_selector_lists: Default::default(),
|
||||
scope_condition_id: ScopeConditionId::none(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -605,6 +608,7 @@ struct SavedContainingRuleState {
|
|||
layer_id: LayerId,
|
||||
container_condition_id: ContainerConditionId,
|
||||
in_starting_style: bool,
|
||||
scope_condition_id: ScopeConditionId,
|
||||
}
|
||||
|
||||
impl ContainingRuleState {
|
||||
|
@ -615,6 +619,7 @@ impl ContainingRuleState {
|
|||
layer_id: self.layer_id,
|
||||
container_condition_id: self.container_condition_id,
|
||||
in_starting_style: self.in_starting_style,
|
||||
scope_condition_id: self.scope_condition_id,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -627,6 +632,7 @@ impl ContainingRuleState {
|
|||
self.layer_id = saved.layer_id;
|
||||
self.container_condition_id = saved.container_condition_id;
|
||||
self.in_starting_style = saved.in_starting_style;
|
||||
self.scope_condition_id = saved.scope_condition_id;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2362,6 +2368,23 @@ impl ScopeConditionId {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, MallocSizeOf)]
|
||||
struct ScopeConditionReference {
|
||||
parent: ScopeConditionId,
|
||||
// TODO(dshin): With replaced parent selectors, these may be unique...
|
||||
#[ignore_malloc_size_of = "Arc inside"]
|
||||
condition: Option<ScopeBounds>,
|
||||
}
|
||||
|
||||
impl ScopeConditionReference {
|
||||
const fn none() -> Self {
|
||||
Self {
|
||||
parent: ScopeConditionId::none(),
|
||||
condition: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Data resulting from performing the CSS cascade that is specific to a given
|
||||
/// origin.
|
||||
///
|
||||
|
@ -2468,6 +2491,9 @@ pub struct CascadeData {
|
|||
/// The list of container conditions, indexed by their id.
|
||||
container_conditions: SmallVec<[ContainerConditionReference; 1]>,
|
||||
|
||||
/// The list of scope conditions, indexed by their id.
|
||||
scope_conditions: SmallVec<[ScopeConditionReference; 1]>,
|
||||
|
||||
/// Effective media query results cached from the last rebuild.
|
||||
effective_media_query_results: EffectiveMediaQueryResults,
|
||||
|
||||
|
@ -2485,6 +2511,22 @@ pub struct CascadeData {
|
|||
num_declarations: usize,
|
||||
}
|
||||
|
||||
fn parent_selector_for_scope(parent: Option<&SelectorList<SelectorImpl>>) -> &SelectorList<SelectorImpl> {
|
||||
lazy_static! {
|
||||
static ref SCOPE: SelectorList<SelectorImpl> = {
|
||||
let list = SelectorList::scope();
|
||||
list.slice()
|
||||
.iter()
|
||||
.for_each(|selector| selector.mark_as_intentionally_leaked());
|
||||
list
|
||||
};
|
||||
};
|
||||
match parent {
|
||||
Some(l) => l,
|
||||
None => &SCOPE,
|
||||
}
|
||||
}
|
||||
|
||||
impl CascadeData {
|
||||
/// Creates an empty `CascadeData`.
|
||||
pub fn new() -> Self {
|
||||
|
@ -2510,6 +2552,7 @@ impl CascadeData {
|
|||
layer_id: Default::default(),
|
||||
layers: smallvec::smallvec![CascadeLayer::root()],
|
||||
container_conditions: smallvec::smallvec![ContainerConditionReference::none()],
|
||||
scope_conditions: smallvec::smallvec![ScopeConditionReference::none()],
|
||||
extra_data: ExtraStyleData::default(),
|
||||
effective_media_query_results: EffectiveMediaQueryResults::new(),
|
||||
rules_source_order: 0,
|
||||
|
@ -2867,7 +2910,11 @@ impl CascadeData {
|
|||
debug_assert!(!has_nested_rules);
|
||||
debug_assert_eq!(stylesheet.contents().origin, Origin::UserAgent);
|
||||
debug_assert_eq!(containing_rule_state.layer_id, LayerId::root());
|
||||
// TODO(dshin, bug 1886441): Because we precompute pseudos, we cannot possibly calculate scope proximity.
|
||||
// Because we precompute pseudos, we cannot possibly calculate scope proximity.
|
||||
debug_assert_eq!(
|
||||
containing_rule_state.scope_condition_id,
|
||||
ScopeConditionId::none()
|
||||
);
|
||||
precomputed_pseudo_element_decls
|
||||
.as_mut()
|
||||
.expect("Expected precomputed declarations for the UA level")
|
||||
|
@ -2902,7 +2949,7 @@ impl CascadeData {
|
|||
containing_rule_state.layer_id,
|
||||
containing_rule_state.container_condition_id,
|
||||
containing_rule_state.in_starting_style,
|
||||
ScopeConditionId::none(),
|
||||
containing_rule_state.scope_condition_id,
|
||||
);
|
||||
|
||||
if collect_replaced_selectors {
|
||||
|
@ -2968,6 +3015,10 @@ impl CascadeData {
|
|||
// NOTE(emilio): It's fine to look at :host and then at
|
||||
// ::slotted(..), since :host::slotted(..) could never
|
||||
// possibly match, as <slot> is not a valid shadow host.
|
||||
// TODO(dshin, bug 1886441): If we have a featureless
|
||||
// `:scope`, we need to add to `host_rules` (Which should
|
||||
// be then rennamed `featureless_rules`).
|
||||
// See https://github.com/w3c/csswg-drafts/issues/9025
|
||||
let rules = if rule
|
||||
.selector
|
||||
.is_featureless_host_selector_or_pseudo_element()
|
||||
|
@ -3194,6 +3245,30 @@ impl CascadeData {
|
|||
CssRule::StartingStyle(..) => {
|
||||
containing_rule_state.in_starting_style = true;
|
||||
},
|
||||
CssRule::Scope(ref rule) => {
|
||||
let id = ScopeConditionId(self.scope_conditions.len() as u16);
|
||||
let replaced = {
|
||||
let start = rule.bounds.start.as_ref().map(|selector| {
|
||||
match containing_rule_state.ancestor_selector_lists.last() {
|
||||
Some(s) => selector.replace_parent_selector(s),
|
||||
None => selector.clone(),
|
||||
}
|
||||
});
|
||||
let end = rule.bounds
|
||||
.end
|
||||
.as_ref()
|
||||
.map(|selector| selector.replace_parent_selector(
|
||||
parent_selector_for_scope(start.as_ref())));
|
||||
containing_rule_state.ancestor_selector_lists.push(parent_selector_for_scope(start.as_ref()).clone());
|
||||
ScopeBounds { start, end }
|
||||
};
|
||||
|
||||
self.scope_conditions.push(ScopeConditionReference {
|
||||
parent: containing_rule_state.scope_condition_id,
|
||||
condition: Some(replaced),
|
||||
});
|
||||
containing_rule_state.scope_condition_id = id;
|
||||
},
|
||||
// We don't care about any other rule.
|
||||
_ => {},
|
||||
}
|
||||
|
@ -3386,6 +3461,8 @@ impl CascadeData {
|
|||
self.container_conditions.clear();
|
||||
self.container_conditions
|
||||
.push(ContainerConditionReference::none());
|
||||
self.scope_conditions.clear();
|
||||
self.scope_conditions.push(ScopeConditionReference::none());
|
||||
self.extra_data.clear();
|
||||
self.rules_source_order = 0;
|
||||
self.num_selectors = 0;
|
||||
|
|
|
@ -1,24 +1,33 @@
|
|||
[scope-specificity.html]
|
||||
[@scope (#main) { .b { } }]
|
||||
[@scope (#main) { .b { } } and .b]
|
||||
expected: FAIL
|
||||
|
||||
[@scope (#main) to (.b) { .a { } }]
|
||||
[@scope (#main) to (.b) { .a { } } and .a]
|
||||
expected: FAIL
|
||||
|
||||
[@scope (#main, .foo, .bar) { #a { } }]
|
||||
[@scope (#main, .foo, .bar) { #a { } } and #a]
|
||||
expected: FAIL
|
||||
|
||||
[@scope (#main) { div.b { } }]
|
||||
[@scope (#main) { div.b { } } and div.b]
|
||||
expected: FAIL
|
||||
|
||||
[@scope (#main) { :scope .b { } }]
|
||||
[@scope (#main) { :scope .b { } } and .a .b]
|
||||
expected: FAIL
|
||||
|
||||
[@scope (#main) { & .b { } }]
|
||||
[@scope (#main) { & .b { } } and #main .b]
|
||||
expected: FAIL
|
||||
|
||||
[@scope (#main) { div .b { } }]
|
||||
[@scope (#main) { div .b { } } and div .b]
|
||||
expected: FAIL
|
||||
|
||||
[@scope (#main) { @scope (.a) { .b { } } }]
|
||||
[@scope (#main) { @scope (.a) { .b { } } } and .b]
|
||||
expected: FAIL
|
||||
|
||||
[@scope (#main) { :scope .b { } } and :scope .b]
|
||||
expected: FAIL
|
||||
|
||||
[@scope { & .b { } } and :scope .b]
|
||||
expected: FAIL
|
||||
|
||||
[@scope (#main) { > .a { } } and :where(#main) > .a]
|
||||
expected: FAIL
|
||||
|
|
|
@ -42,9 +42,9 @@
|
|||
test_valid('@scope to (.a)');
|
||||
test_valid('@scope (.a) to (&)');
|
||||
test_valid('@scope (.a) to (& > &)');
|
||||
test_valid('@scope (.a) to (> .b)', '@scope (.a) to (:scope > .b)');
|
||||
test_valid('@scope (.a) to (+ .b)', '@scope (.a) to (:scope + .b)');
|
||||
test_valid('@scope (.a) to (~ .b)', '@scope (.a) to (:scope ~ .b)');
|
||||
test_valid('@scope (.a) to (> .b)');
|
||||
test_valid('@scope (.a) to (+ .b)');
|
||||
test_valid('@scope (.a) to (~ .b)');
|
||||
test_valid('@scope ()', '@scope');
|
||||
test_valid('@scope to ()', '@scope');
|
||||
test_valid('@scope () to ()', '@scope');
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
<title>@scope and Nesting: Parsing inner style rules with relative selector syntax</title>
|
||||
<link rel="help" href="https://drafts.csswg.org/css-cascade-6/#scoped-rules">
|
||||
<link rel="help" href="https://drafts.csswg.org/css-nesting/#nesting">
|
||||
<link rel="help" href="https://github.com/w3c/csswg-drafts/issues/10196">
|
||||
<script src="/resources/testharness.js"></script>
|
||||
<script src="/resources/testharnessreport.js"></script>
|
||||
<main id=main></main>
|
||||
|
@ -60,9 +61,9 @@
|
|||
|
||||
for (const method of Object.keys(create_method)) {
|
||||
test_inner(['@scope' , '.nest'], method, '> .foo', '& > .foo');
|
||||
test_inner(['.nest', '@scope'], method, '> .foo', ':scope > .foo');
|
||||
test_inner(['.nest', '@scope'], method, '> .foo');
|
||||
|
||||
test_inner(['@scope' , '.nest', '@media screen'], method, '> .foo', '& > .foo');
|
||||
test_inner(['.nest', '@scope', '@media screen'], method, '> .foo', ':scope > .foo');
|
||||
test_inner(['.nest', '@scope', '@media screen'], method, '> .foo');
|
||||
}
|
||||
</script>
|
||||
|
|
|
@ -1,11 +1,12 @@
|
|||
<!DOCTYPE html>
|
||||
<title>@scope - specificty</title>
|
||||
<title>@scope - specificity</title>
|
||||
<link rel="help" href="https://drafts.csswg.org/css-cascade-6/#scope-atrule">
|
||||
<script src="/resources/testharness.js"></script>
|
||||
<script src="/resources/testharnessreport.js"></script>
|
||||
<style id=style>
|
||||
</style>
|
||||
<main id=main>
|
||||
<style id=styleImplicit></style>
|
||||
<div id=a class=a>
|
||||
<div id=b class=b>
|
||||
</div>
|
||||
|
@ -36,8 +37,13 @@ function format_scoped_rule(scoped_selector, declarations) {
|
|||
// Verify that the specificity of 'scoped_selector' is the same
|
||||
// as the specificity of 'ref_selector'. Both selectors must select
|
||||
// an element within #main.
|
||||
function test_scope_specificity(scoped_selector, ref_selector) {
|
||||
test(() => {
|
||||
function test_scope_specificity(scoped_selector, ref_selector, style) {
|
||||
if (style === undefined) {
|
||||
style = document.getElementById("style");
|
||||
}
|
||||
test(t => {
|
||||
t.add_cleanup(() => { style.textContent = ''; });
|
||||
|
||||
let element = main.querySelector(ref_selector);
|
||||
assert_not_equals(element, null);
|
||||
|
||||
|
@ -62,16 +68,25 @@ function test_scope_specificity(scoped_selector, ref_selector) {
|
|||
// cause the unscoped rule to win instead.
|
||||
style.textContent = `div${ref_rule} ${scoped_rule}`;
|
||||
assert_equals(getComputedStyle(element).zIndex, '2', 'unscoped + scoped');
|
||||
}, format_scoped_rule(scoped_selector, ''));
|
||||
}, format_scoped_rule(scoped_selector, '') + ' and ' + ref_selector);
|
||||
}
|
||||
|
||||
// Selectors within @scope implicitly have `:scope <descendant-combinator>`
|
||||
// added, but no specificity associated with it is added.
|
||||
// See https://github.com/w3c/csswg-drafts/issues/10196
|
||||
test_scope_specificity(['@scope (#main)', '.b'], '.b');
|
||||
test_scope_specificity(['@scope (#main) to (.b)', '.a'], '.a');
|
||||
test_scope_specificity(['@scope (#main, .foo, .bar)', '#a'], '#a');
|
||||
test_scope_specificity(['@scope (#main)', 'div.b'], 'div.b');
|
||||
test_scope_specificity(['@scope (#main)', ':scope .b'], '.a .b');
|
||||
// Inherit the specificity of the scope-start selector.
|
||||
test_scope_specificity(['@scope (#main)', '& .b'], '#main .b');
|
||||
test_scope_specificity(['@scope (#main)', 'div .b'], 'div .b');
|
||||
test_scope_specificity(['@scope (#main)', '@scope (.a)', '.b'], '.b');
|
||||
|
||||
// Explicit `:scope` adds specficity.
|
||||
test_scope_specificity(['@scope (#main)', ':scope .b'], ':scope .b');
|
||||
// Using & in scoped style with implicit scope root uses `:scope`, which adds specificity
|
||||
test_scope_specificity(['@scope', '& .b'], ':scope .b', styleImplicit);
|
||||
// Using relative selector syntax does not add specificity
|
||||
test_scope_specificity(['@scope (#main)', '> .a'], ':where(#main) > .a');
|
||||
</script>
|
||||
|
|
Загрузка…
Ссылка в новой задаче