--- a/servo/components/selectors/builder.rs
+++ b/servo/components/selectors/builder.rs
@@ -309,17 +309,17 @@ fn complex_selector_specificity<Impl>(mu
Component::Class(..) |
Component::AttributeInNoNamespace { .. } |
Component::AttributeInNoNamespaceExists { .. } |
Component::AttributeOther(..) |
Component::FirstChild | Component::LastChild |
Component::OnlyChild | Component::Root |
Component::Empty | Component::Scope |
- Component::Host |
+ Component::Host(..) |
Component::NthChild(..) |
Component::NthLastChild(..) |
Component::NthOfType(..) |
Component::NthLastOfType(..) |
Component::FirstOfType | Component::LastOfType |
Component::OnlyOfType |
Component::NonTSPseudoClass(..) => {
specificity.class_like_selectors += 1
--- a/servo/components/selectors/matching.rs
+++ b/servo/components/selectors/matching.rs
@@ -687,20 +687,20 @@ where
E: Element,
F: FnMut(&E, ElementSelectorFlags),
{
debug_assert!(context.shared.is_nested() || !context.shared.in_negation());
match *selector {
Component::Combinator(_) => unreachable!(),
Component::Slotted(ref selector) => {
+ // <slots> are never flattened tree slottables.
+ !element.is_html_slot_element() &&
+ element.assigned_slot().is_some() &&
context.shared.nest(|context| {
- // <slots> are never flattened tree slottables.
- !element.is_html_slot_element() &&
- element.assigned_slot().is_some() &&
matches_complex_selector(
selector.iter(),
element,
context,
flags_setter,
)
})
}
@@ -809,18 +809,28 @@ where
}
Component::Root => {
element.is_root()
}
Component::Empty => {
flags_setter(element, ElementSelectorFlags::HAS_EMPTY_SELECTOR);
element.is_empty()
}
- Component::Host => {
- context.shared.shadow_host().map_or(false, |host| host == element.opaque())
+ Component::Host(ref selector) => {
+ 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 => {
match context.shared.scope_element {
Some(ref scope_element) => element.opaque() == *scope_element,
None => element.is_root(),
}
}
Component::NthChild(a, b) => {
--- a/servo/components/selectors/parser.rs
+++ b/servo/components/selectors/parser.rs
@@ -376,21 +376,24 @@ where
V: SelectorVisitor<Impl = Impl>,
{
use self::Component::*;
if !visitor.visit_simple_selector(self) {
return false;
}
match *self {
- Slotted(ref selectors) => {
- for selector in selectors.iter() {
- if !selector.visit(visitor) {
- return false;
- }
+ Slotted(ref selector) => {
+ if !selector.visit(visitor) {
+ return false;
+ }
+ }
+ Host(Some(ref selector)) => {
+ if !selector.visit(visitor) {
+ return false;
}
}
Negation(ref negated) => {
for component in negated.iter() {
if !component.visit(visitor) {
return false;
}
}
@@ -613,17 +616,17 @@ impl<'a, Impl: 'a + SelectorImpl> Select
pub fn next_sequence(&mut self) -> Option<Combinator> {
self.next_combinator.take()
}
/// Whether this selector is a featureless host selector, with no
/// combinators to the left.
#[inline]
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()
}
/// Returns remaining count of the simple selectors and combinators in the Selector.
#[inline]
pub fn selector_length(&self) -> usize {
self.iter.len()
}
@@ -788,20 +791,16 @@ pub enum Component<Impl: SelectorImpl> {
/// need to think about how this should interact with
/// visit_complex_selector, and what the consumers of those APIs should do
/// about the presence of combinators in negation.
Negation(Box<[Component<Impl>]>),
FirstChild, LastChild, OnlyChild,
Root,
Empty,
Scope,
- /// The `:host` pseudo-class:
- ///
- /// https://drafts.csswg.org/css-scoping/#host-selector
- Host,
NthChild(i32, i32),
NthLastChild(i32, i32),
NthOfType(i32, i32),
NthLastOfType(i32, i32),
FirstOfType,
LastOfType,
OnlyOfType,
NonTSPseudoClass(Impl::NonTSPseudoClass),
@@ -810,17 +809,29 @@ pub enum Component<Impl: SelectorImpl> {
///
/// https://drafts.csswg.org/css-scoping/#slotted-pseudo
///
/// The selector here is a compound selector, that is, no combinators.
///
/// 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()
/// in the rule hash, so we do that too.
+ ///
+ /// See https://github.com/w3c/csswg-drafts/issues/2158
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 ::slotted()
+ /// 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),
}
impl<Impl: SelectorImpl> Component<Impl> {
/// Compute the ancestor hash to check against the bloom filter.
fn ancestor_hash(&self, quirks_mode: QuirksMode) -> Option<u32>
where Impl::Identifier: PrecomputedHash,
Impl::ClassName: PrecomputedHash,
@@ -1114,17 +1125,25 @@ impl<Impl: SelectorImpl> ToCss for Compo
}
FirstChild => dest.write_str(":first-child"),
LastChild => dest.write_str(":last-child"),
OnlyChild => dest.write_str(":only-child"),
Root => dest.write_str(":root"),
Empty => dest.write_str(":empty"),
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"),
LastOfType => dest.write_str(":last-of-type"),
OnlyOfType => dest.write_str(":only-of-type"),
NthChild(a, b) | NthLastChild(a, b) | NthOfType(a, b) | NthLastOfType(a, b) => {
match *self {
NthChild(_, _) => dest.write_str(":nth-child(")?,
NthLastChild(_, _) => dest.write_str(":nth-last-child(")?,
NthOfType(_, _) => dest.write_str(":nth-of-type(")?,
@@ -1811,16 +1830,17 @@ where
P: Parser<'i, Impl=Impl>,
Impl: SelectorImpl,
{
match_ignore_ascii_case! { &name,
"nth-child" => return Ok(parse_nth_pseudo_class(input, Component::NthChild)?),
"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-of-type" => return Ok(parse_nth_pseudo_class(input, Component::NthLastOfType)?),
+ "host" => return Ok(Component::Host(Some(parse_inner_compound_selector(parser, input)?))),
"not" => {
if inside_negation {
return Err(input.new_custom_error(
SelectorParseErrorKind::UnexpectedIdent("not".into())
));
}
return parse_negation(parser, input)
},
@@ -1964,17 +1984,17 @@ where
{
(match_ignore_ascii_case! { &name,
"first-child" => Ok(Component::FirstChild),
"last-child" => Ok(Component::LastChild),
"only-child" => Ok(Component::OnlyChild),
"root" => Ok(Component::Root),
"empty" => Ok(Component::Empty),
"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),
"last-of-type" => Ok(Component::LastOfType),
"only-of-type" => Ok(Component::OnlyOfType),
_ => Err(())
}).or_else(|()| {
P::parse_non_ts_pseudo_class(parser, location, name)
.map(Component::NonTSPseudoClass)
})
--- a/servo/components/style/dom.rs
+++ b/servo/components/style/dom.rs
@@ -789,16 +789,24 @@ pub trait TElement
doc_rules_apply = false;
f(
shadow.style_data(),
self.as_node().owner_doc().quirks_mode(),
Some(shadow.host()),
);
}
+ 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();
while let Some(slot) = current {
// Slots can only have assigned nodes when in a shadow tree.
let shadow = slot.containing_shadow().unwrap();
f(
shadow.style_data(),
self.as_node().owner_doc().quirks_mode(),
Some(shadow.host()),
--- a/servo/components/style/invalidation/element/invalidation_map.rs
+++ b/servo/components/style/invalidation/element/invalidation_map.rs
@@ -202,44 +202,35 @@ impl InvalidationMap {
self.id_to_selector.iter().fold(0, |accum, (_, ref v)| {
accum + v.len()
}) +
self.class_to_selector.iter().fold(0, |accum, (_, ref v)| {
accum + v.len()
})
}
- /// 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.
pub fn clear(&mut self) {
self.class_to_selector.clear();
self.id_to_selector.clear();
self.state_affecting_selectors.clear();
self.document_state_selectors.clear();
self.other_attribute_affecting_selectors.clear();
self.has_id_attribute_selectors = false;
self.has_class_attribute_selectors = false;
}
- // Returns Err(..) to signify OOM.
- fn collect_invalidations_for(
+ /// Adds a selector to this `InvalidationMap`. Returns Err(..) to
+ /// signify OOM.
+ pub fn note_selector(
&mut self,
selector: &Selector<SelectorImpl>,
- quirks_mode: QuirksMode
+ quirks_mode: QuirksMode,
) -> Result<(), FailedAllocationError> {
- debug!("InvalidationMap::collect_invalidations_for({:?})", selector);
+ debug!("InvalidationMap::note_selector({:?})", selector);
let mut iter = selector.iter();
let mut combinator;
let mut index = 0;
let mut document_state = DocumentState::empty();
loop {
--- a/servo/components/style/invalidation/element/invalidator.rs
+++ b/servo/components/style/invalidation/element/invalidator.rs
@@ -1,17 +1,17 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
//! The struct that takes care of encapsulating all the logic on where and how
//! element styles need to be invalidated.
use context::StackLimitChecker;
-use dom::{TElement, TNode};
+use dom::{TElement, TNode, TShadowRoot};
use selector_parser::SelectorImpl;
use selectors::matching::{CompoundSelectorMatchingResult, MatchingContext};
use selectors::matching::matches_compound_selector_from;
use selectors::parser::{Combinator, Component, Selector};
use smallvec::SmallVec;
use std::fmt;
/// A trait to abstract the collection of invalidations for a given pass.
@@ -529,30 +529,32 @@ where
if self.processor.light_tree_only() {
let node = self.element.as_node();
return self.invalidate_dom_descendants_of(node, invalidations);
}
let mut any_descendant = false;
- // NOTE(emilio): This should not be needed for Shadow DOM for normal
- // element state / attribute invalidations (it's needed for XBL though,
- // due to the weird way the anon content there works (it doesn't block
- // combinators)).
+ // NOTE(emilio): This is only needed for Shadow DOM to invalidate
+ // correctly on :host(..) changes. We could not do this instead and add
+ // a third kind of invalidation list instead, that walks shadow root
+ // children, but it's not clear it's worth it.
//
- // However, it's needed as of right now for document state invalidation,
- // were we rely on iterating every element that ends up in the composed
- // doc.
- //
- // Also, we could avoid having that special-case for document state
- // invalidations if we invalidate for document state changes per
- // 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).
+ // Also, it's needed as of right now for document state invalidation,
+ // where we rely on iterating every element that ends up in the composed
+ // doc, but we could fix that invalidating per subtree.
+ if let Some(root) = self.element.shadow_root() {
+ any_descendant |=
+ self.invalidate_dom_descendants_of(root.as_node(), invalidations);
+ }
+
+ // 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() {
any_descendant |=
self.invalidate_dom_descendants_of(anon_content, invalidations);
}
if let Some(before) = self.element.before_pseudo_element() {
any_descendant |=
self.invalidate_pseudo_element_or_nac(before, invalidations);
--- a/servo/components/style/selector_map.rs
+++ b/servo/components/style/selector_map.rs
@@ -452,16 +452,17 @@ fn specific_bucket_for<'a>(
// match the slotted <span>.
//
// FIXME(emilio, bug 1426516): The line below causes valgrind failures
// and it's probably a false positive, we should uncomment whenever
// jseward is back to confirm / whitelist it.
//
// Meanwhile taking the code path below is slower, but still correct.
// Component::Slotted(ref selector) => find_bucket(selector.iter()),
+ Component::Host(Some(ref selector)) => find_bucket(selector.iter()),
_ => Bucket::Universal
}
}
/// Searches a compound selector from left to right, and returns the appropriate
/// bucket for it.
#[inline(always)]
fn find_bucket<'a>(mut iter: SelectorIter<'a, SelectorImpl>) -> Bucket<'a> {
--- a/servo/components/style/stylist.rs
+++ b/servo/components/style/stylist.rs
@@ -2233,17 +2233,20 @@ impl CascadeData {
let rule = Rule::new(
selector.clone(),
hashes,
locked.clone(),
self.rules_source_order
);
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 {
needs_revalidation: false,
passed_rightmost_selector: false,
attribute_dependencies: &mut self.attribute_dependencies,
style_attribute_dependency: &mut self.style_attribute_dependency,
state_dependencies: &mut self.state_dependencies,
document_state_dependencies: &mut self.document_state_dependencies,
mapped_ids: &mut self.mapped_ids,
--- a/testing/web-platform/meta/MANIFEST.json
+++ b/testing/web-platform/meta/MANIFEST.json
@@ -313762,16 +313762,22 @@
]
],
"css/css-scoping/host-dom-001.html": [
[
"/css/css-scoping/host-dom-001.html",
{}
]
],
+ "css/css-scoping/host-functional-descendant-invalidation.html": [
+ [
+ "/css/css-scoping/host-functional-descendant-invalidation.html",
+ {}
+ ]
+ ],
"css/css-scoping/shadow-cascade-order-001.html": [
[
"/css/css-scoping/shadow-cascade-order-001.html",
{}
]
],
"css/css-scoping/slotted-invalidation.html": [
[
@@ -506746,21 +506752,25 @@
"703ba0d07ece44f4cc017b5351dea3057337f234",
"reftest"
],
"css/css-scoping/host-descendant-002.html": [
"c96ebd0da1c7b2ce00e56bbef54bdd382789e2f2",
"reftest"
],
"css/css-scoping/host-descendant-invalidation.html": [
- "ec27e3cbe587470ecb945357c74954baf139d797",
+ "33ad555cd243bb5cd486b6ffb776f657d1185228",
"testharness"
],
"css/css-scoping/host-dom-001.html": [
- "f77b672837e1c9728e53d74b533d79530fbd1249",
+ "33f34152fff538f6080b6fa36de337dca8a2b694",
+ "testharness"
+ ],
+ "css/css-scoping/host-functional-descendant-invalidation.html": [
+ "891d852131c37fd2148d46f5b6dfe32e1fc45bf3",
"testharness"
],
"css/css-scoping/host-multiple-001.html": [
"eb45ceb8c80d2cfbeb5bd317ab906f0881a13435",
"reftest"
],
"css/css-scoping/host-nested-001.html": [
"a0b74d2e6bf24e9142904a925f95e969d206db20",
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/css/css-scoping/host-functional-descendant-invalidation.html
@@ -0,0 +1,22 @@
+<!doctype html>
+<title>CSS Test: element style is correctly updated for rule with :host(..)</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<link rel="author" title="Emilio Cobos Álvarez" href="mailto:emilio@crisal.io">
+<link rel="help" href="https://drafts.csswg.org/css-scoping/#host-selector">
+<div id="host"><div id="slotted"></div></div>
+<script>
+test(function() {
+ let root = host.attachShadow({ mode: "open" });
+ root.innerHTML = `
+ <style>
+ :host ::slotted(div) { width: 100px; height: 100px; background: red; }
+ :host(.foo) ::slotted(div) { background: green; }
+ </style>
+ <slot></slot>
+ `;
+ assert_equals(getComputedStyle(slotted).backgroundColor, "rgb(255, 0, 0)");
+ host.classList.add('foo');
+ assert_equals(getComputedStyle(slotted).backgroundColor, "rgb(0, 128, 0)");
+});
+</script>