Bug 1911216 - Part 2: Support :only-child for view transition selectors. r=layout-reviewers,firefox-style-system-reviewers,emilio

Introduce the concept of pseudo element tree and make sure we achieve
the following behaviors:
1. `::view-transition` doesn't accept any non-functional pseudo class
   after it.
2. `::view-transition-*(name)` accepts only `only-child` pseudo class
   after it.

So for named view transition pseudo elements, we make them accept
non-functional pseudo classes and tree structural pseudo classes after
them, for consistency. However, only `:only-child` is allowed.

Differential Revision: https://phabricator.services.mozilla.com/D219714
This commit is contained in:
Boris Chiou 2024-09-03 19:29:33 +00:00
Родитель 7c3a0ab05d
Коммит d524f0f5e5
4 изменённых файлов: 63 добавлений и 147 удалений

Просмотреть файл

@ -45,6 +45,13 @@ pub trait PseudoElement: Sized + ToCss {
fn specificity_count(&self) -> u32 {
1
}
/// Whether this pseudo-element is in a pseudo-element tree (excluding the pseudo-element
/// root).
/// https://drafts.csswg.org/css-view-transitions-1/#pseudo-root
fn is_in_pseudo_element_tree(&self) -> bool {
false
}
}
/// A trait that represents a pseudo-class.
@ -83,7 +90,7 @@ fn to_ascii_lowercase(s: &str) -> Cow<str> {
bitflags! {
/// Flags that indicate at which point of parsing a selector are we.
#[derive(Copy, Clone)]
struct SelectorParsingState: u8 {
struct SelectorParsingState: u16 {
/// Whether we should avoid adding default namespaces to selectors that
/// aren't type or universal selectors.
const SKIP_DEFAULT_NAMESPACE = 1 << 0;
@ -122,6 +129,10 @@ bitflags! {
/// Whether we explicitly disallow relative selectors (i.e. `:has()`).
const DISALLOW_RELATIVE_SELECTOR = 1 << 7;
/// Whether we've parsed a pseudo-element which is in a pseudo-element tree (i.e. it is a
/// descendant pseudo of a pseudo-element root).
const IN_PSEUDO_ELEMENT_TREE = 1 << 8;
}
}
@ -149,13 +160,18 @@ impl SelectorParsingState {
#[inline]
fn allows_tree_structural_pseudo_classes(self) -> bool {
!self.intersects(Self::AFTER_PSEUDO)
!self.intersects(Self::AFTER_PSEUDO) || self.intersects(Self::IN_PSEUDO_ELEMENT_TREE)
}
#[inline]
fn allows_combinators(self) -> bool {
!self.intersects(Self::DISALLOW_COMBINATORS)
}
#[inline]
fn allows_only_child_pseudo_class_only(self) -> bool {
self.intersects(Self::IN_PSEUDO_ELEMENT_TREE)
}
}
pub type SelectorParseError<'i> = ParseError<'i, SelectorParseErrorKind<'i>>;
@ -3261,6 +3277,9 @@ where
if !p.accepts_state_pseudo_classes() {
state.insert(SelectorParsingState::AFTER_NON_STATEFUL_PSEUDO_ELEMENT);
}
if p.is_in_pseudo_element_tree() {
state.insert(SelectorParsingState::IN_PSEUDO_ELEMENT_TREE);
}
builder.push_combinator(Combinator::PseudoElement);
builder.push_simple_selector(Component::PseudoElement(p));
},
@ -3574,6 +3593,20 @@ where
}
if state.allows_tree_structural_pseudo_classes() {
// If a descendant pseudo of a pseudo-element root has no other siblings, then :only-child
// matches that pseudo. Note that we don't accept other tree structural pseudo classes in
// this case (to match other browsers). And the spec mentions only `:only-child` as well.
// https://drafts.csswg.org/css-view-transitions-1/#pseudo-root
if state.allows_only_child_pseudo_class_only() {
if name.eq_ignore_ascii_case("only-child") {
return Ok(Component::Nth(NthSelectorData::only(/* of_type = */ false)));
}
// Other non-functional pseudo classes are not allowed.
// FIXME: Perhaps we can refactor this, e.g. distinguish tree-structural pseudo classes
// from other non-ts pseudo classes. Otherwise, this special case looks weird.
return Err(location.new_custom_error(SelectorParseErrorKind::InvalidState));
}
match_ignore_ascii_case! { &name,
"first-child" => return Ok(Component::Nth(NthSelectorData::first(/* of_type = */ false))),
"last-child" => return Ok(Component::Nth(NthSelectorData::last(/* of_type = */ false))),

Просмотреть файл

@ -45,13 +45,22 @@ impl ::selectors::parser::PseudoElement for PseudoElement {
#[inline]
fn accepts_state_pseudo_classes(&self) -> bool {
self.supports_user_action_state()
// Note: if the pseudo element is a descendants of a pseudo element, `only-child` should be
// allowed after it.
self.supports_user_action_state() || self.is_in_pseudo_element_tree()
}
#[inline]
fn specificity_count(&self) -> u32 {
self.specificity_count()
}
#[inline]
fn is_in_pseudo_element_tree(&self) -> bool {
// All the named view transition pseudo-elements are the descendants of a pseudo-element
// root.
self.is_named_view_transition()
}
}
impl PseudoElement {
@ -170,6 +179,17 @@ impl PseudoElement {
*self == PseudoElement::TargetText
}
/// Whether this pseudo-element is a named view transition pseudo-element.
pub fn is_named_view_transition(&self) -> bool {
matches!(
*self,
Self::ViewTransitionGroup(..) |
Self::ViewTransitionImagePair(..) |
Self::ViewTransitionOld(..) |
Self::ViewTransitionNew(..)
)
}
/// The count we contribute to the specificity from this pseudo-element.
pub fn specificity_count(&self) -> u32 {
match *self {

Просмотреть файл

@ -1,144 +0,0 @@
[pseudo-elements-valid.html]
["::view-transition-group(*):only-child" should be a valid selector]
expected: FAIL
[":root::view-transition-group(*):only-child" should be a valid selector]
expected: FAIL
[".a::view-transition-group(*):only-child" should be a valid selector]
expected: FAIL
["div ::view-transition-group(*):only-child" should be a valid selector]
expected: FAIL
["::view-transition-group(root):only-child" should be a valid selector]
expected: FAIL
[":root::view-transition-group(root):only-child" should be a valid selector]
expected: FAIL
[".a::view-transition-group(root):only-child" should be a valid selector]
expected: FAIL
["div ::view-transition-group(root):only-child" should be a valid selector]
expected: FAIL
["::view-transition-group(dashed-ident):only-child" should be a valid selector]
expected: FAIL
[":root::view-transition-group(dashed-ident):only-child" should be a valid selector]
expected: FAIL
[".a::view-transition-group(dashed-ident):only-child" should be a valid selector]
expected: FAIL
["div ::view-transition-group(dashed-ident):only-child" should be a valid selector]
expected: FAIL
["::view-transition-image-pair(*):only-child" should be a valid selector]
expected: FAIL
[":root::view-transition-image-pair(*):only-child" should be a valid selector]
expected: FAIL
[".a::view-transition-image-pair(*):only-child" should be a valid selector]
expected: FAIL
["div ::view-transition-image-pair(*):only-child" should be a valid selector]
expected: FAIL
["::view-transition-image-pair(root):only-child" should be a valid selector]
expected: FAIL
[":root::view-transition-image-pair(root):only-child" should be a valid selector]
expected: FAIL
[".a::view-transition-image-pair(root):only-child" should be a valid selector]
expected: FAIL
["div ::view-transition-image-pair(root):only-child" should be a valid selector]
expected: FAIL
["::view-transition-image-pair(dashed-ident):only-child" should be a valid selector]
expected: FAIL
[":root::view-transition-image-pair(dashed-ident):only-child" should be a valid selector]
expected: FAIL
[".a::view-transition-image-pair(dashed-ident):only-child" should be a valid selector]
expected: FAIL
["div ::view-transition-image-pair(dashed-ident):only-child" should be a valid selector]
expected: FAIL
["::view-transition-old(*):only-child" should be a valid selector]
expected: FAIL
[":root::view-transition-old(*):only-child" should be a valid selector]
expected: FAIL
[".a::view-transition-old(*):only-child" should be a valid selector]
expected: FAIL
["div ::view-transition-old(*):only-child" should be a valid selector]
expected: FAIL
["::view-transition-old(root):only-child" should be a valid selector]
expected: FAIL
[":root::view-transition-old(root):only-child" should be a valid selector]
expected: FAIL
[".a::view-transition-old(root):only-child" should be a valid selector]
expected: FAIL
["div ::view-transition-old(root):only-child" should be a valid selector]
expected: FAIL
["::view-transition-old(dashed-ident):only-child" should be a valid selector]
expected: FAIL
[":root::view-transition-old(dashed-ident):only-child" should be a valid selector]
expected: FAIL
[".a::view-transition-old(dashed-ident):only-child" should be a valid selector]
expected: FAIL
["div ::view-transition-old(dashed-ident):only-child" should be a valid selector]
expected: FAIL
["::view-transition-new(*):only-child" should be a valid selector]
expected: FAIL
[":root::view-transition-new(*):only-child" should be a valid selector]
expected: FAIL
[".a::view-transition-new(*):only-child" should be a valid selector]
expected: FAIL
["div ::view-transition-new(*):only-child" should be a valid selector]
expected: FAIL
["::view-transition-new(root):only-child" should be a valid selector]
expected: FAIL
[":root::view-transition-new(root):only-child" should be a valid selector]
expected: FAIL
[".a::view-transition-new(root):only-child" should be a valid selector]
expected: FAIL
["div ::view-transition-new(root):only-child" should be a valid selector]
expected: FAIL
["::view-transition-new(dashed-ident):only-child" should be a valid selector]
expected: FAIL
[":root::view-transition-new(dashed-ident):only-child" should be a valid selector]
expected: FAIL
[".a::view-transition-new(dashed-ident):only-child" should be a valid selector]
expected: FAIL
["div ::view-transition-new(dashed-ident):only-child" should be a valid selector]
expected: FAIL

Просмотреть файл

@ -18,11 +18,18 @@ function test_invalid_selector_combinations(pseudo) {
test_invalid_selector(`${pseudo}.a`);
test_invalid_selector(`${pseudo} div`);
test_invalid_selector(`${pseudo}:hover`);
test_invalid_selector(`${pseudo}:active`);
test_invalid_selector(`${pseudo}:first-child`);
test_invalid_selector(`${pseudo}:last-child`);
test_invalid_selector(`${pseudo}:empty`);
test_invalid_selector(`${pseudo}:visited`);
test_invalid_selector(`${pseudo}:enabled`);
test_invalid_selector(`:not(${pseudo})`);
test_invalid_selector(`:has(${pseudo})`);
}
test_invalid_selector_combinations("::view-transition");
test_invalid_selector("::view-transition:only-child");
test_invalid_selector("::view-transition(*)");
test_invalid_selector("::view-transition(valid)");
test_invalid_selector("::view-transition(root)");