mirror of
https://github.com/servo/servo.git
synced 2025-08-04 05:00:08 +01:00
style: Implement the functional :host(..) selector.
We could invalidate in a slightly more fine-grained way, but I don't think it's worth the churn vs. keeping the special-cases minimal. Bug: 1452640 Reviewed-by: xidorn MozReview-Commit-ID: 5DkQrgwg9GW
This commit is contained in:
parent
bfb9fe6159
commit
eaefaa890e
8 changed files with 82 additions and 47 deletions
|
@ -314,7 +314,7 @@ fn complex_selector_specificity<Impl>(mut iter: slice::Iter<Component<Impl>>)
|
||||||
Component::FirstChild | Component::LastChild |
|
Component::FirstChild | Component::LastChild |
|
||||||
Component::OnlyChild | Component::Root |
|
Component::OnlyChild | Component::Root |
|
||||||
Component::Empty | Component::Scope |
|
Component::Empty | Component::Scope |
|
||||||
Component::Host |
|
Component::Host(..) |
|
||||||
Component::NthChild(..) |
|
Component::NthChild(..) |
|
||||||
Component::NthLastChild(..) |
|
Component::NthLastChild(..) |
|
||||||
Component::NthOfType(..) |
|
Component::NthOfType(..) |
|
||||||
|
|
|
@ -692,10 +692,10 @@ where
|
||||||
match *selector {
|
match *selector {
|
||||||
Component::Combinator(_) => unreachable!(),
|
Component::Combinator(_) => unreachable!(),
|
||||||
Component::Slotted(ref selector) => {
|
Component::Slotted(ref selector) => {
|
||||||
context.shared.nest(|context| {
|
|
||||||
// <slots> are never flattened tree slottables.
|
// <slots> are never flattened tree slottables.
|
||||||
!element.is_html_slot_element() &&
|
!element.is_html_slot_element() &&
|
||||||
element.assigned_slot().is_some() &&
|
element.assigned_slot().is_some() &&
|
||||||
|
context.shared.nest(|context| {
|
||||||
matches_complex_selector(
|
matches_complex_selector(
|
||||||
selector.iter(),
|
selector.iter(),
|
||||||
element,
|
element,
|
||||||
|
@ -814,8 +814,18 @@ where
|
||||||
flags_setter(element, ElementSelectorFlags::HAS_EMPTY_SELECTOR);
|
flags_setter(element, ElementSelectorFlags::HAS_EMPTY_SELECTOR);
|
||||||
element.is_empty()
|
element.is_empty()
|
||||||
}
|
}
|
||||||
Component::Host => {
|
Component::Host(ref selector) => {
|
||||||
context.shared.shadow_host().map_or(false, |host| host == element.opaque())
|
context.shared.shadow_host().map_or(false, |host| host == element.opaque()) &&
|
||||||
|
selector.as_ref().map_or(true, |selector| {
|
||||||
|
context.shared.nest(|context| {
|
||||||
|
matches_complex_selector(
|
||||||
|
selector.iter(),
|
||||||
|
element,
|
||||||
|
context,
|
||||||
|
flags_setter,
|
||||||
|
)
|
||||||
|
})
|
||||||
|
})
|
||||||
}
|
}
|
||||||
Component::Scope => {
|
Component::Scope => {
|
||||||
match context.shared.scope_element {
|
match context.shared.scope_element {
|
||||||
|
|
|
@ -381,12 +381,15 @@ where
|
||||||
}
|
}
|
||||||
|
|
||||||
match *self {
|
match *self {
|
||||||
Slotted(ref selectors) => {
|
Slotted(ref selector) => {
|
||||||
for selector in selectors.iter() {
|
|
||||||
if !selector.visit(visitor) {
|
if !selector.visit(visitor) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Host(Some(ref selector)) => {
|
||||||
|
if !selector.visit(visitor) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Negation(ref negated) => {
|
Negation(ref negated) => {
|
||||||
for component in negated.iter() {
|
for component in negated.iter() {
|
||||||
|
@ -618,7 +621,7 @@ impl<'a, Impl: 'a + SelectorImpl> SelectorIter<'a, Impl> {
|
||||||
/// combinators to the left.
|
/// combinators to the left.
|
||||||
#[inline]
|
#[inline]
|
||||||
pub(crate) fn is_featureless_host_selector(&mut self) -> bool {
|
pub(crate) fn is_featureless_host_selector(&mut self) -> bool {
|
||||||
self.all(|component| matches!(*component, Component::Host)) &&
|
self.all(|component| matches!(*component, Component::Host(..))) &&
|
||||||
self.next_sequence().is_none()
|
self.next_sequence().is_none()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -793,10 +796,6 @@ pub enum Component<Impl: SelectorImpl> {
|
||||||
Root,
|
Root,
|
||||||
Empty,
|
Empty,
|
||||||
Scope,
|
Scope,
|
||||||
/// The `:host` pseudo-class:
|
|
||||||
///
|
|
||||||
/// https://drafts.csswg.org/css-scoping/#host-selector
|
|
||||||
Host,
|
|
||||||
NthChild(i32, i32),
|
NthChild(i32, i32),
|
||||||
NthLastChild(i32, i32),
|
NthLastChild(i32, i32),
|
||||||
NthOfType(i32, i32),
|
NthOfType(i32, i32),
|
||||||
|
@ -815,7 +814,19 @@ pub enum Component<Impl: SelectorImpl> {
|
||||||
/// NOTE(emilio): This should support a list of selectors, but as of this
|
/// NOTE(emilio): This should support a list of selectors, but as of this
|
||||||
/// writing no other browser does, and that allows them to put ::slotted()
|
/// writing no other browser does, and that allows them to put ::slotted()
|
||||||
/// in the rule hash, so we do that too.
|
/// in the rule hash, so we do that too.
|
||||||
|
///
|
||||||
|
/// See https://github.com/w3c/csswg-drafts/issues/2158
|
||||||
Slotted(Selector<Impl>),
|
Slotted(Selector<Impl>),
|
||||||
|
/// The `:host` pseudo-class:
|
||||||
|
///
|
||||||
|
/// https://drafts.csswg.org/css-scoping/#host-selector
|
||||||
|
///
|
||||||
|
/// NOTE(emilio): This should support a list of selectors, but as of this
|
||||||
|
/// writing no other browser does, and that allows them to put :host()
|
||||||
|
/// in the rule hash, so we do that too.
|
||||||
|
///
|
||||||
|
/// See https://github.com/w3c/csswg-drafts/issues/2158
|
||||||
|
Host(Option<Selector<Impl>>),
|
||||||
PseudoElement(Impl::PseudoElement),
|
PseudoElement(Impl::PseudoElement),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1119,7 +1130,15 @@ impl<Impl: SelectorImpl> ToCss for Component<Impl> {
|
||||||
Root => dest.write_str(":root"),
|
Root => dest.write_str(":root"),
|
||||||
Empty => dest.write_str(":empty"),
|
Empty => dest.write_str(":empty"),
|
||||||
Scope => dest.write_str(":scope"),
|
Scope => dest.write_str(":scope"),
|
||||||
Host => dest.write_str(":host"),
|
Host(ref selector) => {
|
||||||
|
dest.write_str(":host")?;
|
||||||
|
if let Some(ref selector) = *selector {
|
||||||
|
dest.write_char('(')?;
|
||||||
|
selector.to_css(dest)?;
|
||||||
|
dest.write_char(')')?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
},
|
||||||
FirstOfType => dest.write_str(":first-of-type"),
|
FirstOfType => dest.write_str(":first-of-type"),
|
||||||
LastOfType => dest.write_str(":last-of-type"),
|
LastOfType => dest.write_str(":last-of-type"),
|
||||||
OnlyOfType => dest.write_str(":only-of-type"),
|
OnlyOfType => dest.write_str(":only-of-type"),
|
||||||
|
@ -1816,6 +1835,7 @@ where
|
||||||
"nth-of-type" => return Ok(parse_nth_pseudo_class(input, Component::NthOfType)?),
|
"nth-of-type" => return Ok(parse_nth_pseudo_class(input, Component::NthOfType)?),
|
||||||
"nth-last-child" => return Ok(parse_nth_pseudo_class(input, Component::NthLastChild)?),
|
"nth-last-child" => return Ok(parse_nth_pseudo_class(input, Component::NthLastChild)?),
|
||||||
"nth-last-of-type" => return Ok(parse_nth_pseudo_class(input, Component::NthLastOfType)?),
|
"nth-last-of-type" => return Ok(parse_nth_pseudo_class(input, Component::NthLastOfType)?),
|
||||||
|
"host" => return Ok(Component::Host(Some(parse_inner_compound_selector(parser, input)?))),
|
||||||
"not" => {
|
"not" => {
|
||||||
if inside_negation {
|
if inside_negation {
|
||||||
return Err(input.new_custom_error(
|
return Err(input.new_custom_error(
|
||||||
|
@ -1969,7 +1989,7 @@ where
|
||||||
"root" => Ok(Component::Root),
|
"root" => Ok(Component::Root),
|
||||||
"empty" => Ok(Component::Empty),
|
"empty" => Ok(Component::Empty),
|
||||||
"scope" => Ok(Component::Scope),
|
"scope" => Ok(Component::Scope),
|
||||||
"host" if P::parse_host(parser) => Ok(Component::Host),
|
"host" if P::parse_host(parser) => Ok(Component::Host(None)),
|
||||||
"first-of-type" => Ok(Component::FirstOfType),
|
"first-of-type" => Ok(Component::FirstOfType),
|
||||||
"last-of-type" => Ok(Component::LastOfType),
|
"last-of-type" => Ok(Component::LastOfType),
|
||||||
"only-of-type" => Ok(Component::OnlyOfType),
|
"only-of-type" => Ok(Component::OnlyOfType),
|
||||||
|
|
|
@ -794,6 +794,14 @@ pub trait TElement
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if let Some(shadow) = self.shadow_root() {
|
||||||
|
f(
|
||||||
|
shadow.style_data(),
|
||||||
|
self.as_node().owner_doc().quirks_mode(),
|
||||||
|
Some(shadow.host()),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
let mut current = self.assigned_slot();
|
let mut current = self.assigned_slot();
|
||||||
while let Some(slot) = current {
|
while let Some(slot) = current {
|
||||||
// Slots can only have assigned nodes when in a shadow tree.
|
// Slots can only have assigned nodes when in a shadow tree.
|
||||||
|
|
|
@ -207,16 +207,6 @@ impl InvalidationMap {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Adds a selector to this `InvalidationMap`. Returns Err(..) to
|
|
||||||
/// signify OOM.
|
|
||||||
pub fn note_selector(
|
|
||||||
&mut self,
|
|
||||||
selector: &Selector<SelectorImpl>,
|
|
||||||
quirks_mode: QuirksMode,
|
|
||||||
) -> Result<(), FailedAllocationError> {
|
|
||||||
self.collect_invalidations_for(selector, quirks_mode)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Clears this map, leaving it empty.
|
/// Clears this map, leaving it empty.
|
||||||
pub fn clear(&mut self) {
|
pub fn clear(&mut self) {
|
||||||
self.class_to_selector.clear();
|
self.class_to_selector.clear();
|
||||||
|
@ -228,13 +218,14 @@ impl InvalidationMap {
|
||||||
self.has_class_attribute_selectors = false;
|
self.has_class_attribute_selectors = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Returns Err(..) to signify OOM.
|
/// Adds a selector to this `InvalidationMap`. Returns Err(..) to
|
||||||
fn collect_invalidations_for(
|
/// signify OOM.
|
||||||
|
pub fn note_selector(
|
||||||
&mut self,
|
&mut self,
|
||||||
selector: &Selector<SelectorImpl>,
|
selector: &Selector<SelectorImpl>,
|
||||||
quirks_mode: QuirksMode
|
quirks_mode: QuirksMode,
|
||||||
) -> Result<(), FailedAllocationError> {
|
) -> Result<(), FailedAllocationError> {
|
||||||
debug!("InvalidationMap::collect_invalidations_for({:?})", selector);
|
debug!("InvalidationMap::note_selector({:?})", selector);
|
||||||
|
|
||||||
let mut iter = selector.iter();
|
let mut iter = selector.iter();
|
||||||
let mut combinator;
|
let mut combinator;
|
||||||
|
|
|
@ -6,7 +6,7 @@
|
||||||
//! element styles need to be invalidated.
|
//! element styles need to be invalidated.
|
||||||
|
|
||||||
use context::StackLimitChecker;
|
use context::StackLimitChecker;
|
||||||
use dom::{TElement, TNode};
|
use dom::{TElement, TNode, TShadowRoot};
|
||||||
use selector_parser::SelectorImpl;
|
use selector_parser::SelectorImpl;
|
||||||
use selectors::matching::{CompoundSelectorMatchingResult, MatchingContext};
|
use selectors::matching::{CompoundSelectorMatchingResult, MatchingContext};
|
||||||
use selectors::matching::matches_compound_selector_from;
|
use selectors::matching::matches_compound_selector_from;
|
||||||
|
@ -534,20 +534,22 @@ where
|
||||||
|
|
||||||
let mut any_descendant = false;
|
let mut any_descendant = false;
|
||||||
|
|
||||||
// NOTE(emilio): This should not be needed for Shadow DOM for normal
|
// NOTE(emilio): This is only needed for Shadow DOM to invalidate
|
||||||
// element state / attribute invalidations (it's needed for XBL though,
|
// correctly on :host(..) changes. Instead of doing this, we could add
|
||||||
// due to the weird way the anon content there works (it doesn't block
|
// a third kind of invalidation list that walks shadow root children,
|
||||||
// combinators)).
|
// but it's not clear it's worth it.
|
||||||
//
|
//
|
||||||
// However, it's needed as of right now for document state invalidation,
|
// Also, it's needed as of right now for document state invalidation,
|
||||||
// were we rely on iterating every element that ends up in the composed
|
// where we rely on iterating every element that ends up in the composed
|
||||||
// doc.
|
// doc, but we could fix that invalidating per subtree.
|
||||||
//
|
if let Some(root) = self.element.shadow_root() {
|
||||||
// Also, we could avoid having that special-case for document state
|
any_descendant |=
|
||||||
// invalidations if we invalidate for document state changes per
|
self.invalidate_dom_descendants_of(root.as_node(), invalidations);
|
||||||
// subtree, though that's kind of annoying because we need to invalidate
|
}
|
||||||
// the shadow host subtree (to handle :host and ::slotted), and the
|
|
||||||
// actual shadow tree (to handle all other rules in the ShadowRoot).
|
// This is needed for XBL (technically) unconditionally, because XBL
|
||||||
|
// bindings do not block combinators in any way. However this is kinda
|
||||||
|
// broken anyway, since we should be looking at XBL rules too.
|
||||||
if let Some(anon_content) = self.element.xbl_binding_anonymous_content() {
|
if let Some(anon_content) = self.element.xbl_binding_anonymous_content() {
|
||||||
any_descendant |=
|
any_descendant |=
|
||||||
self.invalidate_dom_descendants_of(anon_content, invalidations);
|
self.invalidate_dom_descendants_of(anon_content, invalidations);
|
||||||
|
|
|
@ -457,6 +457,7 @@ fn specific_bucket_for<'a>(
|
||||||
//
|
//
|
||||||
// Meanwhile taking the code path below is slower, but still correct.
|
// Meanwhile taking the code path below is slower, but still correct.
|
||||||
// Component::Slotted(ref selector) => find_bucket(selector.iter()),
|
// Component::Slotted(ref selector) => find_bucket(selector.iter()),
|
||||||
|
Component::Host(Some(ref selector)) => find_bucket(selector.iter()),
|
||||||
_ => Bucket::Universal
|
_ => Bucket::Universal
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2238,7 +2238,10 @@ impl CascadeData {
|
||||||
);
|
);
|
||||||
|
|
||||||
if rebuild_kind.should_rebuild_invalidation() {
|
if rebuild_kind.should_rebuild_invalidation() {
|
||||||
self.invalidation_map.note_selector(&rule.selector, quirks_mode)?;
|
self.invalidation_map.note_selector(
|
||||||
|
selector,
|
||||||
|
quirks_mode,
|
||||||
|
)?;
|
||||||
let mut visitor = StylistSelectorVisitor {
|
let mut visitor = StylistSelectorVisitor {
|
||||||
needs_revalidation: false,
|
needs_revalidation: false,
|
||||||
passed_rightmost_selector: false,
|
passed_rightmost_selector: false,
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue