Skip to content

Commit a1738c7

Browse files
keithamusemilio
authored andcommitted
Bug 1980215 - Support parsing of ::before::marker,::after::marker r=emilio,firefox-style-system-reviewers
This supports parsing of pseudo elements after the `::before` and `::after` pseudos. The only pseudo element allowed after these is `::marker`. This also updates the UA stylesheet to include the newly updated `::before::marker, `::after::marker` selector. Differential Revision: https://phabricator.services.mozilla.com/D259290
1 parent 1d80119 commit a1738c7

File tree

5 files changed

+97
-20
lines changed

5 files changed

+97
-20
lines changed

selectors/parser.rs

Lines changed: 79 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -47,12 +47,24 @@ pub trait PseudoElement: Sized + ToCss {
4747
false
4848
}
4949

50+
/// Whether this pseudo-element is valid when directly after a ::before/::after pseudo.
51+
fn valid_after_before_or_after(&self) -> bool {
52+
false
53+
}
54+
5055
/// Whether this pseudo-element is element-backed.
5156
/// https://drafts.csswg.org/css-pseudo-4/#element-like
5257
fn is_element_backed(&self) -> bool {
5358
false
5459
}
5560

61+
/// Whether this pseudo-element is ::before or ::after pseudo element,
62+
/// which are treated specially when deciding what can come after them.
63+
/// https://drafts.csswg.org/css-pseudo-4/#generated-content
64+
fn is_before_or_after(&self) -> bool {
65+
false
66+
}
67+
5668
/// The count we contribute to the specificity from this pseudo-element.
5769
fn specificity_count(&self) -> u32 {
5870
1
@@ -129,32 +141,30 @@ bitflags! {
129141
/// disallowed. If this flag is set, `AFTER_NON_ELEMENT_BACKED_PSEUDO` must be set
130142
/// as well.
131143
const AFTER_NON_STATEFUL_PSEUDO_ELEMENT = 1 << 4;
144+
// Whether we've parsed a generated pseudo-element (as in ::before, ::after).
145+
// If so then some other pseudo elements are disallowed (e.g. another generated pseudo)
146+
// while others allowed (e.g. ::marker).
147+
const AFTER_BEFORE_OR_AFTER_PSEUDO = 1 << 5;
132148

133149
/// Whether we are after any of the pseudo-like things.
134-
const AFTER_PSEUDO = Self::AFTER_PART_LIKE.bits() | Self::AFTER_SLOTTED.bits() | Self::AFTER_NON_ELEMENT_BACKED_PSEUDO.bits();
150+
const AFTER_PSEUDO = Self::AFTER_PART_LIKE.bits() | Self::AFTER_SLOTTED.bits() | Self::AFTER_NON_ELEMENT_BACKED_PSEUDO.bits() | Self::AFTER_BEFORE_OR_AFTER_PSEUDO.bits();
135151

136152
/// Whether we explicitly disallow combinators.
137-
const DISALLOW_COMBINATORS = 1 << 5;
153+
const DISALLOW_COMBINATORS = 1 << 6;
138154

139155
/// Whether we explicitly disallow pseudo-element-like things.
140-
const DISALLOW_PSEUDOS = 1 << 6;
156+
const DISALLOW_PSEUDOS = 1 << 7;
141157

142158
/// Whether we explicitly disallow relative selectors (i.e. `:has()`).
143-
const DISALLOW_RELATIVE_SELECTOR = 1 << 7;
159+
const DISALLOW_RELATIVE_SELECTOR = 1 << 8;
144160

145161
/// Whether we've parsed a pseudo-element which is in a pseudo-element tree (i.e. it is a
146162
/// descendant pseudo of a pseudo-element root).
147-
const IN_PSEUDO_ELEMENT_TREE = 1 << 8;
163+
const IN_PSEUDO_ELEMENT_TREE = 1 << 9;
148164
}
149165
}
150166

151167
impl SelectorParsingState {
152-
#[inline]
153-
fn allows_pseudos(self) -> bool {
154-
// NOTE(emilio): We allow pseudos after ::part and such.
155-
!self.intersects(Self::AFTER_NON_ELEMENT_BACKED_PSEUDO | Self::DISALLOW_PSEUDOS)
156-
}
157-
158168
#[inline]
159169
fn allows_slotted(self) -> bool {
160170
!self.intersects(Self::AFTER_PSEUDO | Self::DISALLOW_PSEUDOS)
@@ -2772,6 +2782,7 @@ where
27722782
if state.intersects(SelectorParsingState::AFTER_PSEUDO) {
27732783
debug_assert!(state.intersects(
27742784
SelectorParsingState::AFTER_NON_ELEMENT_BACKED_PSEUDO |
2785+
SelectorParsingState::AFTER_BEFORE_OR_AFTER_PSEUDO |
27752786
SelectorParsingState::AFTER_SLOTTED |
27762787
SelectorParsingState::AFTER_PART_LIKE
27772788
));
@@ -3310,6 +3321,9 @@ where
33103321
state.insert(SelectorParsingState::AFTER_PART_LIKE);
33113322
} else {
33123323
state.insert(SelectorParsingState::AFTER_NON_ELEMENT_BACKED_PSEUDO);
3324+
if p.is_before_or_after() {
3325+
state.insert(SelectorParsingState::AFTER_BEFORE_OR_AFTER_PSEUDO);
3326+
}
33133327
}
33143328
if !p.accepts_state_pseudo_classes() {
33153329
state.insert(SelectorParsingState::AFTER_NON_STATEFUL_PSEUDO_ELEMENT);
@@ -3552,7 +3566,15 @@ where
35523566
};
35533567
let is_pseudo_element = !is_single_colon || is_css2_pseudo_element(&name);
35543568
if is_pseudo_element {
3555-
if !state.allows_pseudos() {
3569+
// Pseudos after pseudo elements are not allowed in some cases:
3570+
// - Some states will disallow pseudos, such as the interiors of
3571+
// :has/:is/:where/:not (DISALLOW_PSEUDOS).
3572+
// - Non-element backed pseudos do not allow other pseudos to follow (AFTER_NON_ELEMENT_BACKED_PSEUDO)...
3573+
// - ... except ::before and ::after, which allow _some_ pseudos.
3574+
if state.intersects(SelectorParsingState::DISALLOW_PSEUDOS)
3575+
|| (state.intersects(SelectorParsingState::AFTER_NON_ELEMENT_BACKED_PSEUDO)
3576+
&& !state.intersects(SelectorParsingState::AFTER_BEFORE_OR_AFTER_PSEUDO))
3577+
{
35563578
return Err(input.new_custom_error(SelectorParseErrorKind::InvalidState));
35573579
}
35583580
let pseudo_element = if is_functional {
@@ -3590,6 +3612,12 @@ where
35903612
P::parse_pseudo_element(parser, location, name)?
35913613
};
35923614

3615+
if state.intersects(SelectorParsingState::AFTER_BEFORE_OR_AFTER_PSEUDO) &&
3616+
!pseudo_element.valid_after_before_or_after()
3617+
{
3618+
return Err(input.new_custom_error(SelectorParseErrorKind::InvalidState));
3619+
}
3620+
35933621
if state.intersects(SelectorParsingState::AFTER_SLOTTED) &&
35943622
!pseudo_element.valid_after_slotted()
35953623
{
@@ -3688,6 +3716,7 @@ pub mod tests {
36883716
pub enum PseudoElement {
36893717
Before,
36903718
After,
3719+
Marker,
36913720
Highlight(String),
36923721
}
36933722

@@ -3702,8 +3731,16 @@ pub mod tests {
37023731
true
37033732
}
37043733

3734+
fn valid_after_before_or_after(&self) -> bool {
3735+
matches!(self, Self::Marker)
3736+
}
3737+
3738+
fn is_before_or_after(&self) -> bool {
3739+
matches!(self, Self::Before | Self::After)
3740+
}
3741+
37053742
fn is_element_backed(&self) -> bool {
3706-
true
3743+
false
37073744
}
37083745
}
37093746

@@ -3746,6 +3783,7 @@ pub mod tests {
37463783
match *self {
37473784
PseudoElement::Before => dest.write_str("::before"),
37483785
PseudoElement::After => dest.write_str("::after"),
3786+
PseudoElement::Marker => dest.write_str("::marker"),
37493787
PseudoElement::Highlight(ref name) => {
37503788
dest.write_str("::highlight(")?;
37513789
serialize_identifier(&name, dest)?;
@@ -3915,6 +3953,7 @@ pub mod tests {
39153953
match_ignore_ascii_case! { &name,
39163954
"before" => return Ok(PseudoElement::Before),
39173955
"after" => return Ok(PseudoElement::After),
3956+
"marker" => return Ok(PseudoElement::Marker),
39183957
_ => {}
39193958
}
39203959
Err(
@@ -4579,6 +4618,33 @@ pub mod tests {
45794618
assert_eq!(iter.next_sequence(), None);
45804619
}
45814620

4621+
#[test]
4622+
fn test_pseudo_before_marker() {
4623+
let list = parse("::before::marker").unwrap();
4624+
let selector = &list.slice()[0];
4625+
let mut iter = selector.iter();
4626+
assert_eq!(
4627+
iter.next(),
4628+
Some(&Component::PseudoElement(PseudoElement::Marker))
4629+
);
4630+
assert_eq!(iter.next(), None);
4631+
let combinator = iter.next_sequence();
4632+
assert_eq!(combinator, Some(Combinator::PseudoElement));
4633+
assert!(matches!(iter.next(), Some(&Component::PseudoElement(PseudoElement::Before))));
4634+
assert_eq!(iter.next(), None);
4635+
let combinator = iter.next_sequence();
4636+
assert_eq!(combinator, Some(Combinator::PseudoElement));
4637+
assert_eq!(iter.next(), None);
4638+
assert_eq!(iter.next_sequence(), None);
4639+
}
4640+
4641+
#[test]
4642+
fn test_pseudo_duplicate_before_after_or_marker() {
4643+
assert!(parse("::before::before").is_err());
4644+
assert!(parse("::after::after").is_err());
4645+
assert!(parse("::marker::marker").is_err());
4646+
}
4647+
45824648
#[test]
45834649
fn test_universal() {
45844650
let list = parse_ns(

style/gecko/pseudo_element.rs

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ use crate::string_cache::Atom;
1717
use crate::values::serialize_atom_identifier;
1818
use crate::values::AtomIdent;
1919
use cssparser::{ToCss, Parser};
20+
use selectors::parser::PseudoElement as PseudoElementTrait;
2021
use static_prefs::pref;
2122
use std::fmt;
2223
use style_traits::ParseError;
@@ -170,7 +171,7 @@ impl ToCss for PtNameAndClassSelector {
170171
}
171172
}
172173

173-
impl ::selectors::parser::PseudoElement for PseudoElement {
174+
impl PseudoElementTrait for PseudoElement {
174175
type Impl = SelectorImpl;
175176

176177
// ::slotted() should support all tree-abiding pseudo-elements, see
@@ -189,6 +190,13 @@ impl ::selectors::parser::PseudoElement for PseudoElement {
189190
)
190191
}
191192

193+
// ::before/::after should support ::marker, but no others.
194+
// https://drafts.csswg.org/css-pseudo-4/#marker-pseudo
195+
#[inline]
196+
fn valid_after_before_or_after(&self) -> bool {
197+
matches!(*self, Self::Marker)
198+
}
199+
192200
#[inline]
193201
fn accepts_state_pseudo_classes(&self) -> bool {
194202
// Note: if the pseudo element is a descendants of a pseudo element, `only-child` should be
@@ -216,6 +224,12 @@ impl ::selectors::parser::PseudoElement for PseudoElement {
216224
// element, instead of the snapshot containing block.
217225
self.is_named_view_transition() || *self == PseudoElement::DetailsContent
218226
}
227+
228+
/// Whether the current pseudo element is ::before or ::after.
229+
#[inline]
230+
fn is_before_or_after(&self) -> bool {
231+
matches!(*self, PseudoElement::Before | PseudoElement::After)
232+
}
219233
}
220234

221235
impl PseudoElement {
@@ -261,12 +275,6 @@ impl PseudoElement {
261275
matches!(*self, Self::Before | Self::After | Self::Marker)
262276
}
263277

264-
/// Whether the current pseudo element is ::before or ::after.
265-
#[inline]
266-
pub fn is_before_or_after(&self) -> bool {
267-
matches!(*self, Self::Before | Self::After)
268-
}
269-
270278
/// Whether this pseudo-element is the ::before pseudo.
271279
#[inline]
272280
pub fn is_before(&self) -> bool {

style/style_resolver.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ use selectors::matching::{
2020
IncludeStartingStyle, MatchingContext, MatchingForInvalidation, MatchingMode,
2121
NeedsSelectorFlags, VisitedHandlingMode,
2222
};
23+
use selectors::parser::PseudoElement as PseudoElementTrait;
2324
use servo_arc::Arc;
2425

2526
/// Whether pseudo-elements should be resolved or not.

style/traversal.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ use crate::style_resolver::{PseudoElementResolution, StyleResolverForElement};
1515
use crate::stylist::RuleInclusion;
1616
use crate::traversal_flags::TraversalFlags;
1717
use selectors::matching::SelectorCaches;
18+
use selectors::parser::PseudoElement as PseudoElementTrait;
1819
use smallvec::SmallVec;
1920
use std::collections::HashMap;
2021

style/values/resolved/counters.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
//! Resolved values for counter properties
66
77
use super::{Context, ToResolvedValue};
8+
use selectors::parser::PseudoElement;
89
use crate::values::computed;
910

1011
/// https://drafts.csswg.org/css-content/#content-property

0 commit comments

Comments
 (0)