Bug 1864418 - Append implicit parent selector during parsing. r=dshin,devtools-reviewers,nchevobbe

This matches recent spec changes.

Differential Revision: https://phabricator.services.mozilla.com/D206766
This commit is contained in:
Emilio Cobos Álvarez 2024-04-08 08:44:42 +00:00
parent 8003c92d48
commit 206ca74d01
9 changed files with 214 additions and 245 deletions

View file

@ -12,7 +12,7 @@
// --- @container myContainer (width > 10px) {
// ----- div {
// ------- & > span { … }
// ------- .mySpan {
// ------- & .mySpan {
// --------- &:not(:focus) {
const spanNotFocusedRule = `&:not(:focus) {
@ -94,7 +94,7 @@ const EXPECTED_AFTER_SPAN_PROP_CHANGES = EXPECTED_AFTER_DIV_PROP_CHANGE.map(
})
).concat([
{
text: ".mySpan {",
text: "& .mySpan {",
copyRuleClipboard:
applyModificationAfterSpanPropertiesChange(spanClassRule),
},

View file

@ -103,7 +103,7 @@ add_task(async function () {
matches: true,
},
{
selector: ".unmatched",
selector: "& .unmatched",
matches: false,
},
]);

View file

@ -93,7 +93,7 @@ add_task(async function () {
checkRuleViewContent(view, [
{ selector: "element", ancestorRulesData: null, declarations: [] },
{
selector: `.foo`,
selector: `& .foo`,
// prettier-ignore
ancestorRulesData: [
`body {`,
@ -108,7 +108,7 @@ add_task(async function () {
checkRuleViewContent(view, [
{ selector: "element", ancestorRulesData: null, declarations: [] },
{
selector: `#bar`,
selector: `& #bar`,
// prettier-ignore
ancestorRulesData: [
`body {`,
@ -138,7 +138,7 @@ add_task(async function () {
checkRuleViewContent(view, [
{ selector: "element", ancestorRulesData: null, declarations: [] },
{
selector: `[href]`,
selector: `& [href]`,
ancestorRulesData: [
`body {`,
` @media screen {`,

View file

@ -211,7 +211,7 @@ async function checkCopyNestedRule(view) {
const expectedNested = `html {
body {
@container (1px < width) {
#nested {
& #nested {
background: tomato;
color: gold;
}

View file

@ -88,8 +88,8 @@ add_task(async function () {
);
ok(highlighterData.isShown, "The selector highlighter was shown");
info(`Clicking on ".title" selector icon`);
highlighterData = await clickSelectorIcon(view, ".title");
info(`Clicking on "& .title" selector icon`);
highlighterData = await clickSelectorIcon(view, "& .title");
is(
highlighterData.nodeFront.nodeName.toLowerCase(),
"h1",

View file

@ -6,59 +6,45 @@
//!
//! Our selector representation is designed to optimize matching, and has
//! several requirements:
//! * All simple selectors and combinators are stored inline in the same buffer
//! as Component instances.
//! * We store the top-level compound selectors from right to left, i.e. in
//! matching order.
//! * We store the simple selectors for each combinator from left to right, so
//! that we match the cheaper simple selectors first.
//! * All simple selectors and combinators are stored inline in the same buffer as Component
//! instances.
//! * We store the top-level compound selectors from right to left, i.e. in matching order.
//! * We store the simple selectors for each combinator from left to right, so that we match the
//! cheaper simple selectors first.
//!
//! Meeting all these constraints without extra memmove traffic during parsing
//! is non-trivial. This module encapsulates those details and presents an
//! easy-to-use API for the parser.
//! For example, the selector:
//!
//! .bar:hover > .baz:nth-child(2) + .qux
//!
//! Gets stored as:
//!
//! [.qux, + , .baz, :nth-child(2), > , .bar, :hover]
//!
//! Meeting all these constraints without extra memmove traffic during parsing is non-trivial. This
//! module encapsulates those details and presents an easy-to-use API for the parser.
use crate::parser::{Combinator, Component, RelativeSelector, Selector, SelectorImpl};
use crate::parser::{Combinator, Component, RelativeSelector, Selector, SelectorImpl, ParseRelative};
use crate::sink::Push;
use servo_arc::{Arc, ThinArc};
use smallvec::SmallVec;
use std::cmp;
use std::iter;
use std::ptr;
use std::slice;
/// Top-level SelectorBuilder struct. This should be stack-allocated by the
/// consumer and never moved (because it contains a lot of inline data that
/// would be slow to memmov).
/// Top-level SelectorBuilder struct. This should be stack-allocated by the consumer and never
/// moved (because it contains a lot of inline data that would be slow to memmove).
///
/// After instantation, callers may call the push_simple_selector() and
/// push_combinator() methods to append selector data as it is encountered
/// (from left to right). Once the process is complete, callers should invoke
/// build(), which transforms the contents of the SelectorBuilder into a heap-
/// allocated Selector and leaves the builder in a drained state.
/// After instantiation, callers may call the push_simple_selector() and push_combinator() methods
/// to append selector data as it is encountered (from left to right). Once the process is
/// complete, callers should invoke build(), which transforms the contents of the SelectorBuilder
/// into a heap- allocated Selector and leaves the builder in a drained state.
#[derive(Debug)]
pub struct SelectorBuilder<Impl: SelectorImpl> {
/// The entire sequence of simple selectors, from left to right, without combinators.
///
/// We make this large because the result of parsing a selector is fed into a new
/// Arc-ed allocation, so any spilled vec would be a wasted allocation. Also,
/// Components are large enough that we don't have much cache locality benefit
/// from reserving stack space for fewer of them.
simple_selectors: SmallVec<[Component<Impl>; 32]>,
/// The combinators, and the length of the compound selector to their left.
combinators: SmallVec<[(Combinator, usize); 16]>,
/// The length of the current compount selector.
current_len: usize,
}
impl<Impl: SelectorImpl> Default for SelectorBuilder<Impl> {
#[inline(always)]
fn default() -> Self {
SelectorBuilder {
simple_selectors: SmallVec::new(),
combinators: SmallVec::new(),
current_len: 0,
}
}
/// The entire sequence of components. We make this large because the result of parsing a
/// selector is fed into a new Arc-ed allocation, so any spilled vec would be a wasted
/// allocation. Also, Components are large enough that we don't have much cache locality
/// benefit from reserving stack space for fewer of them.
components: SmallVec<[Component<Impl>; 32]>,
last_compound_start: Option<usize>,
}
impl<Impl: SelectorImpl> Push<Component<Impl>> for SelectorBuilder<Impl> {
@ -71,101 +57,115 @@ impl<Impl: SelectorImpl> SelectorBuilder<Impl> {
/// Pushes a simple selector onto the current compound selector.
#[inline(always)]
pub fn push_simple_selector(&mut self, ss: Component<Impl>) {
assert!(!ss.is_combinator());
self.simple_selectors.push(ss);
self.current_len += 1;
debug_assert!(!ss.is_combinator());
self.components.push(ss);
}
/// Completes the current compound selector and starts a new one, delimited
/// by the given combinator.
/// Completes the current compound selector and starts a new one, delimited by the given
/// combinator.
#[inline(always)]
pub fn push_combinator(&mut self, c: Combinator) {
self.combinators.push((c, self.current_len));
self.current_len = 0;
self.reverse_last_compound();
self.components.push(Component::Combinator(c));
self.last_compound_start = Some(self.components.len());
}
fn reverse_last_compound(&mut self) {
let start = self.last_compound_start.unwrap_or(0);
self.components[start..].reverse();
}
/// Returns true if combinators have ever been pushed to this builder.
#[inline(always)]
pub fn has_combinators(&self) -> bool {
!self.combinators.is_empty()
self.last_compound_start.is_some()
}
/// Consumes the builder, producing a Selector.
#[inline(always)]
pub fn build(&mut self) -> ThinArc<SpecificityAndFlags, Component<Impl>> {
pub fn build(&mut self, parse_relative: ParseRelative) -> ThinArc<SpecificityAndFlags, Component<Impl>> {
// Compute the specificity and flags.
let sf = specificity_and_flags(self.simple_selectors.iter());
self.build_with_specificity_and_flags(sf)
let sf = specificity_and_flags(self.components.iter());
self.build_with_specificity_and_flags(sf, parse_relative)
}
/// Builds with an explicit SpecificityAndFlags. This is separated from build() so
/// that unit tests can pass an explicit specificity.
/// Builds with an explicit SpecificityAndFlags. This is separated from build() so that unit
/// tests can pass an explicit specificity.
#[inline(always)]
pub(crate) fn build_with_specificity_and_flags(
&mut self,
spec: SpecificityAndFlags,
mut spec: SpecificityAndFlags,
parse_relative: ParseRelative,
) -> ThinArc<SpecificityAndFlags, Component<Impl>> {
// Create the Arc using an iterator that drains our buffers.
// Panic-safety: if SelectorBuilderIter is not iterated to the end, some simple selectors
// will safely leak.
let raw_simple_selectors = unsafe {
let simple_selectors_len = self.simple_selectors.len();
self.simple_selectors.set_len(0);
std::slice::from_raw_parts(self.simple_selectors.as_ptr(), simple_selectors_len)
};
let (rest, current) = split_from_end(raw_simple_selectors, self.current_len);
let iter = SelectorBuilderIter {
current_simple_selectors: current.iter(),
rest_of_simple_selectors: rest,
combinators: self.combinators.drain(..).rev(),
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 = [
Component::Combinator(Combinator::Descendant),
Component::ParentSelector,
];
&parent_selector_and_combinator[..]
} else {
&[]
};
Arc::from_header_and_iter(spec, iter)
// 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()));
}
self.reverse_last_compound();
Arc::from_header_and_iter(spec, ExactChain(self.components.drain(..).rev(), implicit_parent.iter().cloned()))
}
}
struct SelectorBuilderIter<'a, Impl: SelectorImpl> {
current_simple_selectors: slice::Iter<'a, Component<Impl>>,
rest_of_simple_selectors: &'a [Component<Impl>],
combinators: iter::Rev<smallvec::Drain<'a, [(Combinator, usize); 16]>>,
impl<Impl: SelectorImpl> Default for SelectorBuilder<Impl> {
#[inline(always)]
fn default() -> Self {
SelectorBuilder {
components: SmallVec::new(),
last_compound_start: None,
}
}
}
impl<'a, Impl: SelectorImpl> ExactSizeIterator for SelectorBuilderIter<'a, Impl> {
// This is effectively a Chain<>, but Chain isn't an ExactSizeIterator, see
// https://github.com/rust-lang/rust/issues/34433
struct ExactChain<A, B>(A, B);
impl<A, B, Item> ExactSizeIterator for ExactChain<A, B>
where
A: ExactSizeIterator<Item = Item>,
B: ExactSizeIterator<Item = Item>,
{
fn len(&self) -> usize {
self.current_simple_selectors.len() +
self.rest_of_simple_selectors.len() +
self.combinators.len()
self.0.len() + self.1.len()
}
}
impl<'a, Impl: SelectorImpl> Iterator for SelectorBuilderIter<'a, Impl> {
type Item = Component<Impl>;
impl<A, B, Item> Iterator for ExactChain<A, B>
where
A: ExactSizeIterator<Item = Item>,
B: ExactSizeIterator<Item = Item>,
{
type Item = Item;
#[inline(always)]
fn next(&mut self) -> Option<Self::Item> {
if let Some(simple_selector_ref) = self.current_simple_selectors.next() {
// Move a simple selector out of this slice iterator.
// This is safe because weve called SmallVec::set_len(0) above,
// so SmallVec::drop wont drop this simple selector.
unsafe { Some(ptr::read(simple_selector_ref)) }
} else {
self.combinators.next().map(|(combinator, len)| {
let (rest, current) = split_from_end(self.rest_of_simple_selectors, len);
self.rest_of_simple_selectors = rest;
self.current_simple_selectors = current.iter();
Component::Combinator(combinator)
})
}
self.0.next().or_else(|| self.1.next())
}
fn size_hint(&self) -> (usize, Option<usize>) {
(self.len(), Some(self.len()))
let len = self.len();
(len, Some(len))
}
}
fn split_from_end<T>(s: &[T], at: usize) -> (&[T], &[T]) {
s.split_at(s.len() - at)
}
/// Flags that indicate at which point of parsing a selector are we.
#[derive(Clone, Copy, Default, Eq, PartialEq, ToShmem)]
pub(crate) struct SelectorFlags(u8);

View file

@ -445,6 +445,13 @@ 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 {
@ -952,7 +959,7 @@ impl<Impl: SelectorImpl> Selector<Impl> {
}
}
let spec = SpecificityAndFlags { specificity, flags };
Selector(builder.build_with_specificity_and_flags(spec))
Selector(builder.build_with_specificity_and_flags(spec, ParseRelative::No))
}
#[inline]
@ -980,9 +987,6 @@ impl<Impl: SelectorImpl> Selector<Impl> {
}
let result = SelectorList::from_iter(orig.iter().map(|s| {
if !s.has_parent_selector() {
return s.clone();
}
s.replace_parent_selector(parent)
}));
@ -1050,82 +1054,53 @@ impl<Impl: SelectorImpl> Selector<Impl> {
flags: &mut SelectorFlags,
flags_to_propagate: SelectorFlags,
) -> Selector<Impl> {
if !orig.has_parent_selector() {
return orig.clone();
}
let new_selector = orig.replace_parent_selector(parent);
*specificity += Specificity::from(new_selector.specificity() - orig.specificity());
flags.insert(new_selector.flags().intersection(flags_to_propagate));
new_selector
}
let mut items = if !self.has_parent_selector() {
// Implicit `&` plus descendant combinator.
let iter = self.iter_raw_match_order();
let len = iter.len() + 2;
specificity += Specificity::from(parent_specificity_and_flags.specificity);
flags.insert(
parent_specificity_and_flags
.flags
.intersection(SelectorFlags::for_nesting()),
);
let iter = iter
.cloned()
.chain(std::iter::once(Component::Combinator(
Combinator::Descendant,
)))
.chain(std::iter::once(Component::Is(parent.clone())));
UniqueArc::from_header_and_iter_with_size(Default::default(), iter, len)
} else {
let iter = self.iter_raw_match_order().map(|component| {
use self::Component::*;
match *component {
LocalName(..) |
ID(..) |
Class(..) |
AttributeInNoNamespaceExists { .. } |
AttributeInNoNamespace { .. } |
AttributeOther(..) |
ExplicitUniversalType |
ExplicitAnyNamespace |
ExplicitNoNamespace |
DefaultNamespace(..) |
Namespace(..) |
Root |
Empty |
Scope |
Nth(..) |
NonTSPseudoClass(..) |
PseudoElement(..) |
Combinator(..) |
Host(None) |
Part(..) |
Invalid(..) |
RelativeSelectorAnchor => component.clone(),
ParentSelector => {
specificity += Specificity::from(parent_specificity_and_flags.specificity);
flags.insert(
parent_specificity_and_flags
.flags
.intersection(SelectorFlags::for_nesting()),
);
Is(parent.clone())
},
Negation(ref selectors) => {
Negation(
replace_parent_on_selector_list(
selectors.slice(),
parent,
&mut specificity,
&mut flags,
/* propagate_specificity = */ true,
SelectorFlags::for_nesting(),
)
.unwrap_or_else(|| selectors.clone()),
)
},
Is(ref selectors) => {
Is(replace_parent_on_selector_list(
if !self.has_parent_selector() {
return self.clone();
}
let iter = self.iter_raw_match_order().map(|component| {
use self::Component::*;
match *component {
LocalName(..) |
ID(..) |
Class(..) |
AttributeInNoNamespaceExists { .. } |
AttributeInNoNamespace { .. } |
AttributeOther(..) |
ExplicitUniversalType |
ExplicitAnyNamespace |
ExplicitNoNamespace |
DefaultNamespace(..) |
Namespace(..) |
Root |
Empty |
Scope |
Nth(..) |
NonTSPseudoClass(..) |
PseudoElement(..) |
Combinator(..) |
Host(None) |
Part(..) |
Invalid(..) |
RelativeSelectorAnchor => component.clone(),
ParentSelector => {
specificity += Specificity::from(parent_specificity_and_flags.specificity);
flags.insert(
parent_specificity_and_flags
.flags
.intersection(SelectorFlags::for_nesting()),
);
Is(parent.clone())
},
Negation(ref selectors) => {
Negation(
replace_parent_on_selector_list(
selectors.slice(),
parent,
&mut specificity,
@ -1133,64 +1108,75 @@ impl<Impl: SelectorImpl> Selector<Impl> {
/* propagate_specificity = */ true,
SelectorFlags::for_nesting(),
)
.unwrap_or_else(|| selectors.clone()))
},
Where(ref selectors) => {
Where(
replace_parent_on_selector_list(
selectors.slice(),
parent,
&mut specificity,
&mut flags,
/* propagate_specificity = */ false,
SelectorFlags::for_nesting(),
)
.unwrap_or_else(|| selectors.clone()),
)
},
Has(ref selectors) => Has(replace_parent_on_relative_selector_list(
selectors,
.unwrap_or_else(|| selectors.clone()),
)
},
Is(ref selectors) => {
Is(replace_parent_on_selector_list(
selectors.slice(),
parent,
&mut specificity,
&mut flags,
/* propagate_specificity = */ true,
SelectorFlags::for_nesting(),
)
.into_boxed_slice()),
Host(Some(ref selector)) => Host(Some(replace_parent_on_selector(
selector,
parent,
&mut specificity,
&mut flags,
SelectorFlags::for_nesting() - SelectorFlags::HAS_NON_FEATURELESS_COMPONENT,
))),
NthOf(ref data) => {
let selectors = replace_parent_on_selector_list(
data.selectors(),
.unwrap_or_else(|| selectors.clone()))
},
Where(ref selectors) => {
Where(
replace_parent_on_selector_list(
selectors.slice(),
parent,
&mut specificity,
&mut flags,
/* propagate_specificity = */ true,
/* propagate_specificity = */ false,
SelectorFlags::for_nesting(),
);
NthOf(match selectors {
Some(s) => {
NthOfSelectorData::new(data.nth_data(), s.slice().iter().cloned())
},
None => data.clone(),
})
},
Slotted(ref selector) => Slotted(replace_parent_on_selector(
selector,
)
.unwrap_or_else(|| selectors.clone()),
)
},
Has(ref selectors) => Has(replace_parent_on_relative_selector_list(
selectors,
parent,
&mut specificity,
&mut flags,
SelectorFlags::for_nesting(),
)
.into_boxed_slice()),
Host(Some(ref selector)) => Host(Some(replace_parent_on_selector(
selector,
parent,
&mut specificity,
&mut flags,
SelectorFlags::for_nesting() - SelectorFlags::HAS_NON_FEATURELESS_COMPONENT,
))),
NthOf(ref data) => {
let selectors = replace_parent_on_selector_list(
data.selectors(),
parent,
&mut specificity,
&mut flags,
/* propagate_specificity = */ true,
SelectorFlags::for_nesting(),
)),
}
});
UniqueArc::from_header_and_iter(Default::default(), iter)
};
);
NthOf(match selectors {
Some(s) => {
NthOfSelectorData::new(data.nth_data(), s.slice().iter().cloned())
},
None => data.clone(),
})
},
Slotted(ref selector) => Slotted(replace_parent_on_selector(
selector,
parent,
&mut specificity,
&mut flags,
SelectorFlags::for_nesting(),
)),
}
});
let mut items = UniqueArc::from_header_and_iter(Default::default(), iter);
*items.header_mut() = SpecificityAndFlags {
specificity: specificity.into(),
flags,
@ -2668,7 +2654,7 @@ where
builder.push_combinator(combinator);
}
return Ok(Selector(builder.build()));
return Ok(Selector(builder.build(parse_relative)));
}
fn try_parse_combinator<'i, 't, P, Impl>(input: &mut CssParser<'i, 't>) -> Result<Combinator, ()> {
@ -4400,7 +4386,7 @@ pub mod tests {
parse("#foo:has(:is(.bar, div .baz).bar)").unwrap()
);
let child = parse("#foo").unwrap();
let child = parse_relative_expected("#foo", ParseRelative::ForNesting, Some("& #foo")).unwrap();
assert_eq!(
child.replace_parent_selector(&parent),
parse(":is(.bar, div .baz) #foo").unwrap()

View file

@ -1,3 +1 @@
prefs: [layout.css.at-scope.enabled:true]
[conditional-rules.html]
expected: FAIL

View file

@ -1,15 +0,0 @@
[parsing.html]
[.foo { + .bar, .foo, > .baz { color: green; }}]
expected: FAIL
[.foo { .foo { color: green; }}]
expected: FAIL
[.foo { .foo, .foo & { color: green; }}]
expected: FAIL
[.foo { :is(.bar, .baz) { color: green; }}]
expected: FAIL
[.foo { .foo, .bar { color: green; }}]
expected: FAIL