Title: [196629] trunk
Revision
196629
Author
[email protected]
Date
2016-02-16 00:20:58 -0800 (Tue, 16 Feb 2016)

Log Message

Optimize style invalidations for attribute selectors
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.

Modified Paths

Added Paths

Diff

Modified: trunk/LayoutTests/ChangeLog (196628 => 196629)


--- trunk/LayoutTests/ChangeLog	2016-02-16 08:11:47 UTC (rev 196628)
+++ trunk/LayoutTests/ChangeLog	2016-02-16 08:20:58 UTC (rev 196629)
@@ -1,3 +1,13 @@
+2016-02-15  Antti Koivisto  <[email protected]>
+
+        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  <[email protected]>
 
         Do security checks early in JSDOMWindow::put*()

Added: trunk/LayoutTests/fast/css/style-invalidation-attribute-change-descendants-expected.txt (0 => 196629)


--- trunk/LayoutTests/fast/css/style-invalidation-attribute-change-descendants-expected.txt	                        (rev 0)
+++ trunk/LayoutTests/fast/css/style-invalidation-attribute-change-descendants-expected.txt	2016-02-16 08:20:58 UTC (rev 196629)
@@ -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
+

Added: trunk/LayoutTests/fast/css/style-invalidation-attribute-change-descendants.html (0 => 196629)


--- trunk/LayoutTests/fast/css/style-invalidation-attribute-change-descendants.html	                        (rev 0)
+++ trunk/LayoutTests/fast/css/style-invalidation-attribute-change-descendants.html	2016-02-16 08:20:58 UTC (rev 196629)
@@ -0,0 +1,242 @@
+<!DOCTYPE html>
+<html>
+<head>
+<script src=""
+<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=""
+</html>

Modified: trunk/Source/WebCore/CMakeLists.txt (196628 => 196629)


--- trunk/Source/WebCore/CMakeLists.txt	2016-02-16 08:11:47 UTC (rev 196628)
+++ trunk/Source/WebCore/CMakeLists.txt	2016-02-16 08:20:58 UTC (rev 196629)
@@ -2630,6 +2630,7 @@
     storage/StorageMap.cpp
     storage/StorageNamespaceProvider.cpp
 
+    style/AttributeChangeInvalidation.cpp
     style/ClassChangeInvalidation.cpp
     style/InlineTextBoxStyle.cpp
     style/RenderTreePosition.cpp

Modified: trunk/Source/WebCore/ChangeLog (196628 => 196629)


--- trunk/Source/WebCore/ChangeLog	2016-02-16 08:11:47 UTC (rev 196628)
+++ trunk/Source/WebCore/ChangeLog	2016-02-16 08:20:58 UTC (rev 196629)
@@ -1,3 +1,90 @@
+2016-02-15  Antti Koivisto  <[email protected]>
+
+        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  <[email protected]>
 
         Do security checks early in JSDOMWindow::put*()

Modified: trunk/Source/WebCore/WebCore.vcxproj/WebCore.vcxproj (196628 => 196629)


--- trunk/Source/WebCore/WebCore.vcxproj/WebCore.vcxproj	2016-02-16 08:11:47 UTC (rev 196628)
+++ trunk/Source/WebCore/WebCore.vcxproj/WebCore.vcxproj	2016-02-16 08:20:58 UTC (rev 196629)
@@ -19295,6 +19295,7 @@
     <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" />
@@ -22865,6 +22866,7 @@
     <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" />

Modified: trunk/Source/WebCore/WebCore.xcodeproj/project.pbxproj (196628 => 196629)


--- trunk/Source/WebCore/WebCore.xcodeproj/project.pbxproj	2016-02-16 08:11:47 UTC (rev 196628)
+++ trunk/Source/WebCore/WebCore.xcodeproj/project.pbxproj	2016-02-16 08:20:58 UTC (rev 196629)
@@ -6637,6 +6637,8 @@
 		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 */; };
@@ -14643,6 +14645,8 @@
 		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>"; };
@@ -23433,6 +23437,8 @@
 		E4763D4A17B2704900D35206 /* style */ = {
 			isa = PBXGroup;
 			children = (
+				E4A814D71C70E10500BF85AC /* AttributeChangeInvalidation.cpp */,
+				E4A814D91C70E10D00BF85AC /* AttributeChangeInvalidation.h */,
 				E4A814D51C6DEE8D00BF85AC /* ClassChangeInvalidation.cpp */,
 				E4A814D31C6DEC4000BF85AC /* ClassChangeInvalidation.h */,
 				1C0106FE192594DF008A4201 /* InlineTextBoxStyle.cpp */,
@@ -25909,6 +25915,7 @@
 				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 */,
@@ -30440,6 +30447,7 @@
 				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 */,

Modified: trunk/Source/WebCore/css/DocumentRuleSets.cpp (196628 => 196629)


--- trunk/Source/WebCore/css/DocumentRuleSets.cpp	2016-02-16 08:11:47 UTC (rev 196628)
+++ trunk/Source/WebCore/css/DocumentRuleSets.cpp	2016-02-16 08:20:58 UTC (rev 196629)
@@ -118,7 +118,7 @@
 
 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 @@
     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

Modified: trunk/Source/WebCore/css/DocumentRuleSets.h (196628 => 196629)


--- trunk/Source/WebCore/css/DocumentRuleSets.h	2016-02-16 08:11:47 UTC (rev 196628)
+++ trunk/Source/WebCore/css/DocumentRuleSets.h	2016-02-16 08:20:58 UTC (rev 196629)
@@ -52,6 +52,12 @@
     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 @@
     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

Modified: trunk/Source/WebCore/css/RuleFeature.cpp (196628 => 196629)


--- trunk/Source/WebCore/css/RuleFeature.cpp	2016-02-16 08:11:47 UTC (rev 196628)
+++ trunk/Source/WebCore/css/RuleFeature.cpp	2016-02-16 08:20:58 UTC (rev 196629)
@@ -46,8 +46,12 @@
             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 @@
     } 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 @@
             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 @@
         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 @@
     siblingRules.clear();
     uncommonAttributeRules.clear();
     ancestorClassRules.clear();
+    ancestorAttributeRulesForHTML.clear();
     usesFirstLineRules = false;
     usesFirstLetterRules = false;
 }
