Optimize style invalidation after class attribute change
authorantti@apple.com <antti@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Wed, 10 Feb 2016 20:47:04 +0000 (20:47 +0000)
committerantti@apple.com <antti@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Wed, 10 Feb 2016 20:47:04 +0000 (20:47 +0000)
https://bugs.webkit.org/show_bug.cgi?id=154075
rdar://problem/12526450

Reviewed by Andreas Kling.

Currently a class attribute change invalidates style for the entire element subtree for any class found in the
active stylesheet set.

This patch optimizes class changes by building a new optimization structure called ancestorClassRules. It contains
rules that have class selectors in the portion of the complex selector that matches ancestor elements. The sets
of rules are hashes by the class name.

On class attribute change the existing StyleInvalidationAnalysis mechanism is used with ancestorClassRules to invalidate
exactly those descendants that are affected by the addition or removal of the class name. This is fast because the CSS JIT
makes selector matching cheap and the number of relevant rules is typically small.

This optimization is very effective on many dynamic pages. For example when focusing and unfocusing the web inspector it
cuts down the number of resolved elements from ~1000 to ~50. Even in PLT it reduces the number of resolved elements by ~11%.

* css/DocumentRuleSets.cpp:
(WebCore::DocumentRuleSets::collectFeatures):
(WebCore::DocumentRuleSets::ancestorClassRules):

    Create optimization RuleSets on-demand when there is an actual dynamic class change.

* css/DocumentRuleSets.h:
(WebCore::DocumentRuleSets::features):
(WebCore::DocumentRuleSets::sibling):
(WebCore::DocumentRuleSets::uncommonAttribute):
* css/ElementRuleCollector.cpp:
(WebCore::ElementRuleCollector::ElementRuleCollector):

    Add a new constructor that doesn't requires DocumentRuleSets. Only the user and author style is required.

(WebCore::ElementRuleCollector::matchAuthorRules):
(WebCore::ElementRuleCollector::matchUserRules):
* css/ElementRuleCollector.h:
* css/RuleFeature.cpp:
(WebCore::RuleFeatureSet::recursivelyCollectFeaturesFromSelector):

    Collect class names that show up in the ancestor portion of the selector.
    Make this a member.

(WebCore::RuleFeatureSet::collectFeatures):

    Move this code from RuleData.
    Add the rule to ancestorClassRules if needed.

(WebCore::RuleFeatureSet::add):
(WebCore::RuleFeatureSet::clear):
(WebCore::RuleFeatureSet::shrinkToFit):
(WebCore::recursivelyCollectFeaturesFromSelector): Deleted.
(WebCore::RuleFeatureSet::collectFeaturesFromSelector): Deleted.
* css/RuleFeature.h:
(WebCore::RuleFeature::RuleFeature):
(WebCore::RuleFeatureSet::RuleFeatureSet): Deleted.
* css/RuleSet.cpp:
(WebCore::RuleData::RuleData):
(WebCore::RuleSet::RuleSet):
(WebCore::RuleSet::~RuleSet):
(WebCore::RuleSet::addToRuleSet):
(WebCore::RuleSet::addRule):
(WebCore::RuleSet::addRulesFromSheet):
(WebCore::collectFeaturesFromRuleData): Deleted.
* css/RuleSet.h:
(WebCore::RuleSet::tagRules):
(WebCore::RuleSet::RuleSet): Deleted.
* css/StyleInvalidationAnalysis.cpp:
(WebCore::shouldDirtyAllStyle):
(WebCore::StyleInvalidationAnalysis::StyleInvalidationAnalysis):

    Add a new constructor that takes a ready made RuleSet instead of a stylesheet.

(WebCore::StyleInvalidationAnalysis::invalidateIfNeeded):
(WebCore::StyleInvalidationAnalysis::invalidateStyleForTree):
(WebCore::StyleInvalidationAnalysis::invalidateStyle):
(WebCore::StyleInvalidationAnalysis::invalidateStyle):

    New function for invalidating a subtree instead of the whole document.

* css/StyleInvalidationAnalysis.h:
(WebCore::StyleInvalidationAnalysis::dirtiesAllStyle):
(WebCore::StyleInvalidationAnalysis::hasShadowPseudoElementRulesInAuthorSheet):
* dom/Element.cpp:
(WebCore::classStringHasClassName):
(WebCore::collectClasses):
(WebCore::computeClassChange):

    Factor to return the changed classes.

