[CSS Shadow Parts] Implement style invalidation
authorantti@apple.com <antti@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Tue, 8 Oct 2019 12:22:21 +0000 (12:22 +0000)
committerantti@apple.com <antti@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Tue, 8 Oct 2019 12:22:21 +0000 (12:22 +0000)
https://bugs.webkit.org/show_bug.cgi?id=202632

Reviewed by Ryosuke Niwa.

LayoutTests/imported/w3c:

* web-platform-tests/css/css-shadow-parts/invalidation-change-exportparts-forward-expected.txt:
* web-platform-tests/css/css-shadow-parts/invalidation-change-part-name-expected.txt:
* web-platform-tests/css/css-shadow-parts/invalidation-change-part-name-forward-expected.txt:

Source/WebCore:

Tests: fast/css/shadow-parts/invalidation-class-descendant-combinator-export.html
       fast/css/shadow-parts/invalidation-class-descendant-combinator.html
       fast/css/shadow-parts/invalidation-class-sibling-combinator-export.html
       fast/css/shadow-parts/invalidation-class-sibling-combinator.html

* css/RuleFeature.cpp:
(WebCore::isSiblingOrSubject):

Don't treat crossing to host as moving to ancestor when computing elements impacted by a selector.
StyleInvalidator expects these relations to be about the host element, shadow tree invalidation in handled separately.

* dom/Element.cpp:
(WebCore::Element::attributeChanged):

Invalidate parts in shadow tree on 'exportparts' attribute mutation.

(WebCore::Element::partAttributeChanged):

Invalidate element on 'part' attribute mutation.

* style/StyleInvalidator.cpp:
(WebCore::Style::Invalidator::Invalidator):
(WebCore::Style::Invalidator::invalidateIfNeeded):

Invalidate parts in shadow tree during class and attribute mutation invalidation.

(WebCore::Style::Invalidator::invalidateShadowParts):
* style/StyleInvalidator.h:
(WebCore::Style::Invalidator::dirtiesAllStyle const):
(WebCore::Style::Invalidator::hasShadowPseudoElementRulesInAuthorSheet const): Deleted.

Remove an unncessary bool.

LayoutTests:

Add some tests to verify class mutations and combinators with ::part and exportpart.

* fast/css/shadow-parts/invalidation-class-descendant-combinator-expected.txt: Added.
* fast/css/shadow-parts/invalidation-class-descendant-combinator-export-expected.txt: Added.
* fast/css/shadow-parts/invalidation-class-descendant-combinator-export.html: Added.
* fast/css/shadow-parts/invalidation-class-descendant-combinator.html: Added.
* fast/css/shadow-parts/invalidation-class-sibling-combinator-expected.txt: Added.
* fast/css/shadow-parts/invalidation-class-sibling-combinator-export-expected.txt: Added.
* fast/css/shadow-parts/invalidation-class-sibling-combinator-export.html: Added.
* fast/css/shadow-parts/invalidation-class-sibling-combinator.html: Added.

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

18 files changed:
LayoutTests/ChangeLog
LayoutTests/fast/css/shadow-parts/invalidation-class-descendant-combinator-expected.txt [new file with mode: 0644]
LayoutTests/fast/css/shadow-parts/invalidation-class-descendant-combinator-export-expected.txt [new file with mode: 0644]
LayoutTests/fast/css/shadow-parts/invalidation-class-descendant-combinator-export.html [new file with mode: 0644]
LayoutTests/fast/css/shadow-parts/invalidation-class-descendant-combinator.html [new file with mode: 0644]
LayoutTests/fast/css/shadow-parts/invalidation-class-sibling-combinator-expected.txt [new file with mode: 0644]
LayoutTests/fast/css/shadow-parts/invalidation-class-sibling-combinator-export-expected.txt [new file with mode: 0644]
LayoutTests/fast/css/shadow-parts/invalidation-class-sibling-combinator-export.html [new file with mode: 0644]
LayoutTests/fast/css/shadow-parts/invalidation-class-sibling-combinator.html [new file with mode: 0644]
LayoutTests/imported/w3c/ChangeLog
LayoutTests/imported/w3c/web-platform-tests/css/css-shadow-parts/invalidation-change-exportparts-forward-expected.txt
LayoutTests/imported/w3c/web-platform-tests/css/css-shadow-parts/invalidation-change-part-name-expected.txt
LayoutTests/imported/w3c/web-platform-tests/css/css-shadow-parts/invalidation-change-part-name-forward-expected.txt
Source/WebCore/ChangeLog
Source/WebCore/css/RuleFeature.cpp
Source/WebCore/dom/Element.cpp
Source/WebCore/style/StyleInvalidator.cpp
Source/WebCore/style/StyleInvalidator.h

