--- a/servo/components/selectors/context.rs
+++ b/servo/components/selectors/context.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/. */
use attr::CaseSensitivity;
use bloom::BloomFilter;
use nth_index_cache::NthIndexCache;
use parser::SelectorImpl;
-use tree::OpaqueElement;
+use tree::{Element, OpaqueElement};
/// What kind of selector matching mode we should use.
///
/// There are two modes of selector matching. The difference is only noticeable
/// in presence of pseudo-elements.
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum MatchingMode {
/// Don't ignore any pseudo-element selectors.
@@ -113,16 +113,19 @@ where
/// scoping element is not relevant anymore, so we use a single field for
/// them.
///
/// When this is None, :scope will match the root element.
///
/// See https://drafts.csswg.org/selectors-4/#scope-pseudo
pub scope_element: Option<OpaqueElement>,
+ /// The current shadow host we're collecting :host rules for.
+ pub current_host: Option<OpaqueElement>,
+
/// Controls how matching for links is handled.
visited_handling: VisitedHandlingMode,
/// The current nesting level of selectors that we're matching.
///
/// FIXME(emilio): Consider putting the mutable stuff in a Cell, then make
/// MatchingContext immutable again.
nesting_level: usize,
@@ -173,24 +176,35 @@ where
Self {
matching_mode,
bloom_filter,
visited_handling,
nth_index_cache,
quirks_mode,
classes_and_ids_case_sensitivity: quirks_mode.classes_and_ids_case_sensitivity(),
scope_element: None,
+ current_host: None,
nesting_level: 0,
in_negation: false,
pseudo_element_matching_fn: None,
extra_data: Default::default(),
_impl: ::std::marker::PhantomData,
}
}
+ /// Override the quirks mode we're matching against.
+ ///
+ /// FIXME(emilio): This is a hack for XBL quirks-mode mismatches.
+ #[inline]
+ pub fn set_quirks_mode(&mut self, quirks_mode: QuirksMode) {
+ self.quirks_mode = quirks_mode;
+ self.classes_and_ids_case_sensitivity =
+ quirks_mode.classes_and_ids_case_sensitivity();
+ }
+
/// Whether we're matching a nested selector.
#[inline]
pub fn is_nested(&self) -> bool {
self.nesting_level != 0
}
/// Whether we're matching inside a :not(..) selector.
#[inline]
@@ -261,9 +275,35 @@ where
F: FnOnce(&mut Self) -> R,
{
let original_handling_mode = self.visited_handling;
self.visited_handling = handling_mode;
let result = f(self);
self.visited_handling = original_handling_mode;
result
}
+
+ /// Runs F with a given shadow host which is the root of the tree whose
+ /// rules we're matching.
+ #[inline]
+ pub fn with_shadow_host<F, E, R>(
+ &mut self,
+ host: Option<E>,
+ f: F,
+ ) -> R
+ where
+ E: Element,
+ F: FnOnce(&mut Self) -> R,
+ {
+ let original_host = self.current_host.take();
+ self.current_host = host.map(|h| h.opaque());
+ let result = f(self);
+ self.current_host = original_host;
+ result
+ }
+
+ /// Returns the current shadow host whose shadow root we're matching rules
+ /// against.
+ #[inline]
+ pub fn shadow_host(&self) -> Option<OpaqueElement> {
+ self.current_host.clone()
+ }
}
--- a/servo/components/selectors/matching.rs
+++ b/servo/components/selectors/matching.rs
@@ -461,25 +461,19 @@ where
//
// and also:
//
// When considered within its own shadow trees, the shadow host is
// featureless. Only the :host, :host(), and :host-context()
// pseudo-classes are allowed to match it.
//
// Since we know that the parent is a shadow root, we necessarily
- // are in a shadow tree of the host.
- let all_selectors_could_match = selector.clone().all(|component| {
- match *component {
- Component::NonTSPseudoClass(ref pc) => pc.is_host(),
- _ => false,
- }
- });
-
- if !all_selectors_could_match {
+ // are in a shadow tree of the host, and the next selector will only
+ // match if the selector is a featureless :host selector.
+ if !selector.clone().is_featureless_host_selector() {
return None;
}
element.containing_shadow_host()
}
Combinator::SlotAssignment => {
debug_assert!(element.assigned_slot().map_or(true, |s| s.is_html_slot_element()));
element.assigned_slot()
--- a/servo/components/selectors/parser.rs
+++ b/servo/components/selectors/parser.rs
@@ -508,16 +508,23 @@ impl<Impl: SelectorImpl> Selector<Impl>
#[inline]
pub fn iter(&self) -> SelectorIter<Impl> {
SelectorIter {
iter: self.iter_raw_match_order(),
next_combinator: None,
}
}
+ /// Whether this selector is a featureless :host selector, with no
+ /// combinators to the left.
+ #[inline]
+ pub fn is_featureless_host_selector(&self) -> bool {
+ self.iter().is_featureless_host_selector()
+ }
+
/// Returns an iterator over this selector in matching order (right-to-left),
/// skipping the rightmost |offset| Components.
#[inline]
pub fn iter_from(&self, offset: usize) -> SelectorIter<Impl> {
let iter = self.0.slice[offset..].iter();
SelectorIter {
iter: iter,
next_combinator: None,
@@ -600,16 +607,28 @@ pub struct SelectorIter<'a, Impl: 'a + S
impl<'a, Impl: 'a + SelectorImpl> SelectorIter<'a, Impl> {
/// Prepares this iterator to point to the next sequence to the left,
/// returning the combinator if the sequence was found.
#[inline]
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| {
+ match *component {
+ Component::NonTSPseudoClass(ref pc) => pc.is_host(),
+ _ => false,
+ }
+ }) && 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()
}
}
impl<'a, Impl: SelectorImpl> Iterator for SelectorIter<'a, Impl> {
--- a/servo/components/style/data.rs
+++ b/servo/components/style/data.rs
@@ -257,18 +257,18 @@ impl ElementData {
element.implemented_pseudo_element());
if !element.has_snapshot() || element.handled_snapshot() {
return InvalidationResult::empty();
}
let mut non_document_styles = SmallVec::<[_; 3]>::new();
let matches_doc_author_rules =
- element.each_applicable_non_document_style_rule_data(|data, quirks_mode| {
- non_document_styles.push((data, quirks_mode))
+ element.each_applicable_non_document_style_rule_data(|data, quirks_mode, host| {
+ non_document_styles.push((data, quirks_mode, host.map(|h| h.opaque())))
});
let mut processor = StateAndAttrInvalidationProcessor::new(
shared_context,
&non_document_styles,
matches_doc_author_rules,
element,
self,
--- a/servo/components/style/dom.rs
+++ b/servo/components/style/dom.rs
@@ -767,34 +767,47 @@ pub trait TElement
F: FnMut(&'a CascadeData, QuirksMode),
{
false
}
/// Executes the callback for each applicable style rule data which isn't
/// the main document's data (which stores UA / author rules).
///
+ /// The element passed to the callback is the containing shadow host for the
+ /// data if it comes from Shadow DOM, None if it comes from XBL.
+ ///
/// Returns whether normal document author rules should apply.
fn each_applicable_non_document_style_rule_data<'a, F>(&self, mut f: F) -> bool
where
Self: 'a,
- F: FnMut(&'a CascadeData, QuirksMode),
+ F: FnMut(&'a CascadeData, QuirksMode, Option<Self>),
{
- let mut doc_rules_apply = !self.each_xbl_cascade_data(&mut f);
+ let mut doc_rules_apply = !self.each_xbl_cascade_data(|data, quirks_mode| {
+ f(data, quirks_mode, None);
+ });
if let Some(shadow) = self.containing_shadow() {
doc_rules_apply = false;
- f(shadow.style_data(), self.as_node().owner_doc().quirks_mode());
+ 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 data = slot.containing_shadow().unwrap().style_data();
- f(data, self.as_node().owner_doc().quirks_mode());
+ let shadow = slot.containing_shadow().unwrap();
+ f(
+ shadow.style_data(),
+ self.as_node().owner_doc().quirks_mode(),
+ Some(shadow.host()),
+ );
current = slot.assigned_slot();
}
doc_rules_apply
}
/// Does a rough (and cheap) check for whether or not transitions might need to be updated that
/// will quickly return false for the common case of no transitions specified or running. If
--- a/servo/components/style/dom_apis.rs
+++ b/servo/components/style/dom_apis.rs
@@ -2,17 +2,17 @@
* 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/. */
//! Generic implementations of some DOM APIs so they can be shared between Servo
//! and Gecko.
use Atom;
use context::QuirksMode;
-use dom::{TDocument, TElement, TNode};
+use dom::{TDocument, TElement, TNode, TShadowRoot};
use invalidation::element::invalidator::{DescendantInvalidationLists, Invalidation};
use invalidation::element::invalidator::{InvalidationProcessor, InvalidationVector};
use selectors::{Element, NthIndexCache, SelectorList};
use selectors::attr::CaseSensitivity;
use selectors::matching::{self, MatchingContext, MatchingMode};
use selectors::parser::{Combinator, Component, LocalName};
use smallvec::SmallVec;
use std::borrow::Borrow;
@@ -28,16 +28,17 @@ where
{
let mut context = MatchingContext::new(
MatchingMode::Normal,
None,
None,
quirks_mode,
);
context.scope_element = Some(element.opaque());
+ context.current_host = element.containing_shadow_host().map(|e| e.opaque());
matching::matches_selector_list(selector_list, element, &mut context)
}
/// <https://dom.spec.whatwg.org/#dom-element-closest>
pub fn element_closest<E>(
element: E,
selector_list: &SelectorList<E::Impl>,
quirks_mode: QuirksMode,
@@ -49,16 +50,17 @@ where
let mut context = MatchingContext::new(
MatchingMode::Normal,
None,
Some(&mut nth_index_cache),
quirks_mode,
);
context.scope_element = Some(element.opaque());
+ context.current_host = element.containing_shadow_host().map(|e| e.opaque());
let mut current = Some(element);
while let Some(element) = current.take() {
if matching::matches_selector_list(selector_list, &element, &mut context) {
return Some(element);
}
current = element.parent_element();
}
@@ -542,16 +544,20 @@ where
MatchingMode::Normal,
None,
Some(&mut nth_index_cache),
quirks_mode,
);
let root_element = root.as_element();
matching_context.scope_element = root_element.map(|e| e.opaque());
+ matching_context.current_host = match root_element {
+ Some(root) => root.containing_shadow_host().map(|host| host.opaque()),
+ None => root.as_shadow_root().map(|root| root.host().opaque()),
+ };
let fast_result = query_selector_fast::<E, Q>(
root,
selector_list,
results,
&mut matching_context,
);
--- a/servo/components/style/gecko/selector_parser.rs
+++ b/servo/components/style/gecko/selector_parser.rs
@@ -51,16 +51,20 @@ macro_rules! pseudo_class_name {
)*
/// The `:dir` pseudo-class.
Dir(Box<Direction>),
/// The non-standard `:-moz-any` pseudo-class.
///
/// TODO(emilio): We disallow combinators and pseudos here, so we
/// should use SimpleSelector instead
MozAny(Box<[Selector<SelectorImpl>]>),
+ /// The `:host` pseudo-class:
+ ///
+ /// https://drafts.csswg.org/css-scoping/#host-selector
+ Host,
/// The non-standard `:-moz-locale-dir` pseudo-class.
MozLocaleDir(Box<Direction>),
}
}
}
apply_non_ts_list!(pseudo_class_name);
impl ToCss for NonTSPseudoClass {
@@ -89,16 +93,19 @@ impl ToCss for NonTSPseudoClass {
dir.to_css(&mut CssWriter::new(dest))?;
return dest.write_char(')')
},
NonTSPseudoClass::Dir(ref dir) => {
dest.write_str(":dir(")?;
dir.to_css(&mut CssWriter::new(dest))?;
return dest.write_char(')')
},
+ NonTSPseudoClass::Host => {
+ return dest.write_str(":host");
+ }
NonTSPseudoClass::MozAny(ref selectors) => {
dest.write_str(":-moz-any(")?;
let mut iter = selectors.iter();
let first = iter.next().expect(":-moz-any must have at least 1 selector");
first.to_css(dest)?;
for selector in iter {
dest.write_str(", ")?;
selector.to_css(dest)?;
@@ -112,17 +119,18 @@ impl ToCss for NonTSPseudoClass {
dest.write_str(ser)
}
}
impl Visit for NonTSPseudoClass {
type Impl = SelectorImpl;
fn visit<V>(&self, visitor: &mut V) -> bool
- where V: SelectorVisitor<Impl = Self::Impl>,
+ where
+ V: SelectorVisitor<Impl = Self::Impl>,
{
if let NonTSPseudoClass::MozAny(ref selectors) = *self {
for selector in selectors.iter() {
if !selector.visit(visitor) {
return false;
}
}
}
@@ -137,16 +145,17 @@ impl NonTSPseudoClass {
/// None otherwise. It doesn't check whether the pseudo-class is enabled
/// in a particular state.
pub fn parse_non_functional(name: &str) -> Option<Self> {
macro_rules! pseudo_class_parse {
(bare: [$(($css:expr, $name:ident, $gecko_type:tt, $state:tt, $flags:tt),)*],
string: [$(($s_css:expr, $s_name:ident, $s_gecko_type:tt, $s_state:tt, $s_flags:tt),)*]) => {
match_ignore_ascii_case! { &name,
$($css => Some(NonTSPseudoClass::$name),)*
+ "host" => Some(NonTSPseudoClass::Host),
_ => None,
}
}
}
apply_non_ts_list!(pseudo_class_parse)
}
/// Returns true if this pseudo-class has any of the given flags set.
@@ -158,16 +167,17 @@ impl NonTSPseudoClass {
macro_rules! pseudo_class_check_is_enabled_in {
(bare: [$(($css:expr, $name:ident, $gecko_type:tt, $state:tt, $flags:tt),)*],
string: [$(($s_css:expr, $s_name:ident, $s_gecko_type:tt, $s_state:tt, $s_flags:tt),)*]) => {
match *self {
$(NonTSPseudoClass::$name => check_flag!($flags),)*
$(NonTSPseudoClass::$s_name(..) => check_flag!($s_flags),)*
// TODO(emilio): Maybe -moz-locale-dir shouldn't be
// content-exposed.
+ NonTSPseudoClass::Host |
NonTSPseudoClass::MozLocaleDir(_) |
NonTSPseudoClass::Dir(_) |
NonTSPseudoClass::MozAny(_) => false,
}
}
}
apply_non_ts_list!(pseudo_class_check_is_enabled_in)
}
@@ -202,16 +212,17 @@ impl NonTSPseudoClass {
($state:ident) => (ElementState::$state);
}
macro_rules! pseudo_class_state {
(bare: [$(($css:expr, $name:ident, $gecko_type:tt, $state:tt, $flags:tt),)*],
string: [$(($s_css:expr, $s_name:ident, $s_gecko_type:tt, $s_state:tt, $s_flags:tt),)*]) => {
match *self {
$(NonTSPseudoClass::$name => flag!($state),)*
$(NonTSPseudoClass::$s_name(..) => flag!($s_state),)*
+ NonTSPseudoClass::Host |
NonTSPseudoClass::Dir(..) |
NonTSPseudoClass::MozLocaleDir(..) |
NonTSPseudoClass::MozAny(..) => ElementState::empty(),
}
}
}
apply_non_ts_list!(pseudo_class_state)
}
@@ -225,16 +236,18 @@ impl NonTSPseudoClass {
}
}
/// Returns true if the given pseudoclass should trigger style sharing cache
/// revalidation.
pub fn needs_cache_revalidation(&self) -> bool {
self.state_flag().is_empty() &&
!matches!(*self,
+ // We handle this with the style sharing checks.
+ NonTSPseudoClass::Host |
// :-moz-any is handled by the revalidation visitor walking
// the things inside it; it does not need to cause
// revalidation on its own.
NonTSPseudoClass::MozAny(_) |
// :dir() depends on state only, but doesn't use state_flag
// because its semantics don't quite match. Nevertheless, it
// doesn't need cache revalidation, because we already compare
// states for elements and candidates.
@@ -273,17 +286,17 @@ impl ::selectors::parser::NonTSPseudoCla
#[inline]
fn is_active_or_hover(&self) -> bool {
matches!(*self, NonTSPseudoClass::Active | NonTSPseudoClass::Hover)
}
#[inline]
fn is_host(&self) -> bool {
- false // TODO(emilio)
+ matches!(*self, NonTSPseudoClass::Host)
}
}
/// The dummy struct we use to implement our selector parsing.
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct SelectorImpl;
impl ::selectors::SelectorImpl for SelectorImpl {
--- a/servo/components/style/gecko/wrapper.rs
+++ b/servo/components/style/gecko/wrapper.rs
@@ -2181,16 +2181,21 @@ impl<'le> ::selectors::Element for Gecko
}
NonTSPseudoClass::Dir(ref dir) => {
match **dir {
Direction::Ltr => self.state().intersects(ElementState::IN_LTR_STATE),
Direction::Rtl => self.state().intersects(ElementState::IN_RTL_STATE),
Direction::Other(..) => false,
}
}
+ NonTSPseudoClass::Host => {
+ let matches = context.shadow_host().map_or(false, |host| host == self.opaque());
+ debug_assert!(!matches || self.shadow_root().is_some());
+ matches
+ }
}
}
fn match_pseudo_element(
&self,
pseudo_element: &PseudoElement,
_context: &mut MatchingContext<Self::Impl>,
) -> bool {
--- a/servo/components/style/invalidation/element/state_and_attributes.rs
+++ b/servo/components/style/invalidation/element/state_and_attributes.rs
@@ -16,16 +16,17 @@ use invalidation::element::invalidator::
use invalidation::element::invalidator::{Invalidation, InvalidationProcessor};
use invalidation::element::restyle_hints::RestyleHint;
use selector_map::SelectorMap;
use selector_parser::Snapshot;
use selectors::NthIndexCache;
use selectors::attr::CaseSensitivity;
use selectors::matching::{MatchingContext, MatchingMode, VisitedHandlingMode};
use selectors::matching::matches_selector;
+use selectors::OpaqueElement;
use smallvec::SmallVec;
use stylesheets::origin::{Origin, OriginSet};
use stylist::CascadeData;
#[derive(Debug, PartialEq)]
enum VisitedDependent {
Yes,
No,
@@ -33,46 +34,45 @@ enum VisitedDependent {
/// The collector implementation.
struct Collector<'a, 'b: 'a, 'selectors: 'a, E>
where
E: TElement,
{
element: E,
wrapper: ElementWrapper<'b, E>,
- nth_index_cache: Option<&'a mut NthIndexCache>,
snapshot: &'a Snapshot,
- quirks_mode: QuirksMode,
+ matching_context: &'a mut MatchingContext<'b, E::Impl>,
lookup_element: E,
removed_id: Option<&'a WeakAtom>,
added_id: Option<&'a WeakAtom>,
classes_removed: &'a SmallVec<[Atom; 8]>,
classes_added: &'a SmallVec<[Atom; 8]>,
state_changes: ElementState,
descendant_invalidations: &'a mut DescendantInvalidationLists<'selectors>,
sibling_invalidations: &'a mut InvalidationVector<'selectors>,
invalidates_self: bool,
}
/// An invalidation processor for style changes due to state and attribute
/// changes.
pub struct StateAndAttrInvalidationProcessor<'a, 'b: 'a, E: TElement> {
shared_context: &'a SharedStyleContext<'b>,
- shadow_rule_datas: &'a [(&'b CascadeData, QuirksMode)],
+ shadow_rule_datas: &'a [(&'b CascadeData, QuirksMode, Option<OpaqueElement>)],
matches_document_author_rules: bool,
element: E,
data: &'a mut ElementData,
matching_context: MatchingContext<'a, E::Impl>,
}
impl<'a, 'b: 'a, E: TElement> StateAndAttrInvalidationProcessor<'a, 'b, E> {
/// Creates a new StateAndAttrInvalidationProcessor.
pub fn new(
shared_context: &'a SharedStyleContext<'b>,
- shadow_rule_datas: &'a [(&'b CascadeData, QuirksMode)],
+ shadow_rule_datas: &'a [(&'b CascadeData, QuirksMode, Option<OpaqueElement>)],
matches_document_author_rules: bool,
element: E,
data: &'a mut ElementData,
nth_index_cache: &'a mut NthIndexCache,
) -> Self {
let matching_context = MatchingContext::new_for_visited(
MatchingMode::Normal,
None,
@@ -232,18 +232,17 @@ where
let invalidated_self = {
let mut collector = Collector {
wrapper,
lookup_element,
state_changes,
element,
snapshot: &snapshot,
- quirks_mode: self.shared_context.quirks_mode(),
- nth_index_cache: self.matching_context.nth_index_cache.as_mut().map(|c| &mut **c),
+ matching_context: &mut self.matching_context,
removed_id: id_removed,
added_id: id_added,
classes_removed: &classes_removed,
classes_added: &classes_added,
descendant_invalidations,
sibling_invalidations,
invalidates_self: false,
};
@@ -255,21 +254,22 @@ where
};
for (cascade_data, origin) in self.shared_context.stylist.iter_origins() {
if document_origins.contains(origin.into()) {
collector.collect_dependencies_in_invalidation_map(cascade_data.invalidation_map());
}
}
- for &(ref data, quirks_mode) in self.shadow_rule_datas {
+ for &(ref data, quirks_mode, ref host) in self.shadow_rule_datas {
// FIXME(emilio): Replace with assert / remove when we figure
// out what to do with the quirks mode mismatches
// (that is, when bug 1406875 is properly fixed).
- collector.quirks_mode = quirks_mode;
+ collector.matching_context.set_quirks_mode(quirks_mode);
+ collector.matching_context.current_host = host.clone();
collector.collect_dependencies_in_invalidation_map(data.invalidation_map());
}
collector.invalidates_self
};
// If we generated a ton of descendant invalidations, it's probably not
// worth to go ahead and try to process them.
@@ -328,17 +328,17 @@ impl<'a, 'b, 'selectors, E> Collector<'a
where
E: TElement,
'selectors: 'a,
{
fn collect_dependencies_in_invalidation_map(
&mut self,
map: &'selectors InvalidationMap,
) {
- let quirks_mode = self.quirks_mode;
+ let quirks_mode = self.matching_context.quirks_mode();
let removed_id = self.removed_id;
if let Some(ref id) = removed_id {
if let Some(deps) = map.id_to_selector.get(id, quirks_mode) {
for dep in deps {
self.scan_dependency(dep, VisitedDependent::No);
}
}
}
@@ -381,34 +381,34 @@ where
}
fn collect_dependencies_in_map(
&mut self,
map: &'selectors SelectorMap<Dependency>,
) {
map.lookup_with_additional(
self.lookup_element,
- self.quirks_mode,
+ self.matching_context.quirks_mode(),
self.removed_id,
self.classes_removed,
|dependency| {
self.scan_dependency(dependency, VisitedDependent::No);
true
},
);
}
fn collect_state_dependencies(
&mut self,
map: &'selectors SelectorMap<StateDependency>,
state_changes: ElementState,
) {
map.lookup_with_additional(
self.lookup_element,
- self.quirks_mode,
+ self.matching_context.quirks_mode(),
self.removed_id,
self.classes_removed,
|dependency| {
if !dependency.state.intersects(state_changes) {
return true;
}
let visited_dependent =
if dependency.state.intersects(ElementState::IN_VISITED_OR_UNVISITED_STATE) {
@@ -424,61 +424,39 @@ where
/// Check whether a dependency should be taken into account, using a given
/// visited handling mode.
fn check_dependency(
&mut self,
visited_handling_mode: VisitedHandlingMode,
dependency: &Dependency,
) -> bool {
- let matches_now = {
- let mut context = MatchingContext::new_for_visited(
- MatchingMode::Normal,
- None,
- self.nth_index_cache.as_mut().map(|c| &mut **c),
- visited_handling_mode,
- self.quirks_mode,
- );
-
+ let element = &self.element;
+ let wrapper = &self.wrapper;
+ self.matching_context.with_visited_handling_mode(visited_handling_mode, |mut context| {
let matches_now = matches_selector(
&dependency.selector,
dependency.selector_offset,
None,
- &self.element,
+ element,
&mut context,
&mut |_, _| {},
);
- matches_now
- };
-
- let matched_then = {
- let mut context = MatchingContext::new_for_visited(
- MatchingMode::Normal,
- None,
- self.nth_index_cache.as_mut().map(|c| &mut **c),
- visited_handling_mode,
- self.quirks_mode,
- );
-
let matched_then = matches_selector(
&dependency.selector,
dependency.selector_offset,
None,
- &self.wrapper,
+ wrapper,
&mut context,
&mut |_, _| {},
);
- matched_then
- };
-
- // Check for mismatches in both the match result and also the status
- // of whether a relevant link was found.
- matched_then != matches_now
+ matched_then != matches_now
+ })
}
fn scan_dependency(
&mut self,
dependency: &'selectors Dependency,
is_visited_dependent: VisitedDependent,
) {
debug!("TreeStyleInvalidator::scan_dependency({:?}, {:?}, {:?})",
--- a/servo/components/style/sharing/mod.rs
+++ b/servo/components/style/sharing/mod.rs
@@ -548,16 +548,26 @@ impl<E: TElement> StyleSharingCache<E> {
}
};
if element.is_native_anonymous() {
debug!("Failing to insert into the cache: NAC");
return;
}
+ // We can't share style across shadow hosts right now, because they may
+ // match different :host rules.
+ //
+ // TODO(emilio): We could share across the ones that don't have :host
+ // rules or have the same.
+ if element.shadow_root().is_some() {
+ debug!("Failing to insert into the cache: Shadow Host");
+ return;
+ }
+
// If the element has running animations, we can't share style.
//
// This is distinct from the specifies_{animations,transitions} check below,
// because:
// * Animations can be triggered directly via the Web Animations API.
// * Our computed style can still be affected by animations after we no
// longer match any animation rules, since removing animations involves
// a sequential task and an additional traversal.
@@ -711,16 +721,21 @@ impl<E: TElement> StyleSharingCache<E> {
// assigned to yet another slot in another shadow root.
if target.element.assigned_slot() != candidate.element.assigned_slot() {
// TODO(emilio): We could have a look at whether the shadow roots
// actually have slotted rules and such.
trace!("Miss: Different assigned slots");
return None;
}
+ if target.element.shadow_root().is_some() {
+ trace!("Miss: Shadow host");
+ return None;
+ }
+
if target.matches_user_and_author_rules() !=
candidate.element.matches_user_and_author_rules() {
trace!("Miss: User and Author Rules");
return None;
}
// It's possible that there are no styles for either id.
let may_match_different_id_rules =
--- a/servo/components/style/stylist.rs
+++ b/servo/components/style/stylist.rs
@@ -586,35 +586,34 @@ impl Stylist {
self.stylesheets.remove_stylesheet(Some(&self.device), sheet, guard)
}
/// Returns whether for any of the applicable style rule data a given
/// condition is true.
pub fn any_applicable_rule_data<E, F>(&self, element: E, mut f: F) -> bool
where
E: TElement,
- F: FnMut(&CascadeData, QuirksMode) -> bool,
+ F: FnMut(&CascadeData) -> bool,
{
- if f(&self.cascade_data.user_agent.cascade_data, self.quirks_mode()) {
+ if f(&self.cascade_data.user_agent.cascade_data) {
return true;
}
let mut maybe = false;
let doc_author_rules_apply =
- element.each_applicable_non_document_style_rule_data(|data, quirks_mode| {
- maybe = maybe || f(&*data, quirks_mode);
+ element.each_applicable_non_document_style_rule_data(|data, _, _| {
+ maybe = maybe || f(&*data);
});
if maybe || !doc_author_rules_apply {
return maybe;
}
- f(&self.cascade_data.author, self.quirks_mode()) ||
- f(&self.cascade_data.user, self.quirks_mode())
+ f(&self.cascade_data.author) || f(&self.cascade_data.user)
}
/// Computes the style for a given "precomputed" pseudo-element, taking the
/// universal rules and applying them.
///
/// If `inherit_all` is true, then all properties are inherited from the
/// parent; otherwise, non-inherited properties are reset to their initial
/// values. The flow constructor uses this flag when constructing anonymous
@@ -1256,52 +1255,70 @@ impl Stylist {
// XBL / Shadow DOM rules, which are author rules too.
//
// TODO(emilio): Cascade order here is wrong for Shadow DOM. In
// particular, normally document rules override ::slotted() rules, but
// for !important it should be the other way around. So probably we need
// to add some sort of AuthorScoped cascade level or something.
if matches_author_rules && !only_default_rules {
+ if let Some(shadow) = rule_hash_target.shadow_root() {
+ if let Some(map) = shadow.style_data().host_rules(pseudo_element) {
+ context.with_shadow_host(Some(rule_hash_target), |context| {
+ map.get_all_matching_rules(
+ element,
+ rule_hash_target,
+ applicable_declarations,
+ context,
+ flags_setter,
+ CascadeLevel::AuthorNormal,
+ );
+ });
+ }
+ }
+
// Match slotted rules in reverse order, so that the outer slotted
// rules come before the inner rules (and thus have less priority).
let mut slots = SmallVec::<[_; 3]>::new();
let mut current = rule_hash_target.assigned_slot();
while let Some(slot) = current {
slots.push(slot);
current = slot.assigned_slot();
}
for slot in slots.iter().rev() {
- let styles = slot.containing_shadow().unwrap().style_data();
+ let shadow = slot.containing_shadow().unwrap();
+ let styles = shadow.style_data();
if let Some(map) = styles.slotted_rules(pseudo_element) {
- map.get_all_matching_rules(
- element,
- rule_hash_target,
- applicable_declarations,
- context,
- flags_setter,
- CascadeLevel::AuthorNormal,
- );
+ context.with_shadow_host(Some(shadow.host()), |context| {
+ map.get_all_matching_rules(
+ element,
+ rule_hash_target,
+ applicable_declarations,
+ context,
+ flags_setter,
+ CascadeLevel::AuthorNormal,
+ );
+ });
}
}
- // TODO(emilio): We need to look up :host rules if the element is a
- // shadow host, when we implement that.
if let Some(containing_shadow) = rule_hash_target.containing_shadow() {
let cascade_data = containing_shadow.style_data();
if let Some(map) = cascade_data.normal_rules(pseudo_element) {
- map.get_all_matching_rules(
- element,
- rule_hash_target,
- applicable_declarations,
- context,
- flags_setter,
- CascadeLevel::AuthorNormal,
- );
+ context.with_shadow_host(Some(containing_shadow.host()), |context| {
+ map.get_all_matching_rules(
+ element,
+ rule_hash_target,
+ applicable_declarations,
+ context,
+ flags_setter,
+ CascadeLevel::AuthorNormal,
+ );
+ });
}
match_document_author_rules = false;
}
}
// FIXME(emilio): It looks very wrong to match XBL rules even for
// getDefaultComputedStyle!
@@ -1416,17 +1433,17 @@ impl Stylist {
// If id needs to be compared case-insensitively, the logic below
// wouldn't work. Just conservatively assume it may have such rules.
match self.quirks_mode().classes_and_ids_case_sensitivity() {
CaseSensitivity::AsciiCaseInsensitive => return true,
CaseSensitivity::CaseSensitive => {}
}
let hash = id.get_hash();
- self.any_applicable_rule_data(element, |data, _| {
+ self.any_applicable_rule_data(element, |data| {
data.mapped_ids.might_contain_hash(hash)
})
}
/// Returns the registered `@keyframes` animation for the specified name.
///
/// FIXME(emilio): This needs to account for the element rules.
#[inline]
@@ -1478,32 +1495,34 @@ impl Stylist {
&mut matching_context,
flags_setter
));
true
}
);
}
- element.each_applicable_non_document_style_rule_data(|data, quirks_mode| {
- data.selectors_for_cache_revalidation.lookup(
- element,
- quirks_mode,
- |selector_and_hashes| {
- results.push(matches_selector(
- &selector_and_hashes.selector,
- selector_and_hashes.selector_offset,
- Some(&selector_and_hashes.hashes),
- &element,
- &mut matching_context,
- flags_setter
- ));
- true
- }
- );
+ element.each_applicable_non_document_style_rule_data(|data, quirks_mode, host| {
+ matching_context.with_shadow_host(host, |matching_context| {
+ data.selectors_for_cache_revalidation.lookup(
+ element,
+ quirks_mode,
+ |selector_and_hashes| {
+ results.push(matches_selector(
+ &selector_and_hashes.selector,
+ selector_and_hashes.selector_offset,
+ Some(&selector_and_hashes.hashes),
+ &element,
+ matching_context,
+ flags_setter
+ ));
+ true
+ }
+ );
+ })
});
results
}
/// Computes styles for a given declaration with parent_style.
///
/// FIXME(emilio): the lack of pseudo / cascade flags look quite dubious,
@@ -1924,16 +1943,25 @@ impl ElementAndPseudoRules {
/// FIXME(emilio): Consider renaming and splitting in `CascadeData` and
/// `InvalidationData`? That'd make `clear_cascade_data()` clearer.
#[derive(Debug, MallocSizeOf)]
pub struct CascadeData {
/// The data coming from normal style rules that apply to elements at this
/// cascade level.
normal_rules: ElementAndPseudoRules,
+ /// The `:host` pseudo rules that are the rightmost selector.
+ ///
+ /// Note that as of right now these can't affect invalidation in any way,
+ /// until we support the :host(<selector>) notation.
+ ///
+ /// Also, note that other engines don't accept stuff like :host::before /
+ /// :host::after, so we don't need to store pseudo rules at all.
+ host_rules: Option<Box<SelectorMap<Rule>>>,
+
/// The data coming from ::slotted() pseudo-element rules.
///
/// We need to store them separately because an element needs to match
/// ::slotted() pseudo-element rules in different shadow roots.
///
/// In particular, we need to go through all the style data in all the
/// containing style scopes starting from the closest assigned slot.
slotted_rules: Option<Box<ElementAndPseudoRules>>,
@@ -2000,16 +2028,17 @@ pub struct CascadeData {
num_declarations: usize,
}
impl CascadeData {
/// Creates an empty `CascadeData`.
pub fn new() -> Self {
Self {
normal_rules: ElementAndPseudoRules::default(),
+ host_rules: None,
slotted_rules: None,
invalidation_map: InvalidationMap::new(),
attribute_dependencies: NonCountingBloomFilter::new(),
style_attribute_dependency: false,
state_dependencies: ElementState::empty(),
document_state_dependencies: DocumentState::empty(),
mapped_ids: NonCountingBloomFilter::new(),
selectors_for_cache_revalidation: SelectorMap::new(),
@@ -2087,16 +2116,25 @@ impl CascadeData {
self.attribute_dependencies.might_contain_hash(local_name.get_hash())
}
#[inline]
fn normal_rules(&self, pseudo: Option<&PseudoElement>) -> Option<&SelectorMap<Rule>> {
self.normal_rules.rules(pseudo)
}
#[inline]
+ fn host_rules(&self, pseudo: Option<&PseudoElement>) -> Option<&SelectorMap<Rule>> {
+ if pseudo.is_some() {
+ return None;
+ }
+
+ self.host_rules.as_ref().map(|rules| &**rules)
+ }
+
+ #[inline]
fn slotted_rules(&self, pseudo: Option<&PseudoElement>) -> Option<&SelectorMap<Rule>> {
self.slotted_rules.as_ref().and_then(|d| d.rules(pseudo))
}
/// Collects all the applicable media query results into `results`.
///
/// This duplicates part of the logic in `add_stylesheet`, which is
/// a bit unfortunate.
@@ -2219,29 +2257,33 @@ impl CascadeData {
rule.selector.clone(),
rule.hashes.clone(),
),
quirks_mode
)?;
}
}
- let rules = if selector.is_slotted() {
- self.slotted_rules.get_or_insert_with(|| {
- Box::new(Default::default())
- })
+ if selector.is_featureless_host_selector() {
+ let host_rules =
+ self.host_rules.get_or_insert_with(|| {
+ Box::new(Default::default())
+ });
+ host_rules.insert(rule, quirks_mode)?;
} else {
- &mut self.normal_rules
- };
+ let rules = if selector.is_slotted() {
+ self.slotted_rules.get_or_insert_with(|| {
+ Box::new(Default::default())
+ })
+ } else {
+ &mut self.normal_rules
+ };
- rules.insert(
- rule,
- pseudo_element,
- quirks_mode,
- )?;
+ rules.insert(rule, pseudo_element, quirks_mode)?;
+ }
}
self.rules_source_order += 1;
}
CssRule::Import(ref lock) => {
if rebuild_kind.should_rebuild_invalidation() {
let import_rule = lock.read_with(guard);
self.effective_media_query_results
.saw_effective(import_rule);
--- a/servo/ports/geckolib/glue.rs
+++ b/servo/ports/geckolib/glue.rs
@@ -4909,17 +4909,17 @@ pub extern "C" fn Servo_StyleSet_MightHa
element: RawGeckoElementBorrowed,
local_name: *mut nsAtom,
) -> bool {
let data = PerDocumentStyleData::from_ffi(raw_data).borrow();
let element = GeckoElement(element);
unsafe {
Atom::with(local_name, |atom| {
- data.stylist.any_applicable_rule_data(element, |data, _| {
+ data.stylist.any_applicable_rule_data(element, |data| {
data.might_have_attribute_dependency(atom)
})
})
}
}
#[no_mangle]
pub extern "C" fn Servo_StyleSet_HasStateDependency(
@@ -4927,17 +4927,17 @@ pub extern "C" fn Servo_StyleSet_HasStat
element: RawGeckoElementBorrowed,
state: u64,
) -> bool {
let element = GeckoElement(element);
let state = ElementState::from_bits_truncate(state);
let data = PerDocumentStyleData::from_ffi(raw_data).borrow();
- data.stylist.any_applicable_rule_data(element, |data, _| {
+ data.stylist.any_applicable_rule_data(element, |data| {
data.has_state_dependency(state)
})
}
#[no_mangle]
pub extern "C" fn Servo_StyleSet_HasDocumentStateDependency(
raw_data: RawServoStyleSetBorrowed,
state: u64,
--- a/testing/web-platform/meta/MANIFEST.json
+++ b/testing/web-platform/meta/MANIFEST.json
@@ -125506,16 +125506,76 @@
[
"/css/css-scoping/reference/green-box.html",
"=="
]
],
{}
]
],
+ "css/css-scoping/host-descendant-001.html": [
+ [
+ "/css/css-scoping/host-descendant-001.html",
+ [
+ [
+ "/css/css-scoping/reference/green-box.html",
+ "=="
+ ]
+ ],
+ {}
+ ]
+ ],
+ "css/css-scoping/host-descendant-002.html": [
+ [
+ "/css/css-scoping/host-descendant-002.html",
+ [
+ [
+ "/css/css-scoping/reference/green-box.html",
+ "=="
+ ]
+ ],
+ {}
+ ]
+ ],
+ "css/css-scoping/host-multiple-001.html": [
+ [
+ "/css/css-scoping/host-multiple-001.html",
+ [
+ [
+ "/css/css-scoping/reference/green-box.html",
+ "=="
+ ]
+ ],
+ {}
+ ]
+ ],
+ "css/css-scoping/host-nested-001.html": [
+ [
+ "/css/css-scoping/host-nested-001.html",
+ [
+ [
+ "/css/css-scoping/reference/green-box.html",
+ "=="
+ ]
+ ],
+ {}
+ ]
+ ],
+ "css/css-scoping/host-slotted-001.html": [
+ [
+ "/css/css-scoping/host-slotted-001.html",
+ [
+ [
+ "/css/css-scoping/reference/green-box.html",
+ "=="
+ ]
+ ],
+ {}
+ ]
+ ],
"css/css-scoping/shadow-assign-dynamic-001.html": [
[
"/css/css-scoping/shadow-assign-dynamic-001.html",
[
[
"/css/css-scoping/reference/green-box.html",
"=="
]
@@ -313690,16 +313750,28 @@
]
],
"css/css-ruby/line-break-around-ruby-001.html": [
[
"/css/css-ruby/line-break-around-ruby-001.html",
{}
]
],
+ "css/css-scoping/host-cssom-001.html": [
+ [
+ "/css/css-scoping/host-cssom-001.html",
+ {}
+ ]
+ ],
+ "css/css-scoping/host-descendant-invalidation.html": [
+ [
+ "/css/css-scoping/host-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": [
[
@@ -481454,17 +481526,17 @@
"f5f4f2279506dc6106de38080a639979ad7e05d0",
"testharness"
],
"css/css-align/content-distribution/place-content-shorthand-003.html": [
"966e8dd3893c487b02bb139ac26862995b655603",
"testharness"
],
"css/css-align/content-distribution/place-content-shorthand-004.html": [
- "e755317eedc580d6373df24596590d22fc9aad5d",
+ "52566e7e88edf9f57f441498b9d366b66be50fd7",
"testharness"
],
"css/css-align/content-distribution/place-content-shorthand-005.html": [
"6b3d7b9ae7d5b28510385cccaaade09268409cab",
"testharness"
],
"css/css-align/content-distribution/place-content-shorthand-006.html": [
"a40c054505716dda72b642ada40d8796a5d68795",
@@ -481546,17 +481618,17 @@
"638f5cbcc320477d495b9b0a752e6aaa048fee5e",
"testharness"
],
"css/css-align/default-alignment/place-items-shorthand-006.html": [
"b7106d0ee863673c0d9a6160d035706edb3c67a8",
"testharness"
],
"css/css-align/default-alignment/shorthand-serialization-001.html": [
- "0126d0a56ec9af324a886d286563cf8f39fc1b78",
+ "6c85da43be475b680d5351bdb969d09b90a6d97e",
"testharness"
],
"css/css-align/distribution-values/space-evenly-001.html": [
"6fd28a5bf615ce822ed935de90ce5c1a41d39104",
"reftest"
],
"css/css-align/gaps/column-gap-animation-001.html": [
"00023e39b6fafbfb31cabb30ed4ddf77a71b248f",
@@ -481678,17 +481750,17 @@
"a266e09207859b058f445acb7c472156e525dd21",
"testharness"
],
"css/css-align/self-alignment/place-self-shorthand-003.html": [
"2f17a907183eee3c3383894212f7d3d84770db2d",
"testharness"
],
"css/css-align/self-alignment/place-self-shorthand-004.html": [
- "4f153c62e5d1eecf9eb4678036123d0460d7ecf1",
+ "2259cbd38befc9cf4fcb3e7a567fe3197740f613",
"testharness"
],
"css/css-align/self-alignment/place-self-shorthand-005.html": [
"5a11a37355d05683ac1adbf1a5f6e1d0f5c88863",
"testharness"
],
"css/css-align/self-alignment/place-self-shorthand-006.html": [
"d355eac038a7307e7a969632bc745ad25d8d6f43",
@@ -486298,17 +486370,17 @@
"056b3597f3555c803c74a8f6277a06626efd12ea",
"reftest"
],
"css/css-content/attr-case-insensitive-ref.html": [
"30577fc39afb6ac028e25be11f363e060c0850b2",
"support"
],
"css/css-content/attr-case-insensitive.html": [
- "1118de214d8118e6b973f70b4496a4e5d51f3600",
+ "6b6cf2c15295940fb8831d17209635dc4e31cd78",
"reftest"
],
"css/css-content/element-replacement-ref.html": [
"f1ad3fca133b1b671e45ae1307fbe9454c40e3ec",
"support"
],
"css/css-content/element-replacement.html": [
"f491ddf2b3062ea2f9b616c968c88b9cc95f22eb",
@@ -506665,16 +506737,44 @@
"css/css-scoping/css-scoping-shadow-with-rules-no-style-leak.html": [
"1021b74fe5071a7df6dd9ef085c35e81b29d4fa2",
"reftest"
],
"css/css-scoping/css-scoping-shadow-with-rules.html": [
"69774c51e6df49c891ecaaf2c384552106a1eb32",
"reftest"
],
+ "css/css-scoping/host-cssom-001.html": [
+ "f77b672837e1c9728e53d74b533d79530fbd1249",
+ "testharness"
+ ],
+ "css/css-scoping/host-descendant-001.html": [
+ "703ba0d07ece44f4cc017b5351dea3057337f234",
+ "reftest"
+ ],
+ "css/css-scoping/host-descendant-002.html": [
+ "c96ebd0da1c7b2ce00e56bbef54bdd382789e2f2",
+ "reftest"
+ ],
+ "css/css-scoping/host-descendant-invalidation.html": [
+ "ec27e3cbe587470ecb945357c74954baf139d797",
+ "testharness"
+ ],
+ "css/css-scoping/host-multiple-001.html": [
+ "eb45ceb8c80d2cfbeb5bd317ab906f0881a13435",
+ "reftest"
+ ],
+ "css/css-scoping/host-nested-001.html": [
+ "a0b74d2e6bf24e9142904a925f95e969d206db20",
+ "reftest"
+ ],
+ "css/css-scoping/host-slotted-001.html": [
+ "918bd04b95a276c6035383f2fe4dcfe4274bceeb",
+ "reftest"
+ ],
"css/css-scoping/reference/green-box.html": [
"a736f68dc602c0fccab56ec5cc6234cb3298c88d",
"support"
],
"css/css-scoping/shadow-assign-dynamic-001.html": [
"c57e0fd5aa5be63e1cadf65a4e382798c5e05ec4",
"reftest"
],
deleted file mode 100644
--- a/testing/web-platform/meta/css/css-scoping/css-scoping-shadow-host-rule.html.ini
+++ /dev/null
@@ -1,2 +0,0 @@
-[css-scoping-shadow-host-rule.html]
- expected: FAIL
--- a/testing/web-platform/meta/css/css-scoping/shadow-cascade-order-001.html.ini
+++ b/testing/web-platform/meta/css/css-scoping/shadow-cascade-order-001.html.ini
@@ -1,14 +1,9 @@
[shadow-cascade-order-001.html]
- [C2. document vs :host with !important, :host rule should win for open mode.]
- expected: FAIL
-
- [C6. :host with !important vs inline, :host rule should win for open mode.]
- expected: FAIL
[D1. document vs ::slotted both with !important, ::slotted rule should win for open mode.]
expected: FAIL
[D2. document vs :host both with !important, :host rule should win for open mode.]
expected: FAIL
[D4. ::slotted vs :host both with !important, later in tree-of-trees rule should win for open mode.]
@@ -18,28 +13,19 @@
expected: FAIL
[D6. :host vs inline both with !important, :host rule should win for open mode.]
expected: FAIL
[E2. all styles with !important applied, rule in the last tree-of-trees should win for open mode.]
expected: FAIL
- [F5. document vs :host with !important, important rule should win for open mode.]
- expected: FAIL
-
[F6. all rules with !important, the last rule in tree-of-trees should win for open mode.]
expected: FAIL
- [C2. document vs :host with !important, :host rule should win for closed mode.]
- expected: FAIL
-
- [C6. :host with !important vs inline, :host rule should win for closed mode.]
- expected: FAIL
-
[D1. document vs ::slotted both with !important, ::slotted rule should win for closed mode.]
expected: FAIL
[D2. document vs :host both with !important, :host rule should win for closed mode.]
expected: FAIL
[D4. ::slotted vs :host both with !important, later in tree-of-trees rule should win for closed mode.]
expected: FAIL
@@ -48,14 +34,11 @@
expected: FAIL
[D6. :host vs inline both with !important, :host rule should win for closed mode.]
expected: FAIL
[E2. all styles with !important applied, rule in the last tree-of-trees should win for closed mode.]
expected: FAIL
- [F5. document vs :host with !important, important rule should win for closed mode.]
- expected: FAIL
-
[F6. all rules with !important, the last rule in tree-of-trees should win for closed mode.]
expected: FAIL
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/css/css-scoping/host-cssom-001.html
@@ -0,0 +1,16 @@
+<!doctype html>
+<title>CSS Test: :host in CSSOM</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>
+<script>
+test(function() {
+ let root = host.attachShadow({ mode: "open" });
+ root.innerHTML = `<div></div>`;
+ assert_false(host.matches(":host"), ":host shouldn't match from CSSOM from outside the shadow tree");
+ assert_true(root.firstElementChild.matches(":host div"), ":host should match from within the shadow tree");
+ assert_equals(root.querySelector(":host div"), root.firstElementChild, ":host should match from within the shadow tree");
+})
+</script>
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/css/css-scoping/host-descendant-001.html
@@ -0,0 +1,16 @@
+<!doctype html>
+<link rel="author" title="Emilio Cobos Álvarez" href="mailto:emilio@crisal.io">
+<link rel="help" href="https://drafts.csswg.org/css-scoping/#host-selector">
+<link rel="match" href="reference/green-box.html">
+<p>Test passes if you see a single 100px by 100px green box below.</p>
+<div id="host"><div></div></div>
+<script>
+ let root = host.attachShadow({ mode: "open" });
+ root.innerHTML = `
+ <style>
+ :host ::slotted(div) { width: 100px; height: 100px; background: green; }
+ * :host ::slotted(div) { background: red; }
+ </style>
+ <slot></slot>
+ `;
+</script>
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/css/css-scoping/host-descendant-002.html
@@ -0,0 +1,15 @@
+<!doctype html>
+<link rel="author" title="Emilio Cobos Álvarez" href="mailto:emilio@crisal.io">
+<link rel="help" href="https://drafts.csswg.org/css-scoping/#host-selector">
+<link rel="match" href="reference/green-box.html">
+<p>Test passes if you see a single 100px by 100px green box below.</p>
+<div id="host"></div>
+<script>
+ let root = host.attachShadow({ mode: "open" });
+ root.innerHTML = `
+ <style>
+ :host { width: 100px; height: 100px; background: green; }
+ * :host { background: red; }
+ </style>
+ `;
+</script>
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/css/css-scoping/host-descendant-invalidation.html
@@ -0,0 +1,22 @@
+<!doctype html>
+<title>CSS Test: :host is accounted for during invalidation</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 ::slotted(.foo) { background: green; }
+ </style>
+ <slot></slot>
+ `;
+ assert_equals(getComputedStyle(slotted).backgroundColor, "rgb(255, 0, 0)");
+ host.firstElementChild.classList.add('foo');
+ assert_equals(getComputedStyle(slotted).backgroundColor, "rgb(0, 128, 0)");
+});
+</script>
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/css/css-scoping/host-multiple-001.html
@@ -0,0 +1,15 @@
+<!doctype html>
+<title>CSS Test: :host multiple times in the same compound selector.</title>
+<link rel="author" title="Emilio Cobos Álvarez" href="mailto:emilio@crisal.io">
+<link rel="help" href="https://drafts.csswg.org/css-scoping/#host-selector">
+<link rel="match" href="reference/green-box.html">
+<p>Test passes if you see a single 100px by 100px green box below.</p>
+<div id="host"></div>
+<script>
+ let root = host.attachShadow({ mode: "open" });
+ root.innerHTML = `
+ <style>
+ :host:host { width: 100px; height: 100px; background: green }
+ </style>
+ `;
+</script>
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/css/css-scoping/host-nested-001.html
@@ -0,0 +1,29 @@
+<!doctype html>
+<title>CSS Test: :host doesn't match nested shadow hosts.</title>
+<link rel="author" title="Emilio Cobos Álvarez" href="mailto:emilio@crisal.io">
+<link rel="help" href="https://drafts.csswg.org/css-scoping/#host-selector">
+<link rel="match" href="reference/green-box.html">
+<style>
+ #host {
+ width: 100px;
+ height: 100px;
+ }
+</style>
+<p>Test passes if you see a single 100px by 100px green box below.</p>
+<div id="host">FAIL</div>
+<script>
+ let root = host.attachShadow({ mode: "open" });
+ root.innerHTML = `
+ <style>
+ div {
+ background: green;
+ width: 100px;
+ height: 100px;
+ }
+ :host { background: red !important }
+ div:host { background: red !important }
+ </style>
+ <div id="nested-host">FAIL - nested shadow host</div>
+ `;
+ root.getElementById("nested-host").attachShadow({ mode: "open" });
+</script>
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/css/css-scoping/host-slotted-001.html
@@ -0,0 +1,17 @@
+<!doctype html>
+<title>CSS Test: :host matches while collecting ::slotted rules</title>
+<link rel="author" title="Emilio Cobos Álvarez" href="mailto:emilio@crisal.io">
+<link rel="help" href="https://drafts.csswg.org/css-scoping/#host-selector">
+<link rel="match" href="reference/green-box.html">
+<p>Test passes if you see a single 100px by 100px green box below.</p>
+<div id="host"><div></div></div>
+<script>
+ let root = host.attachShadow({ mode: "open" });
+ root.innerHTML = `
+ <style>
+ ::slotted(div) { width: 100px; height: 100px; background: red; }
+ :host ::slotted(div) { background: green; }
+ </style>
+ <slot></slot>
+ `;
+</script>