(WebCore::invalidateStyleForClassChange):

    First filter out classes that don't show up in stylesheets. If something remains invalidate the current
    element for inline style change (that is a style change that doesn't affect descendants).

    Next check if there are any ancestorClassRules for the changed class. If so use the StyleInvalidationAnalysis
    to find any affected descendants and invalidate them with inline style change as well.

(WebCore::Element::classAttributeChanged):

    Invalidate for removed classes before setting new attribute value, invalidate for added classes afterwards.

(WebCore::Element::absoluteLinkURL):
(WebCore::checkSelectorForClassChange): Deleted.
* dom/ElementData.h:
(WebCore::ElementData::setClassNames):
(WebCore::ElementData::classNames):
(WebCore::ElementData::classNamesMemoryOffset):
(WebCore::ElementData::clearClass): Deleted.
(WebCore::ElementData::setClass): Deleted.

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

13 files changed:
Source/WebCore/ChangeLog
Source/WebCore/css/DocumentRuleSets.cpp
Source/WebCore/css/DocumentRuleSets.h
Source/WebCore/css/ElementRuleCollector.cpp
Source/WebCore/css/ElementRuleCollector.h
Source/WebCore/css/RuleFeature.cpp
Source/WebCore/css/RuleFeature.h
Source/WebCore/css/RuleSet.cpp
Source/WebCore/css/RuleSet.h
Source/WebCore/css/StyleInvalidationAnalysis.cpp
Source/WebCore/css/StyleInvalidationAnalysis.h
Source/WebCore/dom/Element.cpp
Source/WebCore/dom/ElementData.h

index c9bf8b7..47f9c3c 100644 (file)
@@ -1,3 +1,117 @@
+2016-02-10  Antti Koivisto  <antti@apple.com>
+
+        Optimize style invalidation after class attribute change
+        https://bugs.webkit.org/show_bug.cgi?id=154075
+        rdar://problem/12526450
+
+        Reviewed by Andreas Kling.
+
+        Currently a class attribute change invalidates style for the entire element subtree for any class found in the
+        active stylesheet set.
+
+        This patch optimizes class changes by building a new optimization structure called ancestorClassRules. It contains
+        rules that have class selectors in the portion of the complex selector that matches ancestor elements. The sets
+        of rules are hashes by the class name.
+
+        On class attribute change the existing StyleInvalidationAnalysis mechanism is used with ancestorClassRules to invalidate
+        exactly those descendants that are affected by the addition or removal of the class name. This is fast because the CSS JIT
+        makes selector matching cheap and the number of relevant rules is typically small.
+
+        This optimization is very effective on many dynamic pages. For example when focusing and unfocusing the web inspector it
+        cuts down the number of resolved elements from ~1000 to ~50. Even in PLT it reduces the number of resolved elements by ~11%.
+
+        * css/DocumentRuleSets.cpp:
+        (WebCore::DocumentRuleSets::collectFeatures):
+        (WebCore::DocumentRuleSets::ancestorClassRules):
+
+            Create optimization RuleSets on-demand when there is an actual dynamic class change.
+
+        * css/DocumentRuleSets.h:
+        (WebCore::DocumentRuleSets::features):
+        (WebCore::DocumentRuleSets::sibling):
+        (WebCore::DocumentRuleSets::uncommonAttribute):
+        * css/ElementRuleCollector.cpp:
+        (WebCore::ElementRuleCollector::ElementRuleCollector):
+
+            Add a new constructor that doesn't requires DocumentRuleSets. Only the user and author style is required.
+
+        (WebCore::ElementRuleCollector::matchAuthorRules):
+        (WebCore::ElementRuleCollector::matchUserRules):
+        * css/ElementRuleCollector.h:
+        * css/RuleFeature.cpp:
+        (WebCore::RuleFeatureSet::recursivelyCollectFeaturesFromSelector):
+
+            Collect class names that show up in the ancestor portion of the selector.
+            Make this a member.
+
+        (WebCore::RuleFeatureSet::collectFeatures):
+
+            Move this code from RuleData.
+            Add the rule to ancestorClassRules if needed.
+
+        (WebCore::RuleFeatureSet::add):
+        (WebCore::RuleFeatureSet::clear):
+        (WebCore::RuleFeatureSet::shrinkToFit):
+        (WebCore::recursivelyCollectFeaturesFromSelector): Deleted.
+        (WebCore::RuleFeatureSet::collectFeaturesFromSelector): Deleted.
+        * css/RuleFeature.h:
+        (WebCore::RuleFeature::RuleFeature):
+        (WebCore::RuleFeatureSet::RuleFeatureSet): Deleted.
+        * css/RuleSet.cpp:
+        (WebCore::RuleData::RuleData):
+        (WebCore::RuleSet::RuleSet):
+        (WebCore::RuleSet::~RuleSet):
+        (WebCore::RuleSet::addToRuleSet):
+        (WebCore::RuleSet::addRule):
+        (WebCore::RuleSet::addRulesFromSheet):
+        (WebCore::collectFeaturesFromRuleData): Deleted.
+        * css/RuleSet.h:
+        (WebCore::RuleSet::tagRules):
+        (WebCore::RuleSet::RuleSet): Deleted.
+        * css/StyleInvalidationAnalysis.cpp:
+        (WebCore::shouldDirtyAllStyle):
+        (WebCore::StyleInvalidationAnalysis::StyleInvalidationAnalysis):
+
+            Add a new constructor that takes a ready made RuleSet instead of a stylesheet.
+
+        (WebCore::StyleInvalidationAnalysis::invalidateIfNeeded):
+        (WebCore::StyleInvalidationAnalysis::invalidateStyleForTree):
+        (WebCore::StyleInvalidationAnalysis::invalidateStyle):
+        (WebCore::StyleInvalidationAnalysis::invalidateStyle):
+
+            New function for invalidating a subtree instead of the whole document.
+
+        * css/StyleInvalidationAnalysis.h:
+        (WebCore::StyleInvalidationAnalysis::dirtiesAllStyle):
+        (WebCore::StyleInvalidationAnalysis::hasShadowPseudoElementRulesInAuthorSheet):
+        * dom/Element.cpp:
+        (WebCore::classStringHasClassName):
+        (WebCore::collectClasses):
+        (WebCore::computeClassChange):
+
+            Factor to return the changed classes.
+
+        (WebCore::invalidateStyleForClassChange):
+
+            First filter out classes that don't show up in stylesheets. If something remains invalidate the current
+            element for inline style change (that is a style change that doesn't affect descendants).
+
+            Next check if there are any ancestorClassRules for the changed class. If so use the StyleInvalidationAnalysis
+            to find any affected descendants and invalidate them with inline style change as well.
+
+        (WebCore::Element::classAttributeChanged):
+
+            Invalidate for removed classes before setting new attribute value, invalidate for added classes afterwards.
+
+        (WebCore::Element::absoluteLinkURL):
+        (WebCore::checkSelectorForClassChange): Deleted.
+        * dom/ElementData.h:
+        (WebCore::ElementData::setClassNames):
+        (WebCore::ElementData::classNames):
+        (WebCore::ElementData::classNamesMemoryOffset):
+        (WebCore::ElementData::clearClass): Deleted.
+        (WebCore::ElementData::setClass): Deleted.
+
 2016-02-10  Myles C. Maxfield  <mmaxfield@apple.com>
 
         Addressing post-review comments after r196322
index 370c0c0..af4fd4e 100644 (file)
@@ -115,4 +115,14 @@ void DocumentRuleSets::collectFeatures()
     m_uncommonAttributeRuleSet = makeRuleSet(m_features.uncommonAttributeRules);
 }
 
+RuleSet* DocumentRuleSets::ancestorClassRules(AtomicStringImpl* className) const
+{
+    auto addResult = m_ancestorClassRuleSet.add(className, nullptr);
+    if (addResult.isNewEntry) {
+        if (auto* rules = m_features.ancestorClassRules.get(className))
+            addResult.iterator->value = makeRuleSet(*rules);
+    }
+    return addResult.iterator->value.get();
+}
+
 } // namespace WebCore