index 8b13ddc..a29ebe4 100644 (file)
@@ -1,5 +1,23 @@
 2019-10-08  Antti Koivisto  <antti@apple.com>
 
+        [CSS Shadow Parts] Implement style invalidation
+        https://bugs.webkit.org/show_bug.cgi?id=202632
+
+        Reviewed by Ryosuke Niwa.
+
+        Add some tests to verify class mutations and combinators with ::part and exportpart.
+
+        * fast/css/shadow-parts/invalidation-class-descendant-combinator-expected.txt: Added.
+        * fast/css/shadow-parts/invalidation-class-descendant-combinator-export-expected.txt: Added.
+        * fast/css/shadow-parts/invalidation-class-descendant-combinator-export.html: Added.
+        * fast/css/shadow-parts/invalidation-class-descendant-combinator.html: Added.
+        * fast/css/shadow-parts/invalidation-class-sibling-combinator-expected.txt: Added.
+        * fast/css/shadow-parts/invalidation-class-sibling-combinator-export-expected.txt: Added.
+        * fast/css/shadow-parts/invalidation-class-sibling-combinator-export.html: Added.
+        * fast/css/shadow-parts/invalidation-class-sibling-combinator.html: Added.
+
+2019-10-08  Antti Koivisto  <antti@apple.com>
+
         [CSS Shadow Parts] Internal shadow pseudo elements should work with ::part
         https://bugs.webkit.org/show_bug.cgi?id=202620
 
