Optimize style invalidations for attribute selectors
authorantti@apple.com <antti@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Tue, 16 Feb 2016 08:20:58 +0000 (08:20 +0000)
committerantti@apple.com <antti@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Tue, 16 Feb 2016 08:20:58 +0000 (08:20 +0000)
https://bugs.webkit.org/show_bug.cgi?id=154242

Reviewed by Andreas Kling.

Source/WebCore:

Currently we invalidate the whole element subtree if there are any attribute selectors for the changed attribute.
This is slow as generally few if any elements are really affected. Using attribute selectors for dynamic styling
should be performant.

This patch implements optimization strategy for attributes similar to what we already have for classes:

- Collect a map of all rules that contains descendant-affecting attribute selectors for a given attribute.
- When an attribute value changes check if there are any such rules for it.
- Check if the value change affects the results of any of the attribute selectors.
- Only if it does invalidate the exact descendant elements affected by the rules.

Test: fast/css/style-invalidation-attribute-change-descendants.html

* WebCore.xcodeproj/project.pbxproj:
* css/DocumentRuleSets.cpp:
(WebCore::DocumentRuleSets::ancestorClassRules):
(WebCore::DocumentRuleSets::ancestorAttributeRulesForHTML):

    Create optimization RuleSets when needed.

* css/DocumentRuleSets.h:
(WebCore::DocumentRuleSets::uncommonAttribute):
(WebCore::DocumentRuleSets::features):
* css/RuleFeature.cpp:
(WebCore::RuleFeatureSet::recursivelyCollectFeaturesFromSelector):
(WebCore::makeAttributeSelectorKey):
(WebCore::RuleFeatureSet::collectFeatures):

    Collect rules with descendant affecting attribute selectors.

(WebCore::RuleFeatureSet::add):
(WebCore::RuleFeatureSet::clear):
(WebCore::RuleFeatureSet::shrinkToFit):
* css/RuleFeature.h:
* css/SelectorChecker.cpp:
(WebCore::anyAttributeMatches):
(WebCore::SelectorChecker::attributeSelectorMatches):

    Expose function for matching single attribute selectors.

(WebCore::canMatchHoverOrActiveInQuirksMode):
* css/SelectorChecker.h:
* dom/Attr.cpp:
(WebCore::Attr::setValue):
(WebCore::Attr::childrenChanged):
* dom/Element.cpp:
(WebCore::Element::setAttributeInternal):
(WebCore::makeIdForStyleResolution):
(WebCore::Element::attributeChanged):
(WebCore::Element::removeAttributeInternal):
(WebCore::Element::addAttributeInternal):
(WebCore::Element::removeAttribute):

    Add AttributeChangeInvalidation where needed.

(WebCore::Element::needsStyleInvalidation):

    Move to Element from ClassChangeInvalidation.

(WebCore::Element::willModifyAttribute):

    No more full style invalidation on attribute change.

* style/AttributeChangeInvalidation.cpp: Added.
(WebCore::Style::AttributeChangeInvalidation::invalidateStyle):

    Invalidate local style.
    Check if we need to invalidate descendants by looking into ancestorAttributeRules.

(WebCore::Style::AttributeChangeInvalidation::invalidateDescendants):

    Use StyleInvalidationAnalysis to invalidate the subtree for the relevant rules.

* style/AttributeChangeInvalidation.h: Added.
(WebCore::Style::AttributeChangeInvalidation::needsInvalidation):
(WebCore::Style::AttributeChangeInvalidation::AttributeChangeInvalidation):
(WebCore::Style::AttributeChangeInvalidation::~AttributeChangeInvalidation):

    If needed, invalidate descendants before and after attribute change to catch rules that start and stop applying.

LayoutTests:

* fast/css/style-invalidation-attribute-change-descendants-expected.txt: Added.
* fast/css/style-invalidation-attribute-change-descendants.html: Added.

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

19 files changed:
LayoutTests/ChangeLog
LayoutTests/fast/css/style-invalidation-attribute-change-descendants-expected.txt [new file with mode: 0644]
LayoutTests/fast/css/style-invalidation-attribute-change-descendants.html [new file with mode: 0644]
Source/WebCore/CMakeLists.txt
Source/WebCore/ChangeLog
Source/WebCore/WebCore.vcxproj/WebCore.vcxproj
Source/WebCore/WebCore.xcodeproj/project.pbxproj
Source/WebCore/css/DocumentRuleSets.cpp
Source/WebCore/css/DocumentRuleSets.h
Source/WebCore/css/RuleFeature.cpp
Source/WebCore/css/RuleFeature.h
Source/WebCore/css/SelectorChecker.cpp
Source/WebCore/css/SelectorChecker.h
Source/WebCore/dom/Attr.cpp
Source/WebCore/dom/Element.cpp
Source/WebCore/dom/Element.h
Source/WebCore/style/AttributeChangeInvalidation.cpp [new file with mode: 0644]
Source/WebCore/style/AttributeChangeInvalidation.h [new file with mode: 0644]
Source/WebCore/style/ClassChangeInvalidation.h

index 892150c..2b0177c 100644 (file)
@@ -1,3 +1,13 @@
+2016-02-15  Antti Koivisto  <antti@apple.com>
+
+        Optimize style invalidations for attribute selectors
+        https://bugs.webkit.org/show_bug.cgi?id=154242
+
+        Reviewed by Andreas Kling.
+
+        * fast/css/style-invalidation-attribute-change-descendants-expected.txt: Added.
+        * fast/css/style-invalidation-attribute-change-descendants.html: Added.
+
 2016-02-16  Chris Dumez  <cdumez@apple.com>
 
         Do security checks early in JSDOMWindow::put*()