@@ -132,6 +163,8 @@
     uncommonAttributeRules.shrinkToFit();
     for (auto& rules : ancestorClassRules.values())
         rules->shrinkToFit();
+    for (auto& rules : ancestorAttributeRulesForHTML.values())
+        rules->features.shrinkToFit();
 }
 
 } // namespace WebCore

Modified: trunk/Source/WebCore/css/RuleFeature.h (196628 => 196629)


--- trunk/Source/WebCore/css/RuleFeature.h	2016-02-16 08:11:47 UTC (rev 196628)
+++ trunk/Source/WebCore/css/RuleFeature.h	2016-02-16 08:20:58 UTC (rev 196629)
@@ -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 @@
     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 @@
     struct SelectorFeatures {
         bool hasSiblingSelector { false };
         Vector<AtomicStringImpl*> classesMatchingAncestors;
+        Vector<const CSSSelector*> attributeSelectorsMatchingAncestors;
     };
     void recursivelyCollectFeaturesFromSelector(SelectorFeatures&, const CSSSelector&, bool matchesAncestor = false);
 };

Modified: trunk/Source/WebCore/css/SelectorChecker.cpp (196628 => 196629)


--- trunk/Source/WebCore/css/SelectorChecker.cpp	2016-02-16 08:11:47 UTC (rev 196628)
+++ trunk/Source/WebCore/css/SelectorChecker.cpp	2016-02-16 08:20:58 UTC (rev 196629)
@@ -518,6 +518,21 @@
     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

Modified: trunk/Source/WebCore/css/SelectorChecker.h (196628 => 196629)


--- trunk/Source/WebCore/css/SelectorChecker.h	2016-02-16 08:11:47 UTC (rev 196628)
+++ trunk/Source/WebCore/css/SelectorChecker.h	2016-02-16 08:20:58 UTC (rev 196629)
@@ -119,6 +119,7 @@
 
     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*);

Modified: trunk/Source/WebCore/dom/Attr.cpp (196628 => 196629)


--- trunk/Source/WebCore/dom/Attr.cpp	2016-02-16 08:11:47 UTC (rev 196628)
+++ trunk/Source/WebCore/dom/Attr.cpp	2016-02-16 08:20:58 UTC (rev 196629)
@@ -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 @@
     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 @@
     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)

Modified: trunk/Source/WebCore/dom/Element.cpp (196628 => 196629)


--- trunk/Source/WebCore/dom/Element.cpp	2016-02-16 08:11:47 UTC (rev 196628)
+++ trunk/Source/WebCore/dom/Element.cpp	2016-02-16 08:20:58 UTC (rev 196629)
@@ -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 @@
         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 @@
 
     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 @@
     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 (!inSynchronizationOfLazyAttribute)
-        didRemoveAttribute(name, valueBeingRemoved);
+    if (!valueBeingRemoved.isNull())
+        willModifyAttribute(name, valueBeingRemoved, nullAtom);
+
+    {
+        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 @@
     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 @@
             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));
 

Modified: trunk/Source/WebCore/dom/Element.h (196628 => 196629)


--- trunk/Source/WebCore/dom/Element.h	2016-02-16 08:11:47 UTC (rev 196628)
+++ trunk/Source/WebCore/dom/Element.h	2016-02-16 08:20:58 UTC (rev 196629)
@@ -279,6 +279,8 @@
 
     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); }

Added: trunk/Source/WebCore/style/AttributeChangeInvalidation.cpp (0 => 196629)


--- trunk/Source/WebCore/style/AttributeChangeInvalidation.cpp	                        (rev 0)
+++ trunk/Source/WebCore/style/AttributeChangeInvalidation.cpp	2016-02-16 08:20:58 UTC (rev 196629)
@@ -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);
+}
+
+}
+}

Added: trunk/Source/WebCore/style/AttributeChangeInvalidation.h (0 => 196629)


--- trunk/Source/WebCore/style/AttributeChangeInvalidation.h	                        (rev 0)
+++ trunk/Source/WebCore/style/AttributeChangeInvalidation.h	2016-02-16 08:20:58 UTC (rev 196629)
@@ -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
+

Modified: trunk/Source/WebCore/style/ClassChangeInvalidation.h (196628 => 196629)


--- trunk/Source/WebCore/style/ClassChangeInvalidation.h	2016-02-16 08:11:47 UTC (rev 196628)
+++ trunk/Source/WebCore/style/ClassChangeInvalidation.h	2016-02-16 08:20:58 UTC (rev 196629)
@@ -46,7 +46,6 @@
 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 @@
     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)
 
 {
_______________________________________________
webkit-changes mailing list
[email protected]
https://lists.webkit.org/mailman/listinfo/webkit-changes

Reply via email to