diff --git a/LayoutTests/fast/css/shadow-parts/invalidation-class-descendant-combinator-expected.txt b/LayoutTests/fast/css/shadow-parts/invalidation-class-descendant-combinator-expected.txt
new file mode 100644 (file)
index 0000000..c4bcc24
--- /dev/null
@@ -0,0 +1,4 @@
+The following text should be green:
+
+PASS Part in selected host changed color 
+
diff --git a/LayoutTests/fast/css/shadow-parts/invalidation-class-descendant-combinator-export-expected.txt b/LayoutTests/fast/css/shadow-parts/invalidation-class-descendant-combinator-export-expected.txt
new file mode 100644 (file)
index 0000000..c4bcc24
--- /dev/null
@@ -0,0 +1,4 @@
+The following text should be green:
+
+PASS Part in selected host changed color 
+
diff --git a/LayoutTests/fast/css/shadow-parts/invalidation-class-descendant-combinator-export.html b/LayoutTests/fast/css/shadow-parts/invalidation-class-descendant-combinator-export.html
new file mode 100644 (file)
index 0000000..c9d8a59
--- /dev/null
@@ -0,0 +1,35 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <title>CSS Shadow Parts - Invalidation with with class in descendant combinator and part forward</title>
+    <link href="http://www.apple.com/" rel="author" title="Apple">
+    <link href="https://drafts.csswg.org/css-shadow-parts/" rel="help">
+    <script src="../../../resources/testharness.js"></script>
+    <script src="../../../resources/testharnessreport.js"></script>
+    <script src="../../../imported/w3c/web-platform-tests/css/css-shadow-parts/support/shadow-helper.js"></script>
+  </head>
+  <body>
+    <style>.ancestor #c-e-outer::part(part-forwarded) { color: green; }</style>
+    <script>installCustomElement("custom-element-inner", "custom-element-inner-template");</script>
+    <template id="custom-element-inner-template">
+      <style>span { color: red; }</style>
+      <span id="part" part="partp">This text</span>
+    </template>
+    <script>installCustomElement("custom-element-outer", "custom-element-outer-template");</script>
+    <template id="custom-element-outer-template"><custom-element-inner id="c-e-inner" exportparts="partp: part-forwarded"></custom-element-inner></template>
+    The following text should be green:
+    <div id="target">
+        <div><custom-element-outer id="c-e-outer"></custom-element-outer></div>
+    </div>
+    <script>
+      "use strict";
+      test(function() {
+        const part = getElementByShadowIds(document, ["c-e-outer", "c-e-inner", "part"]);
+        const before = window.getComputedStyle(part).color;
+        document.querySelector("#target").classList.add("ancestor");
+        const after = window.getComputedStyle(part).color;
+        assert_not_equals(before, after);
+      }, "Part in selected host changed color");
+    </script>
+  </body>
+</html>
diff --git a/LayoutTests/fast/css/shadow-parts/invalidation-class-descendant-combinator.html b/LayoutTests/fast/css/shadow-parts/invalidation-class-descendant-combinator.html
new file mode 100644 (file)
index 0000000..39ee780
--- /dev/null
@@ -0,0 +1,33 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <title>CSS Shadow Parts - Invalidation with class in descendant combinator</title>
+    <link href="http://www.apple.com/" rel="author" title="Apple">
+    <link href="https://drafts.csswg.org/css-shadow-parts/" rel="help">
+    <script src="../../../resources/testharness.js"></script>
+    <script src="../../../resources/testharnessreport.js"></script>
+    <script src="../../../imported/w3c/web-platform-tests/css/css-shadow-parts/support/shadow-helper.js"></script>
+  </head>
+  <body>
+    <style>.ancestor #c-e::part(partp) { color: green; }</style>
+    <script>installCustomElement("custom-element", "custom-element-template");</script>
+    <template id="custom-element-template">
+      <style>span { color: red; }</style>
+      <span id="part" part="partp">This text</span>
+    </template>
+    The following text should be green:
+    <div id="target">
+      <div><custom-element id="c-e"></custom-element></div>
+    </div>
+    <script>
+      "use strict";
+      test(function() {
+        const part = getElementByShadowIds(document, ["c-e", "part"]);
+        const before = window.getComputedStyle(part).color;
+        document.querySelector("#target").classList.add("ancestor");
+        const after = window.getComputedStyle(part).color;
+        assert_not_equals(before, after);
+      }, "Part in selected host changed color");
+    </script>
+  </body>
+</html>
diff --git a/LayoutTests/fast/css/shadow-parts/invalidation-class-sibling-combinator-expected.txt b/LayoutTests/fast/css/shadow-parts/invalidation-class-sibling-combinator-expected.txt
new file mode 100644 (file)
index 0000000..c4bcc24
--- /dev/null
@@ -0,0 +1,4 @@
+The following text should be green:
+
+PASS Part in selected host changed color 
+
diff --git a/LayoutTests/fast/css/shadow-parts/invalidation-class-sibling-combinator-export-expected.txt b/LayoutTests/fast/css/shadow-parts/invalidation-class-sibling-combinator-export-expected.txt
new file mode 100644 (file)
index 0000000..c4bcc24
--- /dev/null
@@ -0,0 +1,4 @@
+The following text should be green:
+
+PASS Part in selected host changed color 
+
diff --git a/LayoutTests/fast/css/shadow-parts/invalidation-class-sibling-combinator-export.html b/LayoutTests/fast/css/shadow-parts/invalidation-class-sibling-combinator-export.html
new file mode 100644 (file)
index 0000000..263fd09
--- /dev/null
@@ -0,0 +1,37 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <title>CSS Shadow Parts - Invalidation with with class in descendant combinator and part forward</title>
+    <link href="http://www.apple.com/" rel="author" title="Apple">
+    <link href="https://drafts.csswg.org/css-shadow-parts/" rel="help">
+    <script src="../../../resources/testharness.js"></script>
+    <script src="../../../resources/testharnessreport.js"></script>
+    <script src="../../../imported/w3c/web-platform-tests/css/css-shadow-parts/support/shadow-helper.js"></script>
+  </head>
+  <body>
+    <style>.sibling ~ #c-e-outer::part(part-forwarded) { color: green; }</style>
+    <script>installCustomElement("custom-element-inner", "custom-element-inner-template");</script>
+    <template id="custom-element-inner-template">
+      <style>span { color: red; }</style>
+      <span id="part" part="partp">This text</span>
+    </template>
+    <script>installCustomElement("custom-element-outer", "custom-element-outer-template");</script>
+    <template id="custom-element-outer-template"><custom-element-inner id="c-e-inner" exportparts="partp: part-forwarded"></custom-element-inner></template>
+    The following text should be green:
+    <div>
+      <div id="target"></div>
+      <div></div>
+      <custom-element-outer id="c-e-outer"></custom-element-outer>
+    </div>
+    <script>
+      "use strict";
+      test(function() {
+        const part = getElementByShadowIds(document, ["c-e-outer", "c-e-inner", "part"]);
+        const before = window.getComputedStyle(part).color;
+        document.querySelector("#target").classList.add("sibling");
+        const after = window.getComputedStyle(part).color;
+        assert_not_equals(before, after);
+      }, "Part in selected host changed color");
+    </script>
+  </body>
+</html>
diff --git a/LayoutTests/fast/css/shadow-parts/invalidation-class-sibling-combinator.html b/LayoutTests/fast/css/shadow-parts/invalidation-class-sibling-combinator.html
new file mode 100644 (file)
index 0000000..c4f67e7
--- /dev/null
@@ -0,0 +1,35 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <title>CSS Shadow Parts - Invalidation with class in descendant combinator</title>
+    <link href="http://www.apple.com/" rel="author" title="Apple">
+    <link href="https://drafts.csswg.org/css-shadow-parts/" rel="help">
+    <script src="../../../resources/testharness.js"></script>
+    <script src="../../../resources/testharnessreport.js"></script>
+    <script src="../../../imported/w3c/web-platform-tests/css/css-shadow-parts/support/shadow-helper.js"></script>
+  </head>
+  <body>
+    <style>.sibling ~ #c-e::part(partp) { color: green; }</style>
+    <script>installCustomElement("custom-element", "custom-element-template");</script>
+    <template id="custom-element-template">
+      <style>span { color: red; }</style>
+      <span id="part" part="partp">This text</span>
+    </template>
+    The following text should be green:
+    <div>
+      <div id="target"></div>
+      <div></div>
+      <custom-element id="c-e"></custom-element>
+    </div>
+    <script>
+      "use strict";
+      test(function() {
+        const part = getElementByShadowIds(document, ["c-e", "part"]);
+        const before = window.getComputedStyle(part).color;
+        document.querySelector("#target").classList.add("sibling");
+        const after = window.getComputedStyle(part).color;
+        assert_not_equals(before, after);
+      }, "Part in selected host changed color");
+    </script>
+  </body>
+</html>
index 70fb1b8..75e4f53 100644 (file)
@@ -1,3 +1,14 @@
+2019-10-08  Antti Koivisto  <antti@apple.com>
+
+        [CSS Shadow Parts] Implement style invalidation
+        https://bugs.webkit.org/show_bug.cgi?id=202632
+
+        Reviewed by Ryosuke Niwa.
+
+        * web-platform-tests/css/css-shadow-parts/invalidation-change-exportparts-forward-expected.txt:
+        * web-platform-tests/css/css-shadow-parts/invalidation-change-part-name-expected.txt:
+        * web-platform-tests/css/css-shadow-parts/invalidation-change-part-name-forward-expected.txt:
+
 2019-10-07  Rob Buis  <rbuis@igalia.com>
 
         Change Response's statusText's default