diff --git a/LayoutTests/fast/css/style-invalidation-attribute-change-descendants-expected.txt b/LayoutTests/fast/css/style-invalidation-attribute-change-descendants-expected.txt
new file mode 100644 (file)
index 0000000..8c27bf0
--- /dev/null
@@ -0,0 +1,149 @@
+Test that we invalidate the element subtree minimally on class attribute change
+
+On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
+
+
+PASS hasExpectedStyle is true
+PASS testStyleChangeType("root", "NoStyleChange") || testStyleChangeType("root", "InlineStyleChange") is true
+PASS testStyleChangeType("target", "NoStyleChange") is true
+PASS testStyleChangeType("inert", "NoStyleChange") is true
+PASS hasExpectedStyle is true
+Setting attribute 'myattr' value ''
+PASS testStyleChangeType("root", "NoStyleChange") || testStyleChangeType("root", "InlineStyleChange") is true
+PASS testStyleChangeType("target", "InlineStyleChange") is true
+PASS testStyleChangeType("inert", "NoStyleChange") is true
+PASS hasExpectedStyle is true
+Setting attribute 'myattr' value 'foo'
+PASS testStyleChangeType("root", "NoStyleChange") || testStyleChangeType("root", "InlineStyleChange") is true
+PASS testStyleChangeType("target", "NoStyleChange") is true
+PASS testStyleChangeType("inert", "NoStyleChange") is true
+PASS hasExpectedStyle is true
+Setting attribute 'myattr' value 'value2'
+PASS testStyleChangeType("root", "NoStyleChange") || testStyleChangeType("root", "InlineStyleChange") is true
+PASS testStyleChangeType("target", "InlineStyleChange") is true
+PASS testStyleChangeType("inert", "NoStyleChange") is true
+PASS hasExpectedStyle is true
+Setting attribute 'myattr' value 'foo'
+PASS testStyleChangeType("root", "NoStyleChange") || testStyleChangeType("root", "InlineStyleChange") is true
+PASS testStyleChangeType("target", "InlineStyleChange") is true
+PASS testStyleChangeType("inert", "NoStyleChange") is true
+PASS hasExpectedStyle is true
+Removing attribute 'myattr'
+PASS testStyleChangeType("root", "NoStyleChange") || testStyleChangeType("root", "InlineStyleChange") is true
+PASS testStyleChangeType("target", "InlineStyleChange") is true
+PASS testStyleChangeType("inert", "NoStyleChange") is true
+PASS hasExpectedStyle is true
+Setting attribute 'myattr' value 'value3'
+PASS testStyleChangeType("root", "NoStyleChange") || testStyleChangeType("root", "InlineStyleChange") is true
+PASS testStyleChangeType("target", "InlineStyleChange") is true
+PASS testStyleChangeType("inert", "NoStyleChange") is true
+PASS hasExpectedStyle is true
+Setting attribute 'myattr' value 'dummy value3'
+PASS testStyleChangeType("root", "NoStyleChange") || testStyleChangeType("root", "InlineStyleChange") is true
+PASS testStyleChangeType("target", "NoStyleChange") is true
+PASS testStyleChangeType("inert", "NoStyleChange") is true
+PASS hasExpectedStyle is true
+Setting attribute 'myattr' value 'value4-foo'
+PASS testStyleChangeType("root", "NoStyleChange") || testStyleChangeType("root", "InlineStyleChange") is true
+PASS testStyleChangeType("target", "InlineStyleChange") is true
+PASS testStyleChangeType("inert", "NoStyleChange") is true
+PASS hasExpectedStyle is true
+Setting attribute 'myattr' value 'value4-foobar'
+PASS testStyleChangeType("root", "NoStyleChange") || testStyleChangeType("root", "InlineStyleChange") is true
+PASS testStyleChangeType("target", "NoStyleChange") is true
+PASS testStyleChangeType("inert", "NoStyleChange") is true
+PASS hasExpectedStyle is true
+Setting attribute 'myattr' value 'dummy value4-foo'
+PASS testStyleChangeType("root", "NoStyleChange") || testStyleChangeType("root", "InlineStyleChange") is true
+PASS testStyleChangeType("target", "InlineStyleChange") is true
+PASS testStyleChangeType("inert", "NoStyleChange") is true
+PASS hasExpectedStyle is true
+Setting attribute 'myattr' value 'value5foo'
+PASS testStyleChangeType("root", "NoStyleChange") || testStyleChangeType("root", "InlineStyleChange") is true
+PASS testStyleChangeType("target", "InlineStyleChange") is true
+PASS testStyleChangeType("inert", "NoStyleChange") is true
+PASS hasExpectedStyle is true
+Setting attribute 'myattr' value 'value5foobar'
+PASS testStyleChangeType("root", "NoStyleChange") || testStyleChangeType("root", "InlineStyleChange") is true
+PASS testStyleChangeType("target", "NoStyleChange") is true
+PASS testStyleChangeType("inert", "NoStyleChange") is true
+PASS hasExpectedStyle is true
+Setting attribute 'myattr' value 'foovalue5'
+PASS testStyleChangeType("root", "NoStyleChange") || testStyleChangeType("root", "InlineStyleChange") is true
+PASS testStyleChangeType("target", "InlineStyleChange") is true
+PASS testStyleChangeType("inert", "NoStyleChange") is true
+PASS hasExpectedStyle is true
+Setting attribute 'myattr' value 'foovalue6'
+PASS testStyleChangeType("root", "NoStyleChange") || testStyleChangeType("root", "InlineStyleChange") is true
+PASS testStyleChangeType("target", "InlineStyleChange") is true
+PASS testStyleChangeType("inert", "NoStyleChange") is true
+PASS hasExpectedStyle is true
+Setting attribute 'myAttr' value 'foobarvalue6'
+PASS testStyleChangeType("root", "NoStyleChange") || testStyleChangeType("root", "InlineStyleChange") is true
+PASS testStyleChangeType("target", "NoStyleChange") is true
+PASS testStyleChangeType("inert", "NoStyleChange") is true
+PASS hasExpectedStyle is true
+Setting attribute 'MYATTR' value 'value6foo'
+PASS testStyleChangeType("root", "NoStyleChange") || testStyleChangeType("root", "InlineStyleChange") is true
+PASS testStyleChangeType("target", "InlineStyleChange") is true
+PASS testStyleChangeType("inert", "NoStyleChange") is true
+PASS hasExpectedStyle is true
+Setting attribute 'myattr' value 'value7'
+PASS testStyleChangeType("root", "NoStyleChange") || testStyleChangeType("root", "InlineStyleChange") is true
+PASS testStyleChangeType("target", "InlineStyleChange") is true
+PASS testStyleChangeType("inert", "NoStyleChange") is true
+PASS hasExpectedStyle is true
+Setting attribute 'myattr' value 'value7foo'
+PASS testStyleChangeType("root", "NoStyleChange") || testStyleChangeType("root", "InlineStyleChange") is true
+PASS testStyleChangeType("target", "NoStyleChange") is true
+PASS testStyleChangeType("inert", "NoStyleChange") is true
+PASS hasExpectedStyle is true
+Setting attribute 'myATTR' value 'foovalue7foo'
+PASS testStyleChangeType("root", "NoStyleChange") || testStyleChangeType("root", "InlineStyleChange") is true
+PASS testStyleChangeType("target", "NoStyleChange") is true
+PASS testStyleChangeType("inert", "NoStyleChange") is true
+PASS hasExpectedStyle is true
+Setting attribute 'myattr' value 'VALUE7foo'
+PASS testStyleChangeType("root", "NoStyleChange") || testStyleChangeType("root", "InlineStyleChange") is true
+PASS testStyleChangeType("target", "InlineStyleChange") is true
+PASS testStyleChangeType("inert", "NoStyleChange") is true
+PASS hasExpectedStyle is true
+Setting attribute 'myattr' value 'Value8'
+PASS testStyleChangeType("root", "NoStyleChange") || testStyleChangeType("root", "InlineStyleChange") is true
+PASS testStyleChangeType("target", "InlineStyleChange") is true
+PASS testStyleChangeType("inert", "NoStyleChange") is true
+PASS hasExpectedStyle is true
+Setting attribute 'myattr' value 'value8foo'
+PASS testStyleChangeType("root", "NoStyleChange") || testStyleChangeType("root", "InlineStyleChange") is true
+PASS testStyleChangeType("target", "NoStyleChange") is true
+PASS testStyleChangeType("inert", "NoStyleChange") is true
+PASS hasExpectedStyle is true
+Setting attribute 'myATTR' value 'FOOVALue8foo'
+PASS testStyleChangeType("root", "NoStyleChange") || testStyleChangeType("root", "InlineStyleChange") is true
+PASS testStyleChangeType("target", "NoStyleChange") is true
+PASS testStyleChangeType("inert", "NoStyleChange") is true
+PASS hasExpectedStyle is true
+Setting attribute 'myattr' value 'VALUE8foo'
+PASS testStyleChangeType("root", "NoStyleChange") || testStyleChangeType("root", "InlineStyleChange") is true
+PASS testStyleChangeType("target", "NoStyleChange") is true
+PASS testStyleChangeType("inert", "NoStyleChange") is true
+PASS hasExpectedStyle is true
+Setting attribute 'myattr' value 'VALUE 8foo'
+PASS testStyleChangeType("root", "NoStyleChange") || testStyleChangeType("root", "InlineStyleChange") is true
+PASS testStyleChangeType("target", "InlineStyleChange") is true
+PASS testStyleChangeType("inert", "NoStyleChange") is true
+PASS hasExpectedStyle is true
+Setting attribute 'myattr2' value ''
+PASS testStyleChangeType("root", "NoStyleChange") || testStyleChangeType("root", "InlineStyleChange") is true
+PASS testStyleChangeType("target", "NoStyleChange") is true
+PASS testStyleChangeType("inert", "NoStyleChange") is true
+PASS hasExpectedStyle is true
+Setting attribute 'myattr2' value 'foo'
+PASS testStyleChangeType("root", "NoStyleChange") || testStyleChangeType("root", "InlineStyleChange") is true
+PASS testStyleChangeType("target", "NoStyleChange") is true
+PASS testStyleChangeType("inert", "NoStyleChange") is true
+PASS hasExpectedStyle is true
+PASS successfullyParsed is true
+
+TEST COMPLETE
+
diff --git a/LayoutTests/fast/css/style-invalidation-attribute-change-descendants.html b/LayoutTests/fast/css/style-invalidation-attribute-change-descendants.html
new file mode 100644 (file)
index 0000000..cea62dc
--- /dev/null
@@ -0,0 +1,242 @@
+<!DOCTYPE html>
+<html>
+<head>
+<script src="../../resources/js-test-pre.js"></script>
+<style>
+* {
+    color: black;
+}
+[myattr] target {
+    color: rgb(1, 0, 0);
+}
+
+[myattr=value2] > inert target {
+    color: rgb(2, 0, 0);
+}
+
+[myattr~=value3] inert + target {
+    color: rgb(3, 0, 0);
+}
+
+[MYATTR|=value4] inert ~ target {
+    color: rgb(4, 0, 0);
+}
+
+[myAttr^=value5] target {
+    color: rgb(5, 0, 0);
+}
+
+[myattr$=value6] target {
+    color: rgb(6, 0, 0);
+}
+
+[myATTR*=value7] target {
+    color: rgb(7, 0, 0);
+}
+
+[myattr*=vaLUE8 i] target {
+    color: rgb(8, 0, 0);
+}
+
+[myattr2] > target {
+    color: rgb(9, 0, 0);
+}
+
+</style>
+</head>
+<body>
+    <root>
+        <!-- With renderer -->
+        <inert>
+            <inert>
+                <inert></inert>
+                <target>
+                    <inert></inert>
+                    <target></target>
+                </target>
+            </inert>
+            <target></target>
+            <inert></inert>
+        </inert>
+    </root>
+    <root style="display:none;">
+        <!-- Without renderer -->
+        <inert>
+            <inert>
+                <inert></inert>
+                <target>
+                    <inert></inert>
+                    <target></target>
+                </target>
+            </inert>
+            <target></target>
+            <inert></inert>
+        </inert>
+    </root>
+</body>
+<script>
+
+description('Test that we invalidate the element subtree minimally on class attribute change');
+
+function testStyleChangeType(tag, type)
+{
+    var elements = document.querySelectorAll(tag);
+    for (var i = 0; i < elements.length; ++i) {
+        if (window.internals.styleChangeType(elements[i]) != type)
+            return false;
+    }
+    return true;
+}
+
+function testStyleInvalidation(expectedDescendantStyleChange) {
+    // Ideally we would't invalidate the root at all.
+    shouldBeTrue('testStyleChangeType("root", "NoStyleChange") || testStyleChangeType("root", "InlineStyleChange")');
+
+    shouldBeTrue('testStyleChangeType("target", "' + expectedDescendantStyleChange +'")');
+
+    shouldBeTrue('testStyleChangeType("inert", "NoStyleChange")');
+}
+
+function setAttribute(name, value) {
+    debug("Setting attribute '" + name + "' value '" + value + "'");
+    var allRoots = document.querySelectorAll("root");
+    allRoots[0].setAttribute(name, value);
+    allRoots[1].setAttribute(name, value);
+}
+
+function removeAttribute(name) {
+    debug("Removing attribute '" + name + "'");
+    var allRoots = document.querySelectorAll("root");
+    allRoots[0].removeAttribute(name);
+    allRoots[1].removeAttribute(name);
+}
+
+function checkStyle(n) {
+    document.documentElement.offsetTop;
+
+    hasExpectedStyle = true;
+    expectedColor = 'rgb('+n+', 0, 0)';
+    var targets = document.querySelectorAll("target");
+    for (var i = 0; i < targets.length; ++i) {
+        hasExpectedStyle = getComputedStyle(targets[i]).color == expectedColor;
+        if (!hasExpectedStyle)
+            break;
+    }
+    shouldBeTrue("hasExpectedStyle");
+}
+
+checkStyle(0);
+testStyleInvalidation("NoStyleChange");
+checkStyle(0);
+
+setAttribute('myattr', '');
+testStyleInvalidation("InlineStyleChange");
+checkStyle(1);
+
+setAttribute('myattr', 'foo');
+testStyleInvalidation("NoStyleChange");
+checkStyle(1);
+
+setAttribute('myattr', 'value2');
+testStyleInvalidation("InlineStyleChange");
+checkStyle(2);
+
+setAttribute('myattr', 'foo');
+testStyleInvalidation("InlineStyleChange");
+checkStyle(1);
+
+removeAttribute('myattr');
+testStyleInvalidation("InlineStyleChange");
+checkStyle(0);
+
+setAttribute('myattr', 'value3');
+testStyleInvalidation("InlineStyleChange");
+checkStyle(3);
+
+setAttribute('myattr', 'dummy value3');
+testStyleInvalidation("NoStyleChange");
+checkStyle(3);
+
+setAttribute('myattr', 'value4-foo');
+testStyleInvalidation("InlineStyleChange");
+checkStyle(4);
+
+setAttribute('myattr', 'value4-foobar');
+testStyleInvalidation("NoStyleChange");
+checkStyle(4);
+
+setAttribute('myattr', 'dummy value4-foo');
+testStyleInvalidation("InlineStyleChange");
+checkStyle(1);
+
+setAttribute('myattr', 'value5foo');
+testStyleInvalidation("InlineStyleChange");
+checkStyle(5);
+
+setAttribute('myattr', 'value5foobar');
+testStyleInvalidation("NoStyleChange");
+checkStyle(5);
+
+setAttribute('myattr', 'foovalue5');
+testStyleInvalidation("InlineStyleChange");
+checkStyle(1);
+
+setAttribute('myattr', 'foovalue6');
+testStyleInvalidation("InlineStyleChange");
+checkStyle(6);
+
+setAttribute('myAttr', 'foobarvalue6');
+testStyleInvalidation("NoStyleChange");
+checkStyle(6);
+
+setAttribute('MYATTR', 'value6foo');
+testStyleInvalidation("InlineStyleChange");
+checkStyle(1);
+
+setAttribute('myattr', 'value7');
+testStyleInvalidation("InlineStyleChange");
+checkStyle(7);
+
+setAttribute('myattr', 'value7foo');
+testStyleInvalidation("NoStyleChange");
+checkStyle(7);
+
+setAttribute('myATTR', 'foovalue7foo');
+testStyleInvalidation("NoStyleChange");
+checkStyle(7);
+
+setAttribute('myattr', 'VALUE7foo');
+testStyleInvalidation("InlineStyleChange");
+checkStyle(1);
+
+setAttribute('myattr', 'Value8');
+testStyleInvalidation("InlineStyleChange");
+checkStyle(8);
+
+setAttribute('myattr', 'value8foo');
+testStyleInvalidation("NoStyleChange");
+checkStyle(8);
+
+setAttribute('myATTR', 'FOOVALue8foo');
+testStyleInvalidation("NoStyleChange");
+checkStyle(8);
+
+setAttribute('myattr', 'VALUE8foo');
+testStyleInvalidation("NoStyleChange");
+checkStyle(8);
+
+setAttribute('myattr', 'VALUE 8foo');
+testStyleInvalidation("InlineStyleChange");
+checkStyle(1);
+
+setAttribute('myattr2', '');
+testStyleInvalidation("NoStyleChange");
+checkStyle(1);
+
+setAttribute('myattr2', 'foo');
+testStyleInvalidation("NoStyleChange");
+checkStyle(1);
+
+</script>
+<script src="../../resources/js-test-post.js"></script>
+</html>
index d65156e..b1df96f 100644 (file)
@@ -2630,6 +2630,7 @@ set(WebCore_SOURCES
     storage/StorageMap.cpp
     storage/StorageNamespaceProvider.cpp
 
+    style/AttributeChangeInvalidation.cpp
     style/ClassChangeInvalidation.cpp
     style/InlineTextBoxStyle.cpp
     style/RenderTreePosition.cpp
index 89cc223..2d5b1cc 100644 (file)
@@ -1,3 +1,90 @@
+2016-02-15  Antti Koivisto  <antti@apple.com>
+
+        Optimize style invalidations for attribute selectors
+        https://bugs.webkit.org/show_bug.cgi?id=154242
+
+        Reviewed by Andreas Kling.
+
+        Currently we invalidate the whole element subtree if there are any attribute selectors for the changed attribute.
+        This is slow as generally few if any elements are really affected. Using attribute selectors for dynamic styling
+        should be performant.
+
+        This patch implements optimization strategy for attributes similar to what we already have for classes:
+
+        - Collect a map of all rules that contains descendant-affecting attribute selectors for a given attribute.
+        - When an attribute value changes check if there are any such rules for it.
+        - Check if the value change affects the results of any of the attribute selectors.
+        - Only if it does invalidate the exact descendant elements affected by the rules.
+
+        Test: fast/css/style-invalidation-attribute-change-descendants.html
+
+        * WebCore.xcodeproj/project.pbxproj:
+        * css/DocumentRuleSets.cpp:
+        (WebCore::DocumentRuleSets::ancestorClassRules):
+        (WebCore::DocumentRuleSets::ancestorAttributeRulesForHTML):
+
+            Create optimization RuleSets when needed.
+
+        * css/DocumentRuleSets.h:
+        (WebCore::DocumentRuleSets::uncommonAttribute):
+        (WebCore::DocumentRuleSets::features):
+        * css/RuleFeature.cpp:
+        (WebCore::RuleFeatureSet::recursivelyCollectFeaturesFromSelector):
+        (WebCore::makeAttributeSelectorKey):
+        (WebCore::RuleFeatureSet::collectFeatures):
+
+            Collect rules with descendant affecting attribute selectors.
+
+        (WebCore::RuleFeatureSet::add):
+        (WebCore::RuleFeatureSet::clear):
+        (WebCore::RuleFeatureSet::shrinkToFit):
+        * css/RuleFeature.h:
+        * css/SelectorChecker.cpp:
+        (WebCore::anyAttributeMatches):
+        (WebCore::SelectorChecker::attributeSelectorMatches):
+
+            Expose function for matching single attribute selectors.
+
+        (WebCore::canMatchHoverOrActiveInQuirksMode):
+        * css/SelectorChecker.h:
+        * dom/Attr.cpp:
+        (WebCore::Attr::setValue):
+        (WebCore::Attr::childrenChanged):
+        * dom/Element.cpp:
+        (WebCore::Element::setAttributeInternal):
+        (WebCore::makeIdForStyleResolution):
+        (WebCore::Element::attributeChanged):
+        (WebCore::Element::removeAttributeInternal):
+        (WebCore::Element::addAttributeInternal):
+        (WebCore::Element::removeAttribute):
+
+            Add AttributeChangeInvalidation where needed.
+
+        (WebCore::Element::needsStyleInvalidation):
+
+            Move to Element from ClassChangeInvalidation.
+
+        (WebCore::Element::willModifyAttribute):
+
+            No more full style invalidation on attribute change.
+
+        * style/AttributeChangeInvalidation.cpp: Added.
+        (WebCore::Style::AttributeChangeInvalidation::invalidateStyle):
+
+            Invalidate local style.
+            Check if we need to invalidate descendants by looking into ancestorAttributeRules.
+
+        (WebCore::Style::AttributeChangeInvalidation::invalidateDescendants):
+
+            Use StyleInvalidationAnalysis to invalidate the subtree for the relevant rules.
+
+        * style/AttributeChangeInvalidation.h: Added.
+        (WebCore::Style::AttributeChangeInvalidation::needsInvalidation):
+        (WebCore::Style::AttributeChangeInvalidation::AttributeChangeInvalidation):
+        (WebCore::Style::AttributeChangeInvalidation::~AttributeChangeInvalidation):
+
+            If needed, invalidate descendants before and after attribute change to catch rules that start and stop applying.
+
 2016-02-16  Chris Dumez  <cdumez@apple.com>
 
         Do security checks early in JSDOMWindow::put*()
index ff8c902..3a3c1e4 100644 (file)
     <ClCompile Include="..\storage\StorageEventDispatcher.cpp" />
     <ClCompile Include="..\storage\StorageMap.cpp" />
     <ClCompile Include="..\storage\StorageNamespaceProvider.cpp" />
+    <ClCompile Include="..\style\AttributeChangeInvalidation.cpp" />
     <ClCompile Include="..\style\ClassChangeInvalidation.cpp" />
     <ClCompile Include="..\style\InlineTextBoxStyle.cpp" />
     <ClCompile Include="..\style\RenderTreePosition.cpp" />
     <ClInclude Include="..\storage\StorageMap.h" />
     <ClInclude Include="..\storage\StorageNamespace.h" />
     <ClInclude Include="..\storage\StorageNamespaceProvider.h" />
+    <ClInclude Include="..\style\AttributeChangeInvalidation.h" />
     <ClInclude Include="..\style\ClassChangeInvalidation.h" />
     <ClInclude Include="..\style\InlineTextBoxStyle.h" />
     <ClInclude Include="..\style\RenderTreePosition.h" />
index 58aa57d..98d0041 100644 (file)
                E4A007851B820ED3002C5A6E /* DataURLDecoder.cpp in Sources */ = {isa = PBXBuildFile; fileRef = E4A007841B820ED3002C5A6E /* DataURLDecoder.cpp */; };
                E4A814D41C6DEC4000BF85AC /* ClassChangeInvalidation.h in Headers */ = {isa = PBXBuildFile; fileRef = E4A814D31C6DEC4000BF85AC /* ClassChangeInvalidation.h */; };
                E4A814D61C6DEE8D00BF85AC /* ClassChangeInvalidation.cpp in Sources */ = {isa = PBXBuildFile; fileRef = E4A814D51C6DEE8D00BF85AC /* ClassChangeInvalidation.cpp */; };
+               E4A814D81C70E10500BF85AC /* AttributeChangeInvalidation.cpp in Sources */ = {isa = PBXBuildFile; fileRef = E4A814D71C70E10500BF85AC /* AttributeChangeInvalidation.cpp */; };
+               E4A814DA1C70E10D00BF85AC /* AttributeChangeInvalidation.h in Headers */ = {isa = PBXBuildFile; fileRef = E4A814D91C70E10D00BF85AC /* AttributeChangeInvalidation.h */; };
                E4AE7C1617D1BB950009FB31 /* ElementIterator.h in Headers */ = {isa = PBXBuildFile; fileRef = E4AE7C1517D1BB950009FB31 /* ElementIterator.h */; settings = {ATTRIBUTES = (Private, ); }; };
                E4AE7C1A17D232350009FB31 /* ElementAncestorIterator.h in Headers */ = {isa = PBXBuildFile; fileRef = E4AE7C1917D232350009FB31 /* ElementAncestorIterator.h */; settings = {ATTRIBUTES = (Private, ); }; };
                E4AFCFA50DAF29A300F5F55C /* UnitBezier.h in Headers */ = {isa = PBXBuildFile; fileRef = E4AFCFA40DAF29A300F5F55C /* UnitBezier.h */; };
                E4A007841B820ED3002C5A6E /* DataURLDecoder.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = DataURLDecoder.cpp; sourceTree = "<group>"; };
                E4A814D31C6DEC4000BF85AC /* ClassChangeInvalidation.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ClassChangeInvalidation.h; sourceTree = "<group>"; };
                E4A814D51C6DEE8D00BF85AC /* ClassChangeInvalidation.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = ClassChangeInvalidation.cpp; sourceTree = "<group>"; };
+               E4A814D71C70E10500BF85AC /* AttributeChangeInvalidation.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = AttributeChangeInvalidation.cpp; sourceTree = "<group>"; };
+               E4A814D91C70E10D00BF85AC /* AttributeChangeInvalidation.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AttributeChangeInvalidation.h; sourceTree = "<group>"; };
                E4AE7C1517D1BB950009FB31 /* ElementIterator.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ElementIterator.h; sourceTree = "<group>"; };
                E4AE7C1917D232350009FB31 /* ElementAncestorIterator.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ElementAncestorIterator.h; sourceTree = "<group>"; };
                E4AFCFA40DAF29A300F5F55C /* UnitBezier.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = UnitBezier.h; sourceTree = "<group>"; };
                E4763D4A17B2704900D35206 /* style */ = {
                        isa = PBXGroup;
                        children = (
+                               E4A814D71C70E10500BF85AC /* AttributeChangeInvalidation.cpp */,
+                               E4A814D91C70E10D00BF85AC /* AttributeChangeInvalidation.h */,
                                E4A814D51C6DEE8D00BF85AC /* ClassChangeInvalidation.cpp */,
                                E4A814D31C6DEC4000BF85AC /* ClassChangeInvalidation.h */,
                                1C0106FE192594DF008A4201 /* InlineTextBoxStyle.cpp */,
                                31313F661443B35F006E2A90 /* FilterEffectRenderer.h in Headers */,
                                49ECEB6E1499790D00CDD3A4 /* FilterOperation.h in Headers */,
                                49ECEB701499790D00CDD3A4 /* FilterOperations.h in Headers */,
+                               E4A814DA1C70E10D00BF85AC /* AttributeChangeInvalidation.h in Headers */,
                                372C00D9129619F8005C9575 /* FindOptions.h in Headers */,
                                A8CFF04F0A154F09000A4234 /* FixedTableLayout.h in Headers */,
                                BC073BAA0C399B1F000F5979 /* FloatConversion.h in Headers */,
                                B59DD6AA11902A71007E9684 /* JSSQLStatementErrorCallback.cpp in Sources */,
                                9BD4E9161C462872005065BC /* JSCustomElementInterface.cpp in Sources */,
                                514C76380CE9225E007EF3CD /* JSSQLTransaction.cpp in Sources */,
+                               E4A814D81C70E10500BF85AC /* AttributeChangeInvalidation.cpp in Sources */,
                                B59DD69E11902A42007E9684 /* JSSQLTransactionCallback.cpp in Sources */,
                                1AD2316E0CD269E700C1F194 /* JSSQLTransactionCustom.cpp in Sources */,
                                B59DD6A211902A52007E9684 /* JSSQLTransactionErrorCallback.cpp in Sources */,
index a2c63ff..9cc08de 100644 (file)
@@ -118,7 +118,7 @@ void DocumentRuleSets::collectFeatures() const
 
 RuleSet* DocumentRuleSets::ancestorClassRules(AtomicStringImpl* className) const
 {
-    auto addResult = m_ancestorClassRuleSet.add(className, nullptr);
+    auto addResult = m_ancestorClassRuleSets.add(className, nullptr);
     if (addResult.isNewEntry) {
         if (auto* rules = m_features.ancestorClassRules.get(className))
             addResult.iterator->value = makeRuleSet(*rules);
@@ -126,4 +126,20 @@ RuleSet* DocumentRuleSets::ancestorClassRules(AtomicStringImpl* className) const
     return addResult.iterator->value.get();
 }
 
+const DocumentRuleSets::AttributeRules* DocumentRuleSets::ancestorAttributeRulesForHTML(AtomicStringImpl* attributeName) const
+{
+    auto addResult = m_ancestorAttributeRuleSetsForHTML.add(attributeName, nullptr);
+    auto& value = addResult.iterator->value;
+    if (addResult.isNewEntry) {
+        if (auto* rules = m_features.ancestorAttributeRulesForHTML.get(attributeName)) {
+            value = std::make_unique<AttributeRules>();
+            value->attributeSelectors.reserveCapacity(rules->selectors.size());
+            for (auto* selector : rules->selectors.values())
+                value->attributeSelectors.uncheckedAppend(selector);
+            value->ruleSet = makeRuleSet(rules->features);
+        }
+    }
+    return value.get();
+}
+
 } // namespace WebCore
index acd15ca..1f4db52 100644 (file)
@@ -52,6 +52,12 @@ public:
     RuleSet* uncommonAttribute() const { return m_uncommonAttributeRuleSet.get(); }
     RuleSet* ancestorClassRules(AtomicStringImpl* className) const;
 
+    struct AttributeRules {
+        Vector<const CSSSelector*> attributeSelectors;
+        std::unique_ptr<RuleSet> ruleSet;
+    };
+    const AttributeRules* ancestorAttributeRulesForHTML(AtomicStringImpl*) const;
+
     void initUserStyle(ExtensionStyleSheets&, const MediaQueryEvaluator&, StyleResolver&);
     void resetAuthorStyle();
     void appendAuthorStyleSheets(const Vector<RefPtr<CSSStyleSheet>>&, MediaQueryEvaluator*, InspectorCSSOMWrappers&, StyleResolver*);
@@ -67,7 +73,8 @@ private:
     mutable unsigned m_defaultStyleVersionOnFeatureCollection { 0 };
     mutable std::unique_ptr<RuleSet> m_siblingRuleSet;
     mutable std::unique_ptr<RuleSet> m_uncommonAttributeRuleSet;
-    mutable HashMap<AtomicStringImpl*, std::unique_ptr<RuleSet>> m_ancestorClassRuleSet;
+    mutable HashMap<AtomicStringImpl*, std::unique_ptr<RuleSet>> m_ancestorClassRuleSets;
+    mutable HashMap<AtomicStringImpl*, std::unique_ptr<AttributeRules>> m_ancestorAttributeRuleSetsForHTML;
 };
 
 inline const RuleFeatureSet& DocumentRuleSets::features() const
index 7903e9a..7db0161 100644 (file)
@@ -46,8 +46,12 @@ void RuleFeatureSet::recursivelyCollectFeaturesFromSelector(SelectorFeatures& se
             if (matchesAncestor)
                 selectorFeatures.classesMatchingAncestors.append(selector->value().impl());
         } else if (selector->isAttributeSelector()) {
-            attributeCanonicalLocalNamesInRules.add(selector->attributeCanonicalLocalName().impl());
-            attributeLocalNamesInRules.add(selector->attribute().localName().impl());
+            auto* canonicalLocalName = selector->attributeCanonicalLocalName().impl();
+            auto* localName = selector->attribute().localName().impl();
+            attributeCanonicalLocalNamesInRules.add(canonicalLocalName);
+            attributeLocalNamesInRules.add(localName);
+            if (matchesAncestor)
+                selectorFeatures.attributeSelectorsMatchingAncestors.append(selector);
         } else if (selector->match() == CSSSelector::PseudoElement) {
             switch (selector->pseudoElementType()) {
             case CSSSelector::PseudoElementFirstLine:
@@ -78,6 +82,13 @@ void RuleFeatureSet::recursivelyCollectFeaturesFromSelector(SelectorFeatures& se
     } while (selector);
 }
 
+static std::pair<AtomicStringImpl*, unsigned> makeAttributeSelectorKey(const CSSSelector& selector)
+{
+    bool caseInsensitive = selector.attributeValueMatchingIsCaseInsensitive();
+    unsigned matchAndCase = static_cast<unsigned>(selector.match()) << 1 | caseInsensitive;
+    return std::make_pair(selector.attributeCanonicalLocalName().impl(), matchAndCase);
+}
+
 void RuleFeatureSet::collectFeatures(const RuleData& ruleData)
 {
     SelectorFeatures selectorFeatures;
@@ -92,6 +103,16 @@ void RuleFeatureSet::collectFeatures(const RuleData& ruleData)
             addResult.iterator->value = std::make_unique<Vector<RuleFeature>>();
         addResult.iterator->value->append(RuleFeature(ruleData.rule(), ruleData.selectorIndex(), ruleData.hasDocumentSecurityOrigin()));
     }
+    for (auto* selector : selectorFeatures.attributeSelectorsMatchingAncestors) {
+        // Hashing by attributeCanonicalLocalName makes this HTML specific.
+        auto addResult = ancestorAttributeRulesForHTML.add(selector->attributeCanonicalLocalName().impl(), nullptr);
+        if (addResult.isNewEntry)
+            addResult.iterator->value = std::make_unique<AttributeRules>();
+        auto& rules = *addResult.iterator->value;
+        rules.features.append(RuleFeature(ruleData.rule(), ruleData.selectorIndex(), ruleData.hasDocumentSecurityOrigin()));
+        // Deduplicate selectors.
+        rules.selectors.add(makeAttributeSelectorKey(*selector), selector);
+    }
 }
 
 void RuleFeatureSet::add(const RuleFeatureSet& other)
@@ -109,6 +130,15 @@ void RuleFeatureSet::add(const RuleFeatureSet& other)
         else
             addResult.iterator->value->appendVector(*keyValuePair.value);
     }
+    for (auto& keyValuePair : other.ancestorAttributeRulesForHTML) {
+        auto addResult = ancestorAttributeRulesForHTML.add(keyValuePair.key, nullptr);
+        if (addResult.isNewEntry)
+            addResult.iterator->value = std::make_unique<AttributeRules>();
+        auto& rules = *addResult.iterator->value;
+        rules.features.appendVector(keyValuePair.value->features);
+        for (auto& selectorPair : keyValuePair.value->selectors)
+            rules.selectors.add(selectorPair.key, selectorPair.value);
+    }
     usesFirstLineRules = usesFirstLineRules || other.usesFirstLineRules;
     usesFirstLetterRules = usesFirstLetterRules || other.usesFirstLetterRules;
 }
