aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorEmilio Cobos Álvarez <emilio@crisal.io>2019-05-01 17:25:13 +0000
committerEmilio Cobos Álvarez <emilio@crisal.io>2019-05-07 12:55:44 +0200
commita23ad3be501723e14a97bfb3d0f1c2b6ec21cdab (patch)
treeaf2cfe9589c5fb020b76eb9424d7b05b3548b2af
parent9f73576f6a61b3f1866d5eb509b849f6ad5aea36 (diff)
downloadservo-a23ad3be501723e14a97bfb3d0f1c2b6ec21cdab.tar.gz
servo-a23ad3be501723e14a97bfb3d0f1c2b6ec21cdab.zip
style: Add parsing support for ::part().
Disabled for now of course. This should be pretty uncontroversial I'd think. Differential Revision: https://phabricator.services.mozilla.com/D28060
-rw-r--r--components/malloc_size_of/lib.rs1
-rw-r--r--components/selectors/builder.rs1
-rw-r--r--components/selectors/matching.rs3
-rw-r--r--components/selectors/parser.rs95
-rw-r--r--components/selectors/tree.rs5
-rw-r--r--components/style/gecko/selector_parser.rs1
-rw-r--r--components/style/gecko/wrapper.rs4
-rw-r--r--components/style/invalidation/element/element_wrapper.rs4
-rw-r--r--components/style/invalidation/element/invalidation_map.rs1
-rw-r--r--components/style/invalidation/element/invalidator.rs8
10 files changed, 106 insertions, 17 deletions
diff --git a/components/malloc_size_of/lib.rs b/components/malloc_size_of/lib.rs
index b59e26391ef..a305f86f7cb 100644
--- a/components/malloc_size_of/lib.rs
+++ b/components/malloc_size_of/lib.rs
@@ -747,6 +747,7 @@ where
Component::ExplicitUniversalType |
Component::LocalName(..) |
Component::ID(..) |
+ Component::Part(..) |
Component::Class(..) |
Component::AttributeInNoNamespaceExists { .. } |
Component::AttributeInNoNamespace { .. } |
diff --git a/components/selectors/builder.rs b/components/selectors/builder.rs
index 72404de1084..73c1575371c 100644
--- a/components/selectors/builder.rs
+++ b/components/selectors/builder.rs
@@ -270,6 +270,7 @@ where
Component::Combinator(..) => {
unreachable!("Found combinator in simple selectors vector?");
},
+ Component::Part(..) |
Component::PseudoElement(..) | Component::LocalName(..) => {
specificity.element_selectors += 1
},
diff --git a/components/selectors/matching.rs b/components/selectors/matching.rs
index f50dc8ddecf..aa200cc4b26 100644
--- a/components/selectors/matching.rs
+++ b/components/selectors/matching.rs
@@ -450,6 +450,7 @@ where
element.containing_shadow_host()
},
+ Combinator::Part => element.containing_shadow_host(),
Combinator::SlotAssignment => {
debug_assert!(
context.current_host.is_some(),
@@ -517,6 +518,7 @@ where
Combinator::Child |
Combinator::Descendant |
Combinator::SlotAssignment |
+ Combinator::Part |
Combinator::PseudoElement => SelectorMatchingResult::NotMatchedGlobally,
};
@@ -671,6 +673,7 @@ where
match *selector {
Component::Combinator(_) => unreachable!(),
+ Component::Part(ref part) => element.is_part(part),
Component::Slotted(ref selector) => {
// <slots> are never flattened tree slottables.
!element.is_html_slot_element() &&
diff --git a/components/selectors/parser.rs b/components/selectors/parser.rs
index 21459e4e46d..45cbb56236f 100644
--- a/components/selectors/parser.rs
+++ b/components/selectors/parser.rs
@@ -68,24 +68,30 @@ bitflags! {
/// Whether we're inside a negation. If we're inside a negation, we're
/// not allowed to add another negation or such, for example.
const INSIDE_NEGATION = 1 << 0;
- /// Whether we've parsed an ::slotted() pseudo-element already.
+ /// Whether we've parsed a ::slotted() pseudo-element already.
///
/// If so, then we can only parse a subset of pseudo-elements, and
/// whatever comes after them if so.
const AFTER_SLOTTED = 1 << 1;
+ /// Whether we've parsed a ::part() pseudo-element already.
+ ///
+ /// If so, then we can only parse a subset of pseudo-elements, and
+ /// whatever comes after them if so.
+ const AFTER_PART = 1 << 2;
/// Whether we've parsed a pseudo-element (as in, an
- /// `Impl::PseudoElement` thus not accounting for `::slotted`) already.
+ /// `Impl::PseudoElement` thus not accounting for `::slotted` or
+ /// `::part`) already.
///
/// If so, then other pseudo-elements and most other selectors are
/// disallowed.
- const AFTER_PSEUDO_ELEMENT = 1 << 2;
+ const AFTER_PSEUDO_ELEMENT = 1 << 3;
/// Whether we've parsed a non-stateful pseudo-element (again, as-in
/// `Impl::PseudoElement`) already. If so, then other pseudo-classes are
/// disallowed. If this flag is set, `AFTER_PSEUDO_ELEMENT` must be set
/// as well.
- const AFTER_NON_STATEFUL_PSEUDO_ELEMENT = 1 << 3;
+ const AFTER_NON_STATEFUL_PSEUDO_ELEMENT = 1 << 4;
/// Whether we are after any of the pseudo-like things.
- const AFTER_PSEUDO = Self::AFTER_SLOTTED.bits | Self::AFTER_PSEUDO_ELEMENT.bits;
+ const AFTER_PSEUDO = Self::AFTER_PART.bits | Self::AFTER_SLOTTED.bits | Self::AFTER_PSEUDO_ELEMENT.bits;
}
}
@@ -100,6 +106,14 @@ impl SelectorParsingState {
!self.intersects(SelectorParsingState::AFTER_PSEUDO)
}
+ // TODO(emilio): Should we allow other ::part()s after ::part()?
+ //
+ // See https://github.com/w3c/csswg-drafts/issues/3841
+ #[inline]
+ fn allows_part(self) -> bool {
+ !self.intersects(SelectorParsingState::AFTER_PSEUDO)
+ }
+
#[inline]
fn allows_non_functional_pseudo_classes(self) -> bool {
!self.intersects(SelectorParsingState::AFTER_SLOTTED | SelectorParsingState::AFTER_NON_STATEFUL_PSEUDO_ELEMENT)
@@ -156,6 +170,7 @@ macro_rules! with_all_bounds {
type AttrValue: $($InSelector)*;
type Identifier: $($InSelector)*;
type ClassName: $($InSelector)*;
+ type PartName: $($InSelector)*;
type LocalName: $($InSelector)* + Borrow<Self::BorrowedLocalName>;
type NamespaceUrl: $($CommonBounds)* + Default + Borrow<Self::BorrowedNamespaceUrl>;
type NamespacePrefix: $($InSelector)* + Default;
@@ -196,6 +211,11 @@ pub trait Parser<'i> {
false
}
+ /// Whether to parse the `::part()` pseudo-element.
+ fn parse_part(&self) -> bool {
+ false
+ }
+
/// Whether to parse the `:host` pseudo-class.
fn parse_host(&self) -> bool {
false
@@ -841,6 +861,9 @@ pub enum Combinator {
/// Another combinator used for ::slotted(), which represent the jump from
/// a node to its assigned slot.
SlotAssignment,
+ /// Another combinator used for `::part()`, which represents the jump from
+ /// the part to the containing shadow host.
+ Part,
}
impl Combinator {
@@ -934,8 +957,7 @@ pub enum Component<Impl: SelectorImpl> {
LastOfType,
OnlyOfType,
NonTSPseudoClass(#[shmem(field_bound)] Impl::NonTSPseudoClass),
- /// The ::slotted() pseudo-element (which isn't actually a pseudo-element,
- /// and probably should be a pseudo-class):
+ /// The ::slotted() pseudo-element:
///
/// https://drafts.csswg.org/css-scoping/#slotted-pseudo
///
@@ -947,6 +969,9 @@ pub enum Component<Impl: SelectorImpl> {
///
/// See https://github.com/w3c/csswg-drafts/issues/2158
Slotted(Selector<Impl>),
+ /// The `::part` pseudo-element.
+ /// https://drafts.csswg.org/css-shadow-parts/#part
+ Part(#[shmem(field_bound)] Impl::PartName),
/// The `:host` pseudo-class:
///
/// https://drafts.csswg.org/css-scoping/#host-selector
@@ -1196,7 +1221,8 @@ impl ToCss for Combinator {
Combinator::Descendant => dest.write_str(" "),
Combinator::NextSibling => dest.write_str(" + "),
Combinator::LaterSibling => dest.write_str(" ~ "),
- Combinator::PseudoElement => Ok(()),
+ Combinator::PseudoElement |
+ Combinator::Part |
Combinator::SlotAssignment => Ok(()),
}
}
@@ -1236,6 +1262,11 @@ impl<Impl: SelectorImpl> ToCss for Component<Impl> {
selector.to_css(dest)?;
dest.write_char(')')
},
+ Part(ref part_name) => {
+ dest.write_str("::part(")?;
+ display_to_css_identifier(part_name, dest)?;
+ dest.write_char(')')
+ },
PseudoElement(ref p) => p.to_css(dest),
ID(ref s) => {
dest.write_char('#')?;
@@ -1407,15 +1438,12 @@ where
{
let mut builder = SelectorBuilder::default();
- let mut has_pseudo_element;
- let mut slotted;
+ let mut has_pseudo_element = false;
+ let mut slotted = false;
'outer_loop: loop {
// Parse a sequence of simple selectors.
- match parse_compound_selector(parser, input, &mut builder)? {
- Some(state) => {
- has_pseudo_element = state.intersects(SelectorParsingState::AFTER_PSEUDO_ELEMENT);
- slotted = state.intersects(SelectorParsingState::AFTER_SLOTTED);
- },
+ let state = match parse_compound_selector(parser, input, &mut builder)? {
+ Some(state) => state,
None => {
return Err(input.new_custom_error(if builder.has_combinators() {
SelectorParseErrorKind::DanglingCombinator
@@ -1425,7 +1453,11 @@ where
},
};
- if has_pseudo_element || slotted {
+ if state.intersects(SelectorParsingState::AFTER_PSEUDO) {
+ has_pseudo_element = state.intersects(SelectorParsingState::AFTER_PSEUDO_ELEMENT);
+ slotted = state.intersects(SelectorParsingState::AFTER_SLOTTED);
+ let part = state.intersects(SelectorParsingState::AFTER_PART);
+ debug_assert!(has_pseudo_element || slotted || part);
break;
}
@@ -1463,6 +1495,8 @@ where
builder.push_combinator(combinator);
}
+ // TODO(emilio): We'll have to flag part() somehow as well, but we need more
+ // bits!
Ok(Selector(builder.build(has_pseudo_element, slotted)))
}
@@ -1553,6 +1587,7 @@ enum SimpleSelectorParseResult<Impl: SelectorImpl> {
SimpleSelector(Component<Impl>),
PseudoElement(Impl::PseudoElement),
SlottedPseudo(Selector<Impl>),
+ PartPseudo(Impl::PartName),
}
#[derive(Debug)]
@@ -1899,6 +1934,7 @@ where
return Err(input.new_custom_error(SelectorParseErrorKind::EmptyNegation));
},
Some(SimpleSelectorParseResult::PseudoElement(_)) |
+ Some(SimpleSelectorParseResult::PartPseudo(_)) |
Some(SimpleSelectorParseResult::SlottedPseudo(_)) => {
let e = SelectorParseErrorKind::NonSimpleSelectorInNegation;
return Err(input.new_custom_error(e));
@@ -1955,6 +1991,11 @@ where
SimpleSelectorParseResult::SimpleSelector(s) => {
builder.push_simple_selector(s);
},
+ SimpleSelectorParseResult::PartPseudo(part_name) => {
+ state.insert(SelectorParsingState::AFTER_PART);
+ builder.push_combinator(Combinator::Part);
+ builder.push_simple_selector(Component::Part(part_name));
+ },
SimpleSelectorParseResult::SlottedPseudo(selector) => {
state.insert(SelectorParsingState::AFTER_SLOTTED);
if !builder.is_empty() {
@@ -2115,6 +2156,15 @@ where
return Err(input.new_custom_error(SelectorParseErrorKind::InvalidState));
}
let pseudo_element = if is_functional {
+ if P::parse_part(parser) && name.eq_ignore_ascii_case("part") {
+ if !state.allows_part() {
+ return Err(input.new_custom_error(SelectorParseErrorKind::InvalidState));
+ }
+ let name = input.parse_nested_block(|input| {
+ Ok(input.expect_ident()?.as_ref().into())
+ })?;
+ return Ok(Some(SimpleSelectorParseResult::PartPseudo(name)));
+ }
if P::parse_slotted(parser) && name.eq_ignore_ascii_case("slotted") {
if !state.allows_slotted() {
return Err(input.new_custom_error(SelectorParseErrorKind::InvalidState));
@@ -2298,6 +2348,7 @@ pub mod tests {
type AttrValue = DummyAtom;
type Identifier = DummyAtom;
type ClassName = DummyAtom;
+ type PartName = DummyAtom;
type LocalName = DummyAtom;
type NamespaceUrl = DummyAtom;
type NamespacePrefix = DummyAtom;
@@ -2336,6 +2387,10 @@ pub mod tests {
true
}
+ fn parse_part(&self) -> bool {
+ true
+ }
+
fn parse_non_ts_pseudo_class(
&self,
location: SourceLocation,
@@ -2910,6 +2965,14 @@ pub mod tests {
assert!(parse("::slotted(div).foo").is_err());
assert!(parse("::slotted(div + bar)").is_err());
assert!(parse("::slotted(div) + foo").is_err());
+
+ assert!(parse("::part()").is_err());
+ assert!(parse("::part(42)").is_err());
+ // Though note https://github.com/w3c/csswg-drafts/issues/3502
+ assert!(parse("::part(foo bar)").is_err());
+ assert!(parse("::part(foo):hover").is_ok());
+ assert!(parse("::part(foo) + bar").is_err());
+
assert!(parse("div ::slotted(div)").is_ok());
assert!(parse("div + slot::slotted(div)").is_ok());
assert!(parse("div + slot::slotted(div.foo)").is_ok());
diff --git a/components/selectors/tree.rs b/components/selectors/tree.rs
index badfca86ed6..b57876308a5 100644
--- a/components/selectors/tree.rs
+++ b/components/selectors/tree.rs
@@ -110,6 +110,11 @@ pub trait Element: Sized + Clone + Debug {
case_sensitivity: CaseSensitivity,
) -> bool;
+ fn is_part(
+ &self,
+ name: &<Self::Impl as SelectorImpl>::PartName,
+ ) -> bool;
+
/// Returns whether this element matches `:empty`.
///
/// That is, whether it does not contain any child element or any non-zero-length text node.
diff --git a/components/style/gecko/selector_parser.rs b/components/style/gecko/selector_parser.rs
index ec0db9286ec..18718446c1a 100644
--- a/components/style/gecko/selector_parser.rs
+++ b/components/style/gecko/selector_parser.rs
@@ -289,6 +289,7 @@ impl ::selectors::SelectorImpl for SelectorImpl {
type AttrValue = Atom;
type Identifier = Atom;
type ClassName = Atom;
+ type PartName = Atom;
type LocalName = Atom;
type NamespacePrefix = Atom;
type NamespaceUrl = Namespace;
diff --git a/components/style/gecko/wrapper.rs b/components/style/gecko/wrapper.rs
index 079dde2f513..f88e30ab777 100644
--- a/components/style/gecko/wrapper.rs
+++ b/components/style/gecko/wrapper.rs
@@ -2259,6 +2259,10 @@ impl<'le> ::selectors::Element for GeckoElement<'le> {
case_sensitivity.eq_atom(element_id, id)
}
+ fn is_part(&self, _name: &Atom) -> bool {
+ unimplemented!();
+ }
+
#[inline(always)]
fn has_class(&self, name: &Atom, case_sensitivity: CaseSensitivity) -> bool {
let attr = match self.get_class_attr() {
diff --git a/components/style/invalidation/element/element_wrapper.rs b/components/style/invalidation/element/element_wrapper.rs
index c794eb15c2c..114cdc9cd8a 100644
--- a/components/style/invalidation/element/element_wrapper.rs
+++ b/components/style/invalidation/element/element_wrapper.rs
@@ -340,6 +340,10 @@ where
}
}
+ fn is_part(&self, _name: &Atom) -> bool {
+ unimplemented!();
+ }
+
fn has_class(&self, name: &Atom, case_sensitivity: CaseSensitivity) -> bool {
match self.snapshot() {
Some(snapshot) if snapshot.has_attrs() => snapshot.has_class(name, case_sensitivity),
diff --git a/components/style/invalidation/element/invalidation_map.rs b/components/style/invalidation/element/invalidation_map.rs
index e4cc8115760..63d6eb6acc4 100644
--- a/components/style/invalidation/element/invalidation_map.rs
+++ b/components/style/invalidation/element/invalidation_map.rs
@@ -98,6 +98,7 @@ impl Dependency {
// an eager pseudo, and return only Descendants here if not.
Some(Combinator::PseudoElement) => DependencyInvalidationKind::ElementAndDescendants,
Some(Combinator::SlotAssignment) => DependencyInvalidationKind::SlottedElements,
+ Some(Combinator::Part) => unimplemented!("Need to add invalidation for shadow parts"),
}
}
}
diff --git a/components/style/invalidation/element/invalidator.rs b/components/style/invalidation/element/invalidator.rs
index 371c0771183..ec1548f4aa3 100644
--- a/components/style/invalidation/element/invalidator.rs
+++ b/components/style/invalidation/element/invalidator.rs
@@ -158,7 +158,10 @@ impl<'a> Invalidation<'a> {
// We should be able to do better here!
match self.selector.combinator_at_parse_order(self.offset - 1) {
Combinator::Descendant | Combinator::LaterSibling | Combinator::PseudoElement => true,
- Combinator::SlotAssignment | Combinator::NextSibling | Combinator::Child => false,
+ Combinator::Part |
+ Combinator::SlotAssignment |
+ Combinator::NextSibling |
+ Combinator::Child => false,
}
}
@@ -171,6 +174,9 @@ impl<'a> Invalidation<'a> {
Combinator::Child | Combinator::Descendant | Combinator::PseudoElement => {
InvalidationKind::Descendant(DescendantInvalidationKind::Dom)
},
+ Combinator::Part => {
+ unimplemented!("Need to add invalidation for shadow parts");
+ },
Combinator::SlotAssignment => {
InvalidationKind::Descendant(DescendantInvalidationKind::Slotted)
},