index b8fd6df..2d0d99a 100644 (file)
@@ -1,3 +1,43 @@
+2019-10-08  Antti Koivisto  <antti@apple.com>
+
+        [CSS Shadow Parts] Implement style invalidation
+        https://bugs.webkit.org/show_bug.cgi?id=202632
+
+        Reviewed by Ryosuke Niwa.
+
+        Tests: fast/css/shadow-parts/invalidation-class-descendant-combinator-export.html
+               fast/css/shadow-parts/invalidation-class-descendant-combinator.html
+               fast/css/shadow-parts/invalidation-class-sibling-combinator-export.html
+               fast/css/shadow-parts/invalidation-class-sibling-combinator.html
+
+        * css/RuleFeature.cpp:
+        (WebCore::isSiblingOrSubject):
+
+        Don't treat crossing to host as moving to ancestor when computing elements impacted by a selector.
+        StyleInvalidator expects these relations to be about the host element, shadow tree invalidation in handled separately.
+
+        * dom/Element.cpp:
+        (WebCore::Element::attributeChanged):
+
+        Invalidate parts in shadow tree on 'exportparts' attribute mutation.
+
+        (WebCore::Element::partAttributeChanged):
+
+        Invalidate element on 'part' attribute mutation.
+
+        * style/StyleInvalidator.cpp:
+        (WebCore::Style::Invalidator::Invalidator):
+        (WebCore::Style::Invalidator::invalidateIfNeeded):
+
+        Invalidate parts in shadow tree during class and attribute mutation invalidation.
+
+        (WebCore::Style::Invalidator::invalidateShadowParts):
+        * style/StyleInvalidator.h:
+        (WebCore::Style::Invalidator::dirtiesAllStyle const):
+        (WebCore::Style::Invalidator::hasShadowPseudoElementRulesInAuthorSheet const): Deleted.
+
+        Remove an unncessary bool.
+
 2019-10-08  Carlos Garcia Campos  <cgarcia@igalia.com>
 
         Unreviewed. Remove unused WebKitSoupRequestGeneric after r250597
