Can't style descendants in shadow tree using the :host pseudo class
authorantti@apple.com <antti@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Mon, 22 Aug 2016 17:19:29 +0000 (17:19 +0000)
committerantti@apple.com <antti@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Mon, 22 Aug 2016 17:19:29 +0000 (17:19 +0000)
https://bugs.webkit.org/show_bug.cgi?id=160754

Reviewed by Darin Adler.

Source/WebCore:

"The :host pseudo-class, when evaluated in the context of a shadow tree, matches the shadow tree’s shadow host."

Currently :host() works for styling the host element itself (:host(.foo) { ... }). It should also be usable
for styling elements in the shadow tree:

    :host(.foo) div { ... }

Test: fast/shadow-dom/css-scoping-host-descendant.html

* css/SelectorChecker.cpp:
(WebCore::SelectorChecker::matchHostPseudoClass):
(WebCore::localContextForParent):

    Move to shadow host if we are matching :host and are at the root of the shadow tree.
    Set didMoveToShadowHost bit in the context.

(WebCore::SelectorChecker::matchRecursively):
(WebCore::SelectorChecker::checkOne):

    Call the existing matchHostPseudoClass to do :host matching if didMoveToShadowHost bit has been set.

* cssjit/SelectorCompiler.cpp:
(WebCore::SelectorCompiler::addPseudoClassType):

    Disallow :host in the compiler (cases where it would match didn't reach the compiler before).

LayoutTests:

* fast/shadow-dom/css-scoping-host-descendant-expected.html: Added.
* fast/shadow-dom/css-scoping-host-descendant.html: Added.

git-svn-id: http://svn.webkit.org/repository/webkit/trunk@204724 268f45cc-cd09-0410-ab3c-d52691b4dbfc

LayoutTests/ChangeLog
LayoutTests/fast/shadow-dom/css-scoping-host-descendant-expected.html [new file with mode: 0644]
LayoutTests/fast/shadow-dom/css-scoping-host-descendant.html [new file with mode: 0644]
Source/WebCore/ChangeLog
Source/WebCore/css/SelectorChecker.cpp
Source/WebCore/cssjit/SelectorCompiler.cpp

index 72c0299..47f02cd 100644 (file)
@@ -1,3 +1,13 @@
+2016-08-22  Antti Koivisto  <antti@apple.com>
+
+        Can't style descendants in shadow tree using the :host pseudo class
+        https://bugs.webkit.org/show_bug.cgi?id=160754
+
+        Reviewed by Darin Adler.
+
+        * fast/shadow-dom/css-scoping-host-descendant-expected.html: Added.
+        * fast/shadow-dom/css-scoping-host-descendant.html: Added.
+
 2016-08-22  Daniel Bates  <dabates@apple.com>
 
         [iOS] <a ping> and <area ping> tests time out
diff --git a/LayoutTests/fast/shadow-dom/css-scoping-host-descendant-expected.html b/LayoutTests/fast/shadow-dom/css-scoping-host-descendant-expected.html
new file mode 100644 (file)
index 0000000..eda713a
--- /dev/null
@@ -0,0 +1,7 @@
+<!DOCTYPE html>
+<html>
+    <body>
+        <p>Test passes if you see a single 100px by 100px green box below.</p>
+        <div style="width: 100px; height: 100px; background: green;"></div>
+    </body>
+</html>
diff --git a/LayoutTests/fast/shadow-dom/css-scoping-host-descendant.html b/LayoutTests/fast/shadow-dom/css-scoping-host-descendant.html
new file mode 100644 (file)
index 0000000..abd5e49
--- /dev/null
@@ -0,0 +1,65 @@
+<!DOCTYPE html>
+<html>
+<head>
+<style>
+my-host {
+  display: block;
+  width: 100px;
+  height: 25px;
+  background: red;
+}
+</style>
+</head>
+<body>
+<p>Test passes if you see a single 100px by 100px green box below.</p>
+<my-host id="t1"></my-host>
+<my-host id="t2" class="match"></my-host>
+<my-host id="t3" class="no-match"></my-host>
+<my-host id="t4" class="match"><div></div></my-host>
+
+<script>
+var host = document.querySelector('#t1');
+host.attachShadow({ mode: 'open' }).innerHTML = `
+  <style>
+    :host > div {
+      background-color: green; width: 100%; height: 100%;
+    }
+  </style>
+  <div></div>
+`;
+
+var host = document.querySelector('#t2');
+host.attachShadow({ mode: 'open' }).innerHTML = `
+  <style>
+    div { background-color: red; width: 100%; height: 100%; }
+    :host(.match) .descendant {
+      background-color: green;
+    }
+  </style>
+  <div><div class="descendant"></div></div>
+`;
+
+var host = document.querySelector('#t3');
+host.attachShadow({ mode: 'open' }).innerHTML = `
+  <style>
+    div { background-color: green; width: 100%; height: 100%; }
+    :host(.match) div {
+      background-color: red;
+    }
+  </style>
+  <div></div>
+`;
+
+var host = document.querySelector('#t4');
+host.attachShadow({ mode: 'open' }).innerHTML = `
+  <style>
+    :host(.match) ::slotted(div) {
+      background-color: green; width: 100%; height: 100%;
+    }
+  </style>
+  <slot></slot>
+`;
+
+</script>
+</body>
+</html>
index 9dabe7f..d2ca4bc 100644 (file)
@@ -1,3 +1,36 @@
+2016-08-22  Antti Koivisto  <antti@apple.com>
+
+        Can't style descendants in shadow tree using the :host pseudo class
+        https://bugs.webkit.org/show_bug.cgi?id=160754
+
+        Reviewed by Darin Adler.
+
+        "The :host pseudo-class, when evaluated in the context of a shadow tree, matches the shadow tree’s shadow host."
+
+        Currently :host() works for styling the host element itself (:host(.foo) { ... }). It should also be usable
+        for styling elements in the shadow tree:
+
+            :host(.foo) div { ... }
+
+        Test: fast/shadow-dom/css-scoping-host-descendant.html
+
+        * css/SelectorChecker.cpp:
+        (WebCore::SelectorChecker::matchHostPseudoClass):
+        (WebCore::localContextForParent):
+
+            Move to shadow host if we are matching :host and are at the root of the shadow tree.
+            Set didMoveToShadowHost bit in the context.
+
+        (WebCore::SelectorChecker::matchRecursively):
+        (WebCore::SelectorChecker::checkOne):
+
+            Call the existing matchHostPseudoClass to do :host matching if didMoveToShadowHost bit has been set.
+
+        * cssjit/SelectorCompiler.cpp:
+        (WebCore::SelectorCompiler::addPseudoClassType):
+
+            Disallow :host in the compiler (cases where it would match didn't reach the compiler before).
+
 2016-08-22  Frederic Wang  <fwang@igalia.com>
 
         Use memoize pattern for the menclose notation attribute
index 1150d48..b09437e 100644 (file)
@@ -81,6 +81,7 @@ struct SelectorChecker::LocalContext {
     bool pseudoElementEffective { true };
     bool hasScrollbarPseudo { false };
     bool hasSelectionPseudo { false };
+    bool didMoveToShadowHost { false };
 
 };
 
@@ -211,7 +212,6 @@ bool SelectorChecker::matchHostPseudoClass(const CSSSelector& selector, const El
 {
     ASSERT(element.shadowRoot());
     ASSERT(selector.match() == CSSSelector::PseudoClass && selector.pseudoClassType() == CSSSelector::PseudoClassHost);
-    ASSERT(checkingContext.resolvingMode != SelectorChecker::Mode::QueryingRules);
 
     specificity = selector.simpleSelectorSpecificity();
 
@@ -249,8 +249,22 @@ static SelectorChecker::LocalContext localContextForParent(const SelectorChecker
     // Disable :visited matching when we see the first link.
     if (context.element->isLink())
         updatedContext.visitedMatchType = VisitedMatchType::Disabled;
-    updatedContext.element = context.element->parentElement();
+
     updatedContext.isMatchElement = false;
+
+    if (updatedContext.didMoveToShadowHost) {
+        updatedContext.element = nullptr;
+        return updatedContext;
+    }
+
+    // Move to the shadow host if matching :host and the parent is the shadow root.
+    if (context.selector->match() == CSSSelector::PseudoClass && context.selector->pseudoClassType() == CSSSelector::PseudoClassHost && is<ShadowRoot>(context.element->parentNode())) {
+        updatedContext.element = downcast<ShadowRoot>(*context.element->parentNode()).host();
+        updatedContext.didMoveToShadowHost = true;
+        return updatedContext;
+    }
+
+    updatedContext.element = context.element->parentElement();
     return updatedContext;
 }
 
@@ -300,12 +314,12 @@ SelectorChecker::MatchResult SelectorChecker::matchRecursively(CheckingContext&
     CSSSelector::Relation relation = context.selector->relation();
 
     // Prepare next selector
-    const CSSSelector* historySelector = context.selector->tagHistory();
-    if (!historySelector)
+    const CSSSelector* leftSelector = context.selector->tagHistory();
+    if (!leftSelector)
         return MatchResult::matches(matchType);
 
     LocalContext nextContext(context);
-    nextContext.selector = historySelector;
+    nextContext.selector = leftSelector;
 
     if (relation != CSSSelector::SubSelector) {
         // Bail-out if this selector is irrelevant for the pseudoId
@@ -1021,16 +1035,21 @@ bool SelectorChecker::checkOne(CheckingContext& checkingContext, const LocalCont
             return matchesPastCuePseudoClass(element);
 #endif
 
-        case CSSSelector::PseudoClassScope:
-            {
-                const Node* contextualReferenceNode = !checkingContext.scope ? element.document().documentElement() : checkingContext.scope;
-                if (&element == contextualReferenceNode)
-                    return true;
-                break;
-            }
-        case CSSSelector::PseudoClassHost:
-            // :host matches based on context. Cases that reach selector checker don't match.
-            return false;
+        case CSSSelector::PseudoClassScope: {
+            const Node* contextualReferenceNode = !checkingContext.scope ? element.document().documentElement() : checkingContext.scope;
+            if (&element == contextualReferenceNode)
+                return true;
+            break;
+        }
+        case CSSSelector::PseudoClassHost: {
+            if (!context.didMoveToShadowHost)
+                return false;
+            unsigned hostSpecificity;
+            if (!matchHostPseudoClass(selector, element, checkingContext, hostSpecificity))
+                return false;
+            specificity = CSSSelector::addSpecificities(specificity, hostSpecificity);
+            return true;
+        }
 #if ENABLE(CUSTOM_ELEMENTS)
         case CSSSelector::PseudoClassDefined:
             return isDefinedElement(element);
index 8171df3..e3811bc 100644 (file)
@@ -829,8 +829,7 @@ static inline FunctionType addPseudoClassType(const CSSSelector& selector, Selec
             return functionType;
         }
     case CSSSelector::PseudoClassHost:
-        // :host matches based on context. Cases that reach selector checker don't match.
-        return FunctionType::CannotMatchAnything;
+        return FunctionType::CannotCompile;
     case CSSSelector::PseudoClassUnknown:
         ASSERT_NOT_REACHED();
         return FunctionType::CannotMatchAnything;