index fdf1eea..a5461f7 100644 (file)
@@ -26,6 +26,7 @@
 #include "RuleFeature.h"
 #include "RuleSet.h"
 #include <memory>
+#include <wtf/HashMap.h>
 #include <wtf/RefPtr.h>
 #include <wtf/Vector.h>
 
@@ -48,6 +49,7 @@ public:
     const RuleFeatureSet& features() const { return m_features; }
     RuleSet* sibling() const { return m_siblingRuleSet.get(); }
     RuleSet* uncommonAttribute() const { return m_uncommonAttributeRuleSet.get(); }
+    RuleSet* ancestorClassRules(AtomicStringImpl* className) const;
 
     void initUserStyle(ExtensionStyleSheets&, const MediaQueryEvaluator&, StyleResolver&);
     void resetAuthorStyle();
@@ -62,6 +64,7 @@ private:
     RuleFeatureSet m_features;
     std::unique_ptr<RuleSet> m_siblingRuleSet;
     std::unique_ptr<RuleSet> m_uncommonAttributeRuleSet;
+    mutable HashMap<AtomicStringImpl*, std::unique_ptr<RuleSet>> m_ancestorClassRuleSet;
 };
 
 } // namespace WebCore
index b3f7afb..5149fa9 100644 (file)
@@ -80,7 +80,16 @@ public:
 ElementRuleCollector::ElementRuleCollector(Element& element, RenderStyle* style, const DocumentRuleSets& ruleSets, const SelectorFilter* selectorFilter)
     : m_element(element)
     , m_style(style)