index 44eeb1d..ca1b94f 100644 (file)
@@ -42,12 +42,12 @@ static bool isSiblingOrSubject(MatchElement matchElement)
     case MatchElement::IndirectSibling:
     case MatchElement::DirectSibling:
     case MatchElement::AnySibling:
+    case MatchElement::Host:
         return true;
     case MatchElement::Parent:
     case MatchElement::Ancestor:
     case MatchElement::ParentSibling:
     case MatchElement::AncestorSibling:
-    case MatchElement::Host:
         return false;
     }
     ASSERT_NOT_REACHED();
index f15a41b..c769a92 100644 (file)
 #include "Settings.h"
 #include "SimulatedClick.h"
 #include "SlotAssignment.h"
+#include "StyleInvalidator.h"
 #include "StyleProperties.h"
 #include "StyleResolver.h"
 #include "StyleScope.h"
@@ -1750,8 +1751,10 @@ void Element::attributeChanged(const QualifiedName& name, const AtomString& oldV
         } else if (name == HTMLNames::partAttr)
             partAttributeChanged(newValue);
         else if (name == HTMLNames::exportpartsAttr) {
-            if (auto* shadowRoot = this->shadowRoot())
+            if (auto* shadowRoot = this->shadowRoot()) {
                 shadowRoot->invalidatePartMappings();
+                Style::Invalidator::invalidateShadowParts(*shadowRoot);
+            }
         }
     }
 
@@ -1835,6 +1838,9 @@ void Element::partAttributeChanged(const AtomString& newValue)
         if (auto* partList = elementRareData()->partList())
             partList->associatedAttributeValueChanged(newValue);
     }
+
+    if (needsStyleInvalidation() && isInShadowTree())
+        invalidateStyleInternal();
 }
 
 URL Element::absoluteLinkURL() const
index 6d695a6..936a915 100644 (file)
@@ -31,6 +31,7 @@
 #include "ElementIterator.h"
 #include "ElementRuleCollector.h"
 #include "HTMLSlotElement.h"
