Bug 1465291: Make pseudo-elements work with :host. r?xidorn
Imported WebKit's test as a WPT.
MozReview-Commit-ID: 19ZThuoqKLW
--- a/servo/components/selectors/parser.rs
+++ b/servo/components/selectors/parser.rs
@@ -539,20 +539,36 @@ impl<Impl: SelectorImpl> Selector<Impl>
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.
+ /// combinators to the left, and optionally has a pseudo-element to the
+ /// right.
#[inline]
- pub fn is_featureless_host_selector(&self) -> bool {
- self.iter().is_featureless_host_selector()
+ pub fn is_featureless_host_selector_or_pseudo_element(&self) -> bool {
+ let mut iter = self.iter();
+ if !self.has_pseudo_element() {
+ return iter.is_featureless_host_selector();
+ }
+
+ // Skip the pseudo-element.
+ for _ in &mut iter { }
+
+ match iter.next_sequence() {
+ None => return false,
+ Some(combinator) => {
+ debug_assert_eq!(combinator, Combinator::PseudoElement);
+ }
+ }
+
+ 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 {
--- a/servo/components/style/stylist.rs
+++ b/servo/components/style/stylist.rs
@@ -1953,17 +1953,17 @@ pub struct CascadeData {
/// 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>>>,
+ host_rules: Option<Box<ElementAndPseudoRules>>,
/// 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.
@@ -2117,21 +2117,17 @@ impl CascadeData {
}
#[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)
+ self.host_rules.as_ref().and_then(|d| d.rules(pseudo))
}
#[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`.
@@ -2253,30 +2249,30 @@ impl CascadeData {
rule.selector.clone(),
rule.hashes.clone(),
),
quirks_mode,
)?;
}
}
- 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)?;
+ // NOTE(emilio): It's fine to look at :host and then at
+ // ::slotted(..), since :host::slotted(..) could never
+ // possibly match, as <slot> is not a valid shadow host.
+ let rules = if selector.is_featureless_host_selector_or_pseudo_element() {
+ self.host_rules
+ .get_or_insert_with(|| Box::new(Default::default()))
+ } else if selector.is_slotted() {
+ self.slotted_rules
+ .get_or_insert_with(|| Box::new(Default::default()))
} else {
- let rules = if selector.is_slotted() {
- self.slotted_rules
- .get_or_insert_with(|| Box::new(Default::default()))
- } else {
- &mut self.normal_rules
- };
+ &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/testing/web-platform/meta/MANIFEST.json
+++ b/testing/web-platform/meta/MANIFEST.json
@@ -128984,16 +128984,28 @@
[
"/css/css-scoping/reference/green-box.html",
"=="
]
],
{}
]
],
+ "css/css-scoping/shadow-host-with-before-after.html": [
+ [
+ "/css/css-scoping/shadow-host-with-before-after.html",
+ [
+ [
+ "/css/css-scoping/reference/green-box.html",
+ "=="
+ ]
+ ],
+ {}
+ ]
+ ],
"css/css-scoping/shadow-reassign-dynamic-001.html": [
[
"/css/css-scoping/shadow-reassign-dynamic-001.html",
[
[
"/css/css-scoping/reference/green-box.html",
"=="
]
@@ -519948,17 +519960,17 @@
"2680b68f5c1720ce6d2b82d942ea21b0b1518587",
"support"
],
"css/css-scoping/shadow-assign-dynamic-001.html": [
"c57e0fd5aa5be63e1cadf65a4e382798c5e05ec4",
"reftest"
],
"css/css-scoping/shadow-at-import.html": [
- "40f2606177ad3143774d97060ac1bbfa9743801f",
+ "67295000ad3c24c2d9ab0ac556d34758f3ce654c",
"reftest"
],
"css/css-scoping/shadow-cascade-order-001.html": [
"46913ea7e47811b11be898de5c3bd0a330ea6637",
"testharness"
],
"css/css-scoping/shadow-disabled-sheet-001.html": [
"3de2d23c1b3339b964ec2c009832a3207a3b9dc4",
@@ -519979,16 +519991,20 @@
"css/css-scoping/shadow-fallback-dynamic-004.html": [
"71dcc60c4ff59690927c1575fff2eecf85ee558f",
"reftest"
],
"css/css-scoping/shadow-fallback-dynamic-005.html": [
"ab3c3d205e59df800ba5b4217245b83685521c31",
"reftest"
],
+ "css/css-scoping/shadow-host-with-before-after.html": [
+ "b33e82815776f05b6292a88bbb95daa882a50ac6",
+ "reftest"
+ ],
"css/css-scoping/shadow-reassign-dynamic-001.html": [
"11ed4da2e6ce88d8a2b98a8f1c814417ef7770dd",
"reftest"
],
"css/css-scoping/shadow-root-insert-into-document.html": [
"2cee9fff35c9222074f4ef78dcfcb8a3e02bbc98",
"reftest"
],
@@ -546044,17 +546060,17 @@
"f131f271cb2f747e845584abcc445348e8c86521",
"support"
],
"css/cssom/StyleSheetList.html": [
"0a1cd8ed56ac3a5b1a9556835d94fb80325199bf",
"testharness"
],
"css/cssom/at-namespace.html": [
- "96da2dd244e9e19ff8ca1ca81b06c3ebdcee8267",
+ "cd3845557f5c40f51f7e3cbdfff52f440fe689b6",
"testharness"
],
"css/cssom/computed-style-001.html": [
"0331a648e6b0d56f0e7365f1ff7d991ea77ce3e4",
"testharness"
],
"css/cssom/computed-style-002.html": [
"d6579788bcfaf1d4c09324ba877a26ff95d6965d",
@@ -546176,29 +546192,29 @@
"89c506ea58f2ad38eb9ecc1e5f422b81a45b07fa",
"testharness"
],
"css/cssom/inline-style-001.html": [
"4c58b6153eabe796749dcaf181e03d7dce2c9c07",
"testharness"
],
"css/cssom/insertRule-charset-no-index.html": [
- "cd3a96351a4c8dcd417fb03963f9d4fb0760c746",
+ "2be98274fe292089f381d216dc415ddc812a105f",
"testharness"
],
"css/cssom/insertRule-import-no-index.html": [
- "ba89bad41a8d243f89ec91a0c02a34e97b378bc8",
+ "44ef5a2c490675d0088651dc101dbbb1fc83fdd1",
"testharness"
],
"css/cssom/insertRule-namespace-no-index.html": [
- "109ed203fabac2da4279419deb34d5bc5a393d09",
+ "b9b63240c4a7bf52524b8e3dd36d6ca2ecb4bcdc",
"testharness"
],
"css/cssom/insertRule-no-index.html": [
- "812f2b02d7694dd270b7a3e1ef205b99890ab216",
+ "825eb56d8e78bbdbd3bfb1861e6d40c245cd8f4b",
"testharness"
],
"css/cssom/insertRule-syntax-error-01.html": [
"36f824b24dd56e20b7c524111512d8743745daaa",
"testharness"
],
"css/cssom/interfaces.html": [
"42e325d3d7f6be7f557915072f61900ff611cef8",
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/css/css-scoping/shadow-host-with-before-after.html
@@ -0,0 +1,87 @@
+<!doctype html>
+<title>CSS Test: Pseudo-elements and :host selector.</title>
+<link rel="author" title="Antti Koivisto" href="mailto:koivisto@iki.fi"/>
+<link rel="author" title="Emilio Cobos Álvarez" href="mailto:emilio@crisal.io"/>
+<link rel="help" href="https://drafts.csswg.org/css-scoping/#selectors-data-model">
+<link rel="match" href="reference/green-box.html"/>
+<style>
+.test {
+ width: 100px;
+ height: 25px;
+ background: red;
+ color: red;
+}
+#host1, #host2 {
+ color: green;
+}
+#host3 div, #host4 div {
+ width: 50%;
+ height: 100%;
+ background: green;
+ display: inline-block;
+}
+</style>
+<p>Test passes if you see a single 100px by 100px green box below.</p>
+<div id="host1" class="test"></div>
+<div id="host2" class="test"></div>
+<div id="host3" class="test"><div>text</div></div>
+<div id="host4" class="test"><div>text</div></div>
+<script>
+
+host1.attachShadow({mode: 'closed'}).innerHTML = `<style>
+ :host::before, :host::after {
+ background: green;
+ width: 50%;
+ height: 100%;
+ background: green;
+ display: inline-block;
+ content: "test";
+ }
+ </style>`;
+
+host2.attachShadow({mode: 'closed'}).innerHTML = `<style>
+ :host(.green)::before, :host(.green)::after {
+ background: green;
+ width: 50%;
+ height: 100%;
+ background: green;
+ display: inline-block;
+ content: "test";
+ }
+ </style>`;
+
+getComputedStyle(host2).backgroundColor;
+host2.classList.add('green');
+
+host3.attachShadow({mode: 'closed'}).innerHTML = `<style>
+ :host {
+ color: green !important;
+ }
+ :host::before {
+ background: green;
+ width: 50%;
+ height: 100%;
+ background: green;
+ display: inline-block;
+ content: "test";
+ }
+ </style><slot></slot>`;
+
+host4.attachShadow({mode: 'closed'}).innerHTML = `<style>
+ :host(.green) {
+ color: green !important;
+ }
+ :host(.green)::after {
+ background: green;
+ width: 50%;
+ height: 100%;
+ background: green;
+ display: inline-block;
+ content: "test";
+ }
+ </style><slot></slot>`;
+
+getComputedStyle(host4).backgroundColor;
+host4.classList.add('green');
+
+</script>