@@ -122,6 +152,7 @@ void RuleFeatureSet::clear()
     siblingRules.clear();
     uncommonAttributeRules.clear();
     ancestorClassRules.clear();
+    ancestorAttributeRulesForHTML.clear();
     usesFirstLineRules = false;
     usesFirstLetterRules = false;
 }
@@ -132,6 +163,8 @@ void RuleFeatureSet::shrinkToFit()
     uncommonAttributeRules.shrinkToFit();
     for (auto& rules : ancestorClassRules.values())
         rules->shrinkToFit();
+    for (auto& rules : ancestorAttributeRulesForHTML.values())
+        rules->features.shrinkToFit();
 }
 
 } // namespace WebCore
index fd6175e..a084ec3 100644 (file)
@@ -22,6 +22,7 @@
 #ifndef RuleFeature_h
 #define RuleFeature_h
 
+#include "CSSSelector.h"
 #include <wtf/Forward.h>
 #include <wtf/HashMap.h>
 #include <wtf/HashSet.h>
@@ -29,7 +30,6 @@
 
 namespace WebCore {
 
-class CSSSelector;
 class RuleData;
 class StyleRule;
 
@@ -58,6 +58,12 @@ struct RuleFeatureSet {
     Vector<RuleFeature> siblingRules;
     Vector<RuleFeature> uncommonAttributeRules;
     HashMap<AtomicStringImpl*, std::unique_ptr<Vector<RuleFeature>>> ancestorClassRules;
+
+    struct AttributeRules {
+        HashMap<std::pair<AtomicStringImpl*, unsigned>, const CSSSelector*> selectors;
+        Vector<RuleFeature> features;
+    };
+    HashMap<AtomicStringImpl*, std::unique_ptr<AttributeRules>> ancestorAttributeRulesForHTML;
     bool usesFirstLineRules { false };
     bool usesFirstLetterRules { false };
 
@@ -65,6 +71,7 @@ private:
     struct SelectorFeatures {
         bool hasSiblingSelector { false };
         Vector<AtomicStringImpl*> classesMatchingAncestors;
+        Vector<const CSSSelector*> attributeSelectorsMatchingAncestors;
     };
     void recursivelyCollectFeaturesFromSelector(SelectorFeatures&, const CSSSelector&, bool matchesAncestor = false);
 };
index 8952922..0f0d365 100644 (file)
@@ -518,6 +518,21 @@ static bool anyAttributeMatches(const Element& element, const CSSSelector& selec
     return false;
 }
 
+bool SelectorChecker::attributeSelectorMatches(const Element& element, const QualifiedName& attributeName, const AtomicString& attributeValue, const CSSSelector& selector)
+{
+    ASSERT(selector.isAttributeSelector());
+    auto& selectorAttribute = selector.attribute();
+    auto& selectorName = element.isHTMLElement() ? selector.attributeCanonicalLocalName() : selectorAttribute.localName();
+    if (!Attribute::nameMatchesFilter(attributeName, selectorAttribute.prefix(), selectorName, selectorAttribute.namespaceURI()))
+        return false;
+    bool caseSensitive = true;
+    if (selector.attributeValueMatchingIsCaseInsensitive())
+        caseSensitive = false;
+    else if (element.document().isHTMLDocument() && element.isHTMLElement() && !HTMLDocument::isCaseSensitiveAttribute(selector.attribute()))
+        caseSensitive = false;
+    return attributeValueMatches(Attribute(attributeName, attributeValue), selector.match(), selector.value(), caseSensitive);
+}
+
 static bool canMatchHoverOrActiveInQuirksMode(const SelectorChecker::LocalContext& context)
 {
     // For quirks mode, follow this: http://quirks.spec.whatwg.org/#the-:active-and-:hover-quirk
index 6188b21..eb209f9 100644 (file)
@@ -119,6 +119,7 @@ public:
 
     static bool isCommonPseudoClassSelector(const CSSSelector*);
     static bool matchesFocusPseudoClass(const Element&);
+    static bool attributeSelectorMatches(const Element&, const QualifiedName&, const AtomicString& attributeValue, const CSSSelector&);
 
     enum LinkMatchMask { MatchDefault = 0, MatchLink = 1, MatchVisited = 2, MatchAll = MatchLink | MatchVisited };
     static unsigned determineLinkMatchType(const CSSSelector*);
index 4a8dd43..d3b8e28 100644 (file)
@@ -23,6 +23,7 @@
 #include "config.h"
 #include "Attr.h"
 
+#include "AttributeChangeInvalidation.h"
 #include "Event.h"
 #include "ExceptionCode.h"
 #include "ScopedEventQueue.h"
@@ -108,9 +109,10 @@ void Attr::setValue(const AtomicString& value)
     EventQueueScope scope;
     m_ignoreChildrenChanged++;
     removeChildren();
-    if (m_element)
+    if (m_element) {
+        Style::AttributeChangeInvalidation styleInvalidation(*m_element, qualifiedName(), elementAttribute().value(), value);
         elementAttribute().setValue(value);
-    else
+    else
         m_standaloneValue = value;
     createTextChild();
     m_ignoreChildrenChanged--;
@@ -163,9 +165,10 @@ void Attr::childrenChanged(const ChildChange&)
     if (m_element)
         m_element->willModifyAttribute(qualifiedName(), oldValue, newValue);
 
-    if (m_element)
+    if (m_element) {
+        Style::AttributeChangeInvalidation styleInvalidation(*m_element, qualifiedName(), oldValue, newValue);
         elementAttribute().setValue(newValue);
-    else
+    else
         m_standaloneValue = newValue;
 
     if (m_element)
index 59fe486..efe57fa 100644 (file)
@@ -28,6 +28,7 @@
 
 #include "AXObjectCache.h"
 #include "Attr.h"
+#include "AttributeChangeInvalidation.h"
 #include "CSSParser.h"
 #include "Chrome.h"
 #include "ChromeClient.h"
@@ -1182,26 +1183,30 @@ inline void Element::setAttributeInternal(unsigned index, const QualifiedName& n
         return;
     }
 
+    if (inSynchronizationOfLazyAttribute) {
+        ensureUniqueElementData().attributeAt(index).setValue(newValue);
+        return;
+    }
+
     const Attribute& attribute = attributeAt(index);
+    QualifiedName attributeName = attribute.name();
     AtomicString oldValue = attribute.value();
-    bool valueChanged = newValue != oldValue;
-    QualifiedName attributeName = (!inSynchronizationOfLazyAttribute || valueChanged) ? attribute.name() : name;
 
-    if (!inSynchronizationOfLazyAttribute)
-        willModifyAttribute(attributeName, oldValue, newValue);
+    willModifyAttribute(attributeName, oldValue, newValue);
 
-    if (valueChanged) {
+    if (newValue != oldValue) {
         // If there is an Attr node hooked to this attribute, the Attr::setValue() call below
         // will write into the ElementData.
         // FIXME: Refactor this so it makes some sense.
-        if (RefPtr<Attr> attrNode = inSynchronizationOfLazyAttribute ? nullptr : attrIfExists(attributeName))
+        if (RefPtr<Attr> attrNode = attrIfExists(attributeName))
             attrNode->setValue(newValue);
-        else
+        else {
+            Style::AttributeChangeInvalidation styleInvalidation(*this, name, oldValue, newValue);
             ensureUniqueElementData().attributeAt(index).setValue(newValue);
+        }
     }
 
-    if (!inSynchronizationOfLazyAttribute)
-        didModifyAttribute(attributeName, oldValue, newValue);
+    didModifyAttribute(attributeName, oldValue, newValue);
 }
 
 static inline AtomicString makeIdForStyleResolution(const AtomicString& value, bool inQuirksMode)
@@ -1268,9 +1273,6 @@ void Element::attributeChanged(const QualifiedName& name, const AtomicString& ol
 
     invalidateNodeListAndCollectionCachesInAncestors(&name, this);
 
-    // If there is currently no StyleResolver, we can't be sure that this attribute change won't affect style.
-    shouldInvalidateStyle |= !styleResolver;
-
     if (shouldInvalidateStyle)
         setNeedsStyleRecalc();
 
@@ -2050,27 +2052,38 @@ void Element::removeAttributeInternal(unsigned index, SynchronizationOfLazyAttri
     QualifiedName name = elementData.attributeAt(index).name();
     AtomicString valueBeingRemoved = elementData.attributeAt(index).value();
 
-    if (!inSynchronizationOfLazyAttribute) {
-        if (!valueBeingRemoved.isNull())
-            willModifyAttribute(name, valueBeingRemoved, nullAtom);
-    }
-
     if (RefPtr<Attr> attrNode = attrIfExists(name))
         detachAttrNodeFromElementWithValue(attrNode.get(), elementData.attributeAt(index).value());
 
-    elementData.removeAttribute(index);
+    if (inSynchronizationOfLazyAttribute) {
+        elementData.removeAttribute(index);
+        return;
+    }
+
+    if (!valueBeingRemoved.isNull())
+        willModifyAttribute(name, valueBeingRemoved, nullAtom);
 
-    if (!inSynchronizationOfLazyAttribute)
-        didRemoveAttribute(name, valueBeingRemoved);
+    {
+        Style::AttributeChangeInvalidation styleInvalidation(*this, name, valueBeingRemoved, nullAtom);
+        elementData.removeAttribute(index);
+    }
+
+    didRemoveAttribute(name, valueBeingRemoved);
 }
 
 void Element::addAttributeInternal(const QualifiedName& name, const AtomicString& value, SynchronizationOfLazyAttribute inSynchronizationOfLazyAttribute)
 {
-    if (!inSynchronizationOfLazyAttribute)
-        willModifyAttribute(name, nullAtom, value);
-    ensureUniqueElementData().addAttribute(name, value);
-    if (!inSynchronizationOfLazyAttribute)
-        didAddAttribute(name, value);
+    if (inSynchronizationOfLazyAttribute) {
+        ensureUniqueElementData().addAttribute(name, value);
+        return;
+    }
+
+    willModifyAttribute(name, nullAtom, value);
+    {
+        Style::AttributeChangeInvalidation styleInvalidation(*this, name, nullAtom, value);
+        ensureUniqueElementData().addAttribute(name, value);
+    }
+    didAddAttribute(name, value);
 }
 
 bool Element::removeAttribute(const AtomicString& name)
@@ -2485,6 +2498,18 @@ RenderStyle* Element::computedStyle(PseudoId pseudoElementSpecifier)
     return style;
 }
 
+bool Element::needsStyleInvalidation() const
+{
+    if (!inRenderedDocument())
+        return false;
+    if (styleChangeType() >= FullStyleChange)
+        return false;
+    if (!document().styleResolverIfExists())
+        return false;
+
+    return true;
+}
+
 void Element::setStyleAffectedByEmpty()
 {
     ensureElementRareData().setStyleAffectedByEmpty(true);
@@ -3080,12 +3105,6 @@ void Element::willModifyAttribute(const QualifiedName& name, const AtomicString&
             updateLabel(treeScope(), oldValue, newValue);
     }
 
-    if (oldValue != newValue) {
-        auto styleResolver = document().styleResolverIfExists();
-        if (styleResolver && styleResolver->hasSelectorForAttribute(*this, name.localName()))
-            setNeedsStyleRecalc();
-    }
-
     if (std::unique_ptr<MutationObserverInterestGroup> recipients = MutationObserverInterestGroup::createForAttributesMutation(*this, name))
         recipients->enqueueMutationRecord(MutationRecord::createAttributes(*this, name, oldValue));
 
index bab05be..eb6fe94 100644 (file)
@@ -279,6 +279,8 @@ public:
 
     virtual RenderStyle* computedStyle(PseudoId = NOPSEUDO) override;
 
+    bool needsStyleInvalidation() const;
+
     // Methods for indicating the style is affected by dynamic updates (e.g., children changing, our position changing in our sibling list, etc.)
     bool styleAffectedByEmpty() const { return hasRareData() && rareDataStyleAffectedByEmpty(); }
     bool childrenAffectedByHover() const { return getFlag(ChildrenAffectedByHoverRulesFlag); }
diff --git a/Source/WebCore/style/AttributeChangeInvalidation.cpp b/Source/WebCore/style/AttributeChangeInvalidation.cpp
new file mode 100644 (file)
index 0000000..3479ab6
--- /dev/null
@@ -0,0 +1,90 @@
+/*
+ * Copyright (C) 2016 Apple Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "config.h"
+#include "AttributeChangeInvalidation.h"
+
+#include "DocumentRuleSets.h"
+#include "ElementIterator.h"
+#include "StyleInvalidationAnalysis.h"
+#include "StyleResolver.h"
+
+namespace WebCore {
+namespace Style {
+
+void AttributeChangeInvalidation::invalidateStyle(const QualifiedName& attributeName, const AtomicString& oldValue, const AtomicString& newValue)
+{
+    if (newValue == oldValue)
+        return;
+
+    auto& ruleSets = m_element.styleResolver().ruleSets();
+    bool isHTML = m_element.isHTMLElement();
+
+    auto& nameSet = isHTML ? ruleSets.features().attributeCanonicalLocalNamesInRules : ruleSets.features().attributeLocalNamesInRules;
+    bool shouldInvalidate = nameSet.contains(attributeName.localName().impl());
+    if (!shouldInvalidate)
+        return;
+
+    if (!isHTML) {
+        m_element.setNeedsStyleRecalc(FullStyleChange);
+        return;
+    }
+
+    if (m_element.shadowRoot() && ruleSets.authorStyle()->hasShadowPseudoElementRules()) {
+        m_element.setNeedsStyleRecalc(FullStyleChange);
+        return;
+    }
+
+    m_element.setNeedsStyleRecalc(InlineStyleChange);
+
+    if (!childrenOfType<Element>(m_element).first())
+        return;
+
+    auto* attributeRules = ruleSets.ancestorAttributeRulesForHTML(attributeName.localName().impl());
+    if (!attributeRules)
+        return;
+
+    // Check if descendants may be affected by this attribute change.
+    for (auto* selector : attributeRules->attributeSelectors) {
+        bool oldMatches = oldValue.isNull() ? false : SelectorChecker::attributeSelectorMatches(m_element, attributeName, oldValue, *selector);
+        bool newMatches = newValue.isNull() ? false : SelectorChecker::attributeSelectorMatches(m_element, attributeName, newValue, *selector);
+
+        if (oldMatches != newMatches) {
+            m_descendantInvalidationRuleSet = attributeRules->ruleSet.get();
+            return;
+        }
+    }
+}
+
+void AttributeChangeInvalidation::invalidateDescendants()
+{
+    if (!m_descendantInvalidationRuleSet)
+        return;
+    StyleInvalidationAnalysis invalidationAnalysis(*m_descendantInvalidationRuleSet);
+    invalidationAnalysis.invalidateStyle(m_element);
+}
+
+}
+}
diff --git a/Source/WebCore/style/AttributeChangeInvalidation.h b/Source/WebCore/style/AttributeChangeInvalidation.h
new file mode 100644 (file)
index 0000000..caec5c0
--- /dev/null
@@ -0,0 +1,73 @@
+/*
+ * Copyright (C) 2016 Apple Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef AttributeChangeInvalidation_h
+#define AttributeChangeInvalidation_h
+
+#include "Element.h"
+
+namespace WebCore {
+
+class RuleSet;
+
+namespace Style {
+
+class AttributeChangeInvalidation {
+public:
+    AttributeChangeInvalidation(Element&, const QualifiedName&, const AtomicString& oldValue, const AtomicString& newValue);
+    ~AttributeChangeInvalidation();
+
+private:
+    void invalidateStyle(const QualifiedName&, const AtomicString& oldValue, const AtomicString& newValue);
+    void invalidateDescendants();
+
+    const bool m_isEnabled;
+    Element& m_element;
+
+    RuleSet* m_descendantInvalidationRuleSet { nullptr };
+};
+
+inline AttributeChangeInvalidation::AttributeChangeInvalidation(Element& element, const QualifiedName& attributeName, const AtomicString& oldValue, const AtomicString& newValue)
+    : m_isEnabled(element.needsStyleInvalidation())
+    , m_element(element)
+{
+    if (!m_isEnabled)
+        return;
+    invalidateStyle(attributeName, oldValue, newValue);
+    invalidateDescendants();
+}
+
+inline AttributeChangeInvalidation::~AttributeChangeInvalidation()
+{
+    if (!m_isEnabled)
+        return;
+    invalidateDescendants();
+}
+    
+}
+}
+
+#endif
+
index 6c95f92..b914fcf 100644 (file)
@@ -46,7 +46,6 @@ public:
 private:
     using ClassChangeVector = Vector<AtomicStringImpl*, 4>;
 
-    static bool needsInvalidation(const Element&);
     void computeClassChange(const SpaceSplitString& oldClasses, const SpaceSplitString& newClasses);
     void invalidateStyle(const ClassChangeVector&);
 
@@ -59,19 +58,8 @@ private:
     ClassChangeVector m_removedClasses;
 };
 
-inline bool ClassChangeInvalidation::needsInvalidation(const Element& element)
-{
-    if (!element.inRenderedDocument())
-        return false;
-    if (element.styleChangeType() >= FullStyleChange)
-        return false;
-    if (!element.document().styleResolverIfExists())
-        return false;
-    return true;
-}
-
 inline ClassChangeInvalidation::ClassChangeInvalidation(Element& element, const SpaceSplitString& oldClasses, const SpaceSplitString& newClasses)
-    : m_isEnabled(needsInvalidation(element))
+    : m_isEnabled(element.needsStyleInvalidation())
     , m_element(element)
 
 {