-    , m_ruleSets(ruleSets)
+    , m_authorStyle(*ruleSets.authorStyle())
+    , m_userStyle(ruleSets.userStyle())
+    , m_selectorFilter(selectorFilter)
+{
+    ASSERT(!m_selectorFilter || m_selectorFilter->parentStackIsConsistent(element.parentNode()));
+}
+
+ElementRuleCollector::ElementRuleCollector(Element& element, const RuleSet& authorStyle, const SelectorFilter* selectorFilter)
+    : m_element(element)
+    , m_authorStyle(authorStyle)
     , m_selectorFilter(selectorFilter)
 {
     ASSERT(!m_selectorFilter || m_selectorFilter->parentStackIsConsistent(element.parentNode()));
@@ -203,7 +212,7 @@ void ElementRuleCollector::matchAuthorRules(bool includeEmptyRules)
     m_result.ranges.lastAuthorRule = m_result.matchedProperties().size() - 1;
 
     // Match global author rules.
-    MatchRequest matchRequest(m_ruleSets.authorStyle(), includeEmptyRules);
+    MatchRequest matchRequest(&m_authorStyle, includeEmptyRules);
     StyleResolver::RuleRange ruleRange = m_result.ranges.authorRuleRange();
     collectMatchingRules(matchRequest, ruleRange);
     collectMatchingRulesForRegion(matchRequest, ruleRange);
@@ -236,13 +245,13 @@ void ElementRuleCollector::matchHostPseudoClassRules(bool includeEmptyRules)
 
 void ElementRuleCollector::matchUserRules(bool includeEmptyRules)
 {
-    if (!m_ruleSets.userStyle())
+    if (!m_userStyle)
         return;
     
     clearMatchedRules();
 
     m_result.ranges.lastUserRule = m_result.matchedProperties().size() - 1;
-    MatchRequest matchRequest(m_ruleSets.userStyle(), includeEmptyRules);
+    MatchRequest matchRequest(m_userStyle, includeEmptyRules);
     StyleResolver::RuleRange ruleRange = m_result.ranges.userRuleRange();
     collectMatchingRules(matchRequest, ruleRange);
     collectMatchingRulesForRegion(matchRequest, ruleRange);
index 514d32e..c9ec8f5 100644 (file)
@@ -46,6 +46,7 @@ struct MatchedRule {
 class ElementRuleCollector {
 public:
     ElementRuleCollector(Element&, RenderStyle*, const DocumentRuleSets&, const SelectorFilter*);
+    ElementRuleCollector(Element&, const RuleSet& authorStyle, const SelectorFilter*);
 
     void matchAllRules(bool matchAuthorAndUserStyles, bool includeSMILProperties);
     void matchUARules();
@@ -87,9 +88,10 @@ private:
     void commitStyleRelations(const SelectorChecker::StyleRelations&);
 
     Element& m_element;
-    RenderStyle* m_style;
-    const DocumentRuleSets& m_ruleSets;
-    const SelectorFilter* m_selectorFilter;
+    RenderStyle* m_style { nullptr };
+    const RuleSet& m_authorStyle;
+    const RuleSet* m_userStyle { nullptr };
+    const SelectorFilter* m_selectorFilter { nullptr };
 
     bool m_isPrintStyle { false };
     const RenderRegion* m_regionForStyling { nullptr };
index c24ec82..7903e9a 100644 (file)
 
 #include "CSSSelector.h"
 #include "CSSSelectorList.h"
+#include "RuleSet.h"
 
 namespace WebCore {
 
-static void recursivelyCollectFeaturesFromSelector(RuleFeatureSet& features, const CSSSelector& firstSelector, bool& hasSiblingSelector)
+void RuleFeatureSet::recursivelyCollectFeaturesFromSelector(SelectorFeatures& selectorFeatures, const CSSSelector& firstSelector, bool matchesAncestor)
 {
     const CSSSelector* selector = &firstSelector;
     do {
         if (selector->match() == CSSSelector::Id)
-            features.idsInRules.add(selector->value().impl());
-        else if (selector->match() == CSSSelector::Class)
-            features.classesInRules.add(selector->value().impl());
-        else if (selector->isAttributeSelector()) {
-            features.attributeCanonicalLocalNamesInRules.add(selector->attributeCanonicalLocalName().impl());
-            features.attributeLocalNamesInRules.add(selector->attribute().localName().impl());
+            idsInRules.add(selector->value().impl());
+        else if (selector->match() == CSSSelector::Class) {
+            classesInRules.add(selector->value().impl());
+            if (matchesAncestor)
+                selectorFeatures.classesMatchingAncestors.append(selector->value().impl());
+        } else if (selector->isAttributeSelector()) {
+            attributeCanonicalLocalNamesInRules.add(selector->attributeCanonicalLocalName().impl());
+            attributeLocalNamesInRules.add(selector->attribute().localName().impl());
         } else if (selector->match() == CSSSelector::PseudoElement) {
             switch (selector->pseudoElementType()) {
             case CSSSelector::PseudoElementFirstLine:
-                features.usesFirstLineRules = true;
+                usesFirstLineRules = true;
                 break;
             case CSSSelector::PseudoElementFirstLetter:
-                features.usesFirstLetterRules = true;
+                usesFirstLetterRules = true;
                 break;
             default:
                 break;
             }
         }
 
-        if (!hasSiblingSelector && selector->isSiblingSelector())
-            hasSiblingSelector = true;
+        if (!selectorFeatures.hasSiblingSelector && selector->isSiblingSelector())
+            selectorFeatures.hasSiblingSelector = true;
 
         if (const CSSSelectorList* selectorList = selector->selectorList()) {
             for (const CSSSelector* subSelector = selectorList->first(); subSelector; subSelector = CSSSelectorList::next(subSelector)) {
-                if (!hasSiblingSelector && selector->isSiblingSelector())
-                    hasSiblingSelector = true;
-                recursivelyCollectFeaturesFromSelector(features, *subSelector, hasSiblingSelector);
+                if (!selectorFeatures.hasSiblingSelector && selector->isSiblingSelector())
+                    selectorFeatures.hasSiblingSelector = true;
+                recursivelyCollectFeaturesFromSelector(selectorFeatures, *subSelector, matchesAncestor);
             }
         }
+        if (selector->relation() == CSSSelector::Child || selector->relation() == CSSSelector::Descendant)
+            matchesAncestor = true;
 
         selector = selector->tagHistory();
     } while (selector);
 }
 
-void RuleFeatureSet::collectFeaturesFromSelector(const CSSSelector& firstSelector, bool& hasSiblingSelector)
+void RuleFeatureSet::collectFeatures(const RuleData& ruleData)
 {
-    hasSiblingSelector = false;
-    recursivelyCollectFeaturesFromSelector(*this, firstSelector, hasSiblingSelector);
+    SelectorFeatures selectorFeatures;
+    recursivelyCollectFeaturesFromSelector(selectorFeatures, *ruleData.selector());
+    if (selectorFeatures.hasSiblingSelector)
+        siblingRules.append(RuleFeature(ruleData.rule(), ruleData.selectorIndex(), ruleData.hasDocumentSecurityOrigin()));
+    if (ruleData.containsUncommonAttributeSelector())
+        uncommonAttributeRules.append(RuleFeature(ruleData.rule(), ruleData.selectorIndex(), ruleData.hasDocumentSecurityOrigin()));
+    for (auto* className : selectorFeatures.classesMatchingAncestors) {
+        auto addResult = ancestorClassRules.add(className, nullptr);
+        if (addResult.isNewEntry)
+            addResult.iterator->value = std::make_unique<Vector<RuleFeature>>();
+        addResult.iterator->value->append(RuleFeature(ruleData.rule(), ruleData.selectorIndex(), ruleData.hasDocumentSecurityOrigin()));
+    }
 }
 
 void RuleFeatureSet::add(const RuleFeatureSet& other)
@@ -87,6 +102,13 @@ void RuleFeatureSet::add(const RuleFeatureSet& other)
     attributeLocalNamesInRules.add(other.attributeLocalNamesInRules.begin(), other.attributeLocalNamesInRules.end());
     siblingRules.appendVector(other.siblingRules);
     uncommonAttributeRules.appendVector(other.uncommonAttributeRules);
+    for (auto& keyValuePair : other.ancestorClassRules) {
+        auto addResult = ancestorClassRules.add(keyValuePair.key, nullptr);
+        if (addResult.isNewEntry)
+            addResult.iterator->value = std::make_unique<Vector<RuleFeature>>(*keyValuePair.value);
+        else
+            addResult.iterator->value->appendVector(*keyValuePair.value);
+    }
     usesFirstLineRules = usesFirstLineRules || other.usesFirstLineRules;
     usesFirstLetterRules = usesFirstLetterRules || other.usesFirstLetterRules;
 }
@@ -99,6 +121,7 @@ void RuleFeatureSet::clear()
     attributeLocalNamesInRules.clear();
     siblingRules.clear();
     uncommonAttributeRules.clear();
+    ancestorClassRules.clear();
     usesFirstLineRules = false;
     usesFirstLetterRules = false;
 }
@@ -107,6 +130,8 @@ void RuleFeatureSet::shrinkToFit()
 {
     siblingRules.shrinkToFit();
     uncommonAttributeRules.shrinkToFit();
+    for (auto& rules : ancestorClassRules.values())
+        rules->shrinkToFit();
 }
 
 } // namespace WebCore
index 0a3a57e..fd6175e 100644 (file)
@@ -29,8 +29,9 @@
 
 namespace WebCore {
 
-class StyleRule;
 class CSSSelector;
+class RuleData;
+class StyleRule;
 
 struct RuleFeature {
     RuleFeature(StyleRule* rule, unsigned selectorIndex, bool hasDocumentSecurityOrigin)
@@ -45,15 +46,10 @@ struct RuleFeature {
 };
 
 struct RuleFeatureSet {
-    RuleFeatureSet()
-        : usesFirstLineRules(false)
-        , usesFirstLetterRules(false)
-    { }
-
     void add(const RuleFeatureSet&);
     void clear();
     void shrinkToFit();
-    void collectFeaturesFromSelector(const CSSSelector&, bool& hasSiblingSelector);
+    void collectFeatures(const RuleData&);
 
     HashSet<AtomicStringImpl*> idsInRules;
     HashSet<AtomicStringImpl*> classesInRules;
@@ -61,8 +57,16 @@ struct RuleFeatureSet {
     HashSet<AtomicStringImpl*> attributeLocalNamesInRules;
     Vector<RuleFeature> siblingRules;
     Vector<RuleFeature> uncommonAttributeRules;
-    bool usesFirstLineRules;
-    bool usesFirstLetterRules;
+    HashMap<AtomicStringImpl*, std::unique_ptr<Vector<RuleFeature>>> ancestorClassRules;
+    bool usesFirstLineRules { false };
+    bool usesFirstLetterRules { false };
+
+private:
+    struct SelectorFeatures {
+        bool hasSiblingSelector { false };
+        Vector<AtomicStringImpl*> classesMatchingAncestors;
+    };
+    void recursivelyCollectFeaturesFromSelector(SelectorFeatures&, const CSSSelector&, bool matchesAncestor = false);
 };
 
 } // namespace WebCore
index 4c85451..0ebd5fc 100644 (file)
@@ -172,15 +172,12 @@ RuleData::RuleData(StyleRule* rule, unsigned selectorIndex, unsigned position, A
     SelectorFilter::collectIdentifierHashes(selector(), m_descendantSelectorIdentifierHashes, maximumIdentifierCount);
 }
 
-static void collectFeaturesFromRuleData(RuleFeatureSet& features, const RuleData& ruleData)
+RuleSet::RuleSet()
 {
-    bool hasSiblingSelector;
-    features.collectFeaturesFromSelector(*ruleData.selector(), hasSiblingSelector);
+}
 
-    if (hasSiblingSelector)
-        features.siblingRules.append(RuleFeature(ruleData.rule(), ruleData.selectorIndex(), ruleData.hasDocumentSecurityOrigin()));
-    if (ruleData.containsUncommonAttributeSelector())
-        features.uncommonAttributeRules.append(RuleFeature(ruleData.rule(), ruleData.selectorIndex(), ruleData.hasDocumentSecurityOrigin()));
+RuleSet::~RuleSet()
+{
 }
 
 void RuleSet::addToRuleSet(AtomicStringImpl* key, AtomRuleMap& map, const RuleData& ruleData)
@@ -203,7 +200,7 @@ static unsigned rulesCountForName(const RuleSet::AtomRuleMap& map, AtomicStringI
 void RuleSet::addRule(StyleRule* rule, unsigned selectorIndex, AddRuleFlags addRuleFlags)
 {
     RuleData ruleData(rule, selectorIndex, m_ruleCount++, addRuleFlags);
-    collectFeaturesFromRuleData(m_features, ruleData);
+    m_features.collectFeatures(ruleData);
 
     unsigned classBucketSize = 0;
     const CSSSelector* tagSelector = nullptr;
index 926861f..fe51d90 100644 (file)
@@ -157,6 +157,7 @@ public:
     };
 
     RuleSet();
+    ~RuleSet();
 
     typedef Vector<RuleData, 1> RuleDataVector;
     typedef HashMap<AtomicStringImpl*, std::unique_ptr<RuleDataVector>> AtomRuleMap;
@@ -213,18 +214,12 @@ private:
     RuleDataVector m_focusPseudoClassRules;
     RuleDataVector m_universalRules;
     Vector<StyleRulePage*> m_pageRules;
-    unsigned m_ruleCount;
-    bool m_autoShrinkToFitEnabled;
+    unsigned m_ruleCount { 0 };
+    bool m_autoShrinkToFitEnabled { false };
     RuleFeatureSet m_features;
     Vector<RuleSetSelectorPair> m_regionSelectorsAndRuleSets;
 };
 
-inline RuleSet::RuleSet()
-    : m_ruleCount(0)
-    , m_autoShrinkToFitEnabled(true)
-{
-}
-
 inline const RuleSet::RuleDataVector* RuleSet::tagRules(AtomicStringImpl* key, bool isHTMLName) const
 {
     const AtomRuleMap* tagRules;
index 4048184..c6c39d8 100644 (file)
@@ -75,19 +75,27 @@ static bool shouldDirtyAllStyle(const Vector<StyleSheetContents*>& sheets)
 }
 
 StyleInvalidationAnalysis::StyleInvalidationAnalysis(const Vector<StyleSheetContents*>& sheets, const MediaQueryEvaluator& mediaQueryEvaluator)
-    : m_dirtiesAllStyle(shouldDirtyAllStyle(sheets))
+    : m_ownedRuleSet(std::make_unique<RuleSet>())
+    , m_ruleSet(*m_ownedRuleSet)
+    , m_dirtiesAllStyle(shouldDirtyAllStyle(sheets))
 {
     if (m_dirtiesAllStyle)
         return;
 
-    m_ruleSets.resetAuthorStyle();
+    m_ownedRuleSet->disableAutoShrinkToFit();
     for (auto& sheet : sheets)
-        m_ruleSets.authorStyle()->addRulesFromSheet(*sheet, mediaQueryEvaluator);
+        m_ownedRuleSet->addRulesFromSheet(*sheet, mediaQueryEvaluator);
 
-    m_hasShadowPseudoElementRulesInAuthorSheet = m_ruleSets.authorStyle()->hasShadowPseudoElementRules();
+    m_hasShadowPseudoElementRulesInAuthorSheet = m_ruleSet.hasShadowPseudoElementRules();
 }
 
-StyleInvalidationAnalysis::CheckDescendants StyleInvalidationAnalysis::invalidateIfNeeded(Element& element, SelectorFilter& filter)
+StyleInvalidationAnalysis::StyleInvalidationAnalysis(const RuleSet& ruleSet)
+    : m_ruleSet(ruleSet)
+    , m_hasShadowPseudoElementRulesInAuthorSheet(ruleSet.hasShadowPseudoElementRules())
+{
+}
+
+StyleInvalidationAnalysis::CheckDescendants StyleInvalidationAnalysis::invalidateIfNeeded(Element& element, const SelectorFilter* filter)
 {
     if (m_hasShadowPseudoElementRulesInAuthorSheet) {
         // FIXME: This could do actual rule matching too.
@@ -97,7 +105,7 @@ StyleInvalidationAnalysis::CheckDescendants StyleInvalidationAnalysis::invalidat
 
     switch (element.styleChangeType()) {
     case NoStyleChange: {
-        ElementRuleCollector ruleCollector(element, nullptr, m_ruleSets, &filter);
+        ElementRuleCollector ruleCollector(element, m_ruleSet, filter);
         ruleCollector.setMode(SelectorChecker::Mode::CollectingRulesIgnoringVirtualPseudoElements);
         ruleCollector.matchAuthorRules(false);
 
@@ -116,7 +124,7 @@ StyleInvalidationAnalysis::CheckDescendants StyleInvalidationAnalysis::invalidat
     return CheckDescendants::Yes;
 }
 
-void StyleInvalidationAnalysis::invalidateStyleForTree(Element& root, SelectorFilter& filter)
+void StyleInvalidationAnalysis::invalidateStyleForTree(Element& root, SelectorFilter* filter)
 {
     if (invalidateIfNeeded(root, filter) == CheckDescendants::No)
         return;
@@ -130,11 +138,13 @@ void StyleInvalidationAnalysis::invalidateStyleForTree(Element& root, SelectorFi
         if (parentStack.isEmpty() || parentStack.last() != parent) {
             if (parent == previousElement) {
                 parentStack.append(parent);
-                filter.pushParent(parent);
+                if (filter)
+                    filter->pushParent(parent);
             } else {
                 while (parentStack.last() != parent) {
                     parentStack.removeLast();
-                    filter.popParent();
+                    if (filter)
+                        filter->popParent();
                 }
             }
         }
@@ -150,15 +160,21 @@ void StyleInvalidationAnalysis::invalidateStyleForTree(Element& root, SelectorFi
 void StyleInvalidationAnalysis::invalidateStyle(Document& document)
 {
     ASSERT(!m_dirtiesAllStyle);
-    if (!m_ruleSets.authorStyle())
-        return;
 
     Element* documentElement = document.documentElement();
     if (!documentElement)
         return;
 
     SelectorFilter filter;
-    invalidateStyleForTree(*documentElement, filter);
+    invalidateStyleForTree(*documentElement, &filter);
+}
+
+void StyleInvalidationAnalysis::invalidateStyle(Element& element)
+{
+    ASSERT(!m_dirtiesAllStyle);
+
+    // Don't use SelectorFilter as the rule sets here tend to be small and the filter would have setup cost deep in the tree.
+    invalidateStyleForTree(element, nullptr);
 }
 
 }
index 95fb64d..ebb7728 100644 (file)
 #ifndef StyleInvalidationAnalysis_h
 #define StyleInvalidationAnalysis_h
 
-#include "DocumentRuleSets.h"
 #include <wtf/HashSet.h>
 #include <wtf/text/AtomicStringImpl.h>
 
 namespace WebCore {
 
 class Document;
+class Element;
+class MediaQueryEvaluator;
+class RuleSet;
 class SelectorFilter;
 class StyleSheetContents;
 
 class StyleInvalidationAnalysis {
 public:
     StyleInvalidationAnalysis(const Vector<StyleSheetContents*>&, const MediaQueryEvaluator&);
+    StyleInvalidationAnalysis(const RuleSet&);
 
     bool dirtiesAllStyle() const { return m_dirtiesAllStyle; }
     bool hasShadowPseudoElementRulesInAuthorSheet() const { return m_hasShadowPseudoElementRulesInAuthorSheet; }
     void invalidateStyle(Document&);
+    void invalidateStyle(Element&);
 
 private:
     enum class CheckDescendants { Yes, No };
-    CheckDescendants invalidateIfNeeded(Element&, SelectorFilter&);
-    void invalidateStyleForTree(Element&, SelectorFilter&);
+    CheckDescendants invalidateIfNeeded(Element&, const SelectorFilter*);
+    void invalidateStyleForTree(Element&, SelectorFilter*);
 
+    std::unique_ptr<RuleSet> m_ownedRuleSet;
+    const RuleSet& m_ruleSet;
     bool m_dirtiesAllStyle { false };
     bool m_hasShadowPseudoElementRulesInAuthorSheet { false };
-    DocumentRuleSets m_ruleSets;
 };
 
 }
index 2a31099..f38a2bd 100644 (file)
@@ -76,6 +76,7 @@
 #include "ScrollLatchingState.h"
 #include "SelectorQuery.h"
 #include "Settings.h"
+#include "StyleInvalidationAnalysis.h"
 #include "StyleProperties.h"
 #include "StyleResolver.h"
 #include "StyleTreeResolver.h"
@@ -1299,25 +1300,39 @@ static inline bool classStringHasClassName(const AtomicString& newClassString)
     return classStringHasClassName(newClassString.characters16(), length);
 }
 
-static bool checkSelectorForClassChange(const SpaceSplitString& changedClasses, const StyleResolver& styleResolver)
+static Vector<AtomicStringImpl*, 4> collectClasses(const SpaceSplitString& classes)
 {
-    unsigned changedSize = changedClasses.size();
-    for (unsigned i = 0; i < changedSize; ++i) {
-        if (styleResolver.hasSelectorForClass(changedClasses[i]))
-            return true;
-    }
-    return false;
+    Vector<AtomicStringImpl*, 4> result;
+    result.reserveCapacity(classes.size());
+    for (unsigned i = 0; i < classes.size(); ++i)
+        result.uncheckedAppend(classes[i].impl());
+    return result;
 }
 
-static bool checkSelectorForClassChange(const SpaceSplitString& oldClasses, const SpaceSplitString& newClasses, const StyleResolver& styleResolver)
+struct ClassChange {
+    Vector<AtomicStringImpl*, 4> added;
+    Vector<AtomicStringImpl*, 4> removed;
+};
+
+static ClassChange computeClassChange(const SpaceSplitString& oldClasses, const SpaceSplitString& newClasses)
 {
+    ClassChange classChange;
+
     unsigned oldSize = oldClasses.size();
-    if (!oldSize)
-        return checkSelectorForClassChange(newClasses, styleResolver);
+    unsigned newSize = newClasses.size();
+
+    if (!oldSize) {
+        classChange.added = collectClasses(newClasses);
+        return classChange;
+    }
+    if (!newSize) {
+        classChange.removed = collectClasses(oldClasses);
+        return classChange;
+    }
+
     BitVector remainingClassBits;
     remainingClassBits.ensureSize(oldSize);
     // Class vectors tend to be very short. This is faster than using a hash table.
-    unsigned newSize = newClasses.size();
     for (unsigned i = 0; i < newSize; ++i) {
         bool foundFromBoth = false;
         for (unsigned j = 0; j < oldSize; ++j) {
@@ -1328,47 +1343,81 @@ static bool checkSelectorForClassChange(const SpaceSplitString& oldClasses, cons
         }
         if (foundFromBoth)
             continue;
-        if (styleResolver.hasSelectorForClass(newClasses[i]))
-            return true;
+        classChange.added.append(newClasses[i].impl());
     }
     for (unsigned i = 0; i < oldSize; ++i) {
         // If the bit is not set the the corresponding class has been removed.
         if (remainingClassBits.quickGet(i))
             continue;
-        if (styleResolver.hasSelectorForClass(oldClasses[i]))
-            return true;
+        classChange.removed.append(oldClasses[i].impl());
+    }
+
+    return classChange;
+}
+
+static void invalidateStyleForClassChange(Element& element, const Vector<AtomicStringImpl*, 4>& changedClasses, const DocumentRuleSets& ruleSets)
+{
+    Vector<AtomicStringImpl*, 4> changedClassesAffectingStyle;
+    for (auto* changedClass : changedClasses) {
+        if (ruleSets.features().classesInRules.contains(changedClass))
+            changedClassesAffectingStyle.append(changedClass);
+    };
+
+    if (changedClassesAffectingStyle.isEmpty())
+        return;
+
+    if (element.shadowRoot() && ruleSets.authorStyle()->hasShadowPseudoElementRules()) {
+        element.setNeedsStyleRecalc(FullStyleChange);
+        return;
+    }
+
+    element.setNeedsStyleRecalc(InlineStyleChange);
+
+    if (!element.firstElementChild())
+        return;
+
+    for (auto* changedClass : changedClassesAffectingStyle) {
+        auto* ancestorClassRules = ruleSets.ancestorClassRules(changedClass);
+        if (!ancestorClassRules)
+            continue;
+        StyleInvalidationAnalysis invalidationAnalysis(*ancestorClassRules);
+        invalidationAnalysis.invalidateStyle(element);
     }
-    return false;
 }
 
 void Element::classAttributeChanged(const AtomicString& newClassString)
 {
+    // Note: We'll need ElementData, but it doesn't have to be UniqueElementData.
+    if (!elementData())
+        ensureUniqueElementData();
+
+    bool shouldFoldCase = document().inQuirksMode();
+    bool newStringHasClasses = classStringHasClassName(newClassString);
+
+    auto oldClassNames = elementData()->classNames();
+    auto newClassNames = newStringHasClasses ? SpaceSplitString(newClassString, shouldFoldCase) : SpaceSplitString();
+
     StyleResolver* styleResolver = document().styleResolverIfExists();
-    bool testShouldInvalidateStyle = inRenderedDocument() && styleResolver && styleChangeType() < FullStyleChange;
-    bool shouldInvalidateStyle = false;
+    bool shouldInvalidateStyle = inRenderedDocument() && styleResolver && styleChangeType() < FullStyleChange;
+
+    ClassChange classChange;
+    if (shouldInvalidateStyle) {
+        classChange = computeClassChange(oldClassNames, newClassNames);
+        if (!classChange.removed.isEmpty())
+            invalidateStyleForClassChange(*this, classChange.removed, styleResolver->ruleSets());
+    }
+
+    elementData()->setClassNames(newClassNames);
 
-    if (classStringHasClassName(newClassString)) {
-        const bool shouldFoldCase = document().inQuirksMode();
-        // Note: We'll need ElementData, but it doesn't have to be UniqueElementData.
-        if (!elementData())
-            ensureUniqueElementData();
-        const SpaceSplitString oldClasses = elementData()->classNames();
-        elementData()->setClass(newClassString, shouldFoldCase);
-        const SpaceSplitString& newClasses = elementData()->classNames();
-        shouldInvalidateStyle = testShouldInvalidateStyle && checkSelectorForClassChange(oldClasses, newClasses, *styleResolver);
-    } else if (elementData()) {
-        const SpaceSplitString& oldClasses = elementData()->classNames();
-        shouldInvalidateStyle = testShouldInvalidateStyle && checkSelectorForClassChange(oldClasses, *styleResolver);
-        elementData()->clearClass();
+    if (shouldInvalidateStyle) {
+        if (!classChange.added.isEmpty())
+            invalidateStyleForClassChange(*this, classChange.added, styleResolver->ruleSets());
     }
 
     if (hasRareData()) {
         if (auto* classList = elementRareData()->classList())
             classList->attributeValueChanged(newClassString);
     }
-
-    if (shouldInvalidateStyle)
-        setNeedsStyleRecalc();
 }
 
 URL Element::absoluteLinkURL() const
index e790dee..46da40f 100644 (file)
@@ -85,8 +85,7 @@ public:
 
     static const unsigned attributeNotFound = static_cast<unsigned>(-1);
 
-    void clearClass() const { m_classNames.clear(); }
-    void setClass(const AtomicString& className, bool shouldFoldCase) const { m_classNames.set(className, shouldFoldCase); }
+    void setClassNames(const SpaceSplitString& classNames) const { m_classNames = classNames; }
     const SpaceSplitString& classNames() const { return m_classNames; }
     static ptrdiff_t classNamesMemoryOffset() { return OBJECT_OFFSETOF(ElementData, m_classNames); }