+#include "RuntimeEnabledFeatures.h"
 #include "SelectorFilter.h"
 #include "ShadowRoot.h"
 #include "StyleRuleImport.h"
@@ -88,24 +89,25 @@ Invalidator::Invalidator(const Vector<StyleSheetContents*>& sheets, const MediaQ
     m_ownedRuleSet->disableAutoShrinkToFit();
     for (auto& sheet : sheets)
         m_ownedRuleSet->addRulesFromSheet(*sheet, mediaQueryEvaluator);
-
-    m_hasShadowPseudoElementRulesInAuthorSheet = m_ruleSet.hasShadowPseudoElementRules();
 }
 
 Invalidator::Invalidator(const RuleSet& ruleSet)
     : m_ruleSet(ruleSet)
-    , m_hasShadowPseudoElementRulesInAuthorSheet(ruleSet.hasShadowPseudoElementRules())
 {
 }
 
 Invalidator::CheckDescendants Invalidator::invalidateIfNeeded(Element& element, const SelectorFilter* filter)
 {
-    if (m_hasShadowPseudoElementRulesInAuthorSheet) {
+    if (m_ruleSet.hasShadowPseudoElementRules()) {
         // FIXME: This could do actual rule matching too.
         if (element.shadowRoot())
             element.invalidateStyleForSubtreeInternal();
     }
 
+    // FIXME: More fine-grained invalidation for ::part()
+    if (!m_ruleSet.partPseudoElementRules().isEmpty() && element.shadowRoot())
+        invalidateShadowParts(*element.shadowRoot());
+
     bool shouldCheckForSlots = !m_ruleSet.slottedPseudoElementRules().isEmpty() && !m_didInvalidateHostChildren;
     if (shouldCheckForSlots && is<HTMLSlotElement>(element)) {
         auto* containingShadowRoot = element.containingShadowRoot();
@@ -262,5 +264,24 @@ void Invalidator::invalidateStyleWithMatchElement(Element& element, MatchElement
     }
 }
 
+void Invalidator::invalidateShadowParts(ShadowRoot& shadowRoot)
+{
+    if (!RuntimeEnabledFeatures::sharedFeatures().cssShadowPartsEnabled())
+        return;
+
+    if (shadowRoot.mode() == ShadowRootMode::UserAgent)
+        return;
+
+    for (auto& descendant : descendantsOfType<Element>(shadowRoot)) {
+        // FIXME: We could only invalidate part names that actually show up in rules.
+        if (!descendant.partNames().isEmpty())
+            descendant.invalidateStyleInternal();
+
+        auto* nestedShadowRoot = descendant.shadowRoot();
+        if (nestedShadowRoot && !nestedShadowRoot->partMappings().isEmpty())
+            invalidateShadowParts(*nestedShadowRoot);
+    }
+}
+
 }
 }
index 6468e85..200cdb6 100644 (file)
@@ -46,12 +46,13 @@ public:
     Invalidator(const RuleSet&);
 
     bool dirtiesAllStyle() const { return m_dirtiesAllStyle; }
-    bool hasShadowPseudoElementRulesInAuthorSheet() const { return m_hasShadowPseudoElementRulesInAuthorSheet; }
     void invalidateStyle(Document&);
     void invalidateStyle(ShadowRoot&);
     void invalidateStyle(Element&);
     void invalidateStyleWithMatchElement(Element&, MatchElement);
 
+    static void invalidateShadowParts(ShadowRoot&);
+
 private:
     enum class CheckDescendants { Yes, No };
     CheckDescendants invalidateIfNeeded(Element&, const SelectorFilter*);
@@ -61,7 +62,6 @@ private:
     std::unique_ptr<RuleSet> m_ownedRuleSet;
     const RuleSet& m_ruleSet;
     bool m_dirtiesAllStyle { false };
-    bool m_hasShadowPseudoElementRulesInAuthorSheet { false };
     bool m_didInvalidateHostChildren { false };
 };