Upstream version 5.34.104.0
[platform/framework/web/crosswalk.git] / src / third_party / WebKit / Source / core / css / RuleFeature.cpp
index 8dfb8cd..b52569a 100644 (file)
 #include "core/css/CSSSelector.h"
 #include "core/css/CSSSelectorList.h"
 #include "core/css/RuleSet.h"
+#include "core/dom/Document.h"
+#include "core/dom/Element.h"
+#include "core/dom/Node.h"
+#include "core/dom/shadow/ElementShadow.h"
+#include "core/dom/shadow/ShadowRoot.h"
+#include "wtf/BitVector.h"
 
 namespace WebCore {
 
-static bool isSkippableComponentForInvalidation(const CSSSelector* selector)
+static bool isSkippableComponentForInvalidation(const CSSSelector& selector)
 {
-    if (selector->matchesPseudoElement() || selector->pseudoType() == CSSSelector::PseudoHost)
+    if (selector.matchesPseudoElement() || selector.pseudoType() == CSSSelector::PseudoHost)
         return false;
     return true;
 }
 
 // This method is somewhat conservative in what it acceptss.
-static bool supportsClassDescendantInvalidation(const CSSSelector* selector)
+static bool supportsClassDescendantInvalidation(const CSSSelector& selector)
 {
     bool foundDescendantRelation = false;
     bool foundAncestorIdent = false;
     bool foundIdent = false;
-    for (const CSSSelector* component = selector; component; component = component->tagHistory()) {
+    for (const CSSSelector* component = &selector; component; component = component->tagHistory()) {
 
         // FIXME: We should allow pseudo elements, but we need to change how they hook
         // into recalcStyle by moving them to recalcOwnStyle instead of recalcChildStyle.
 
-        if (component->m_match == CSSSelector::Tag
-            || component->m_match == CSSSelector::Id
-            || component->m_match == CSSSelector::Class) {
+        // FIXME: next up: Tag and Id.
+        if (component->m_match == CSSSelector::Class) {
             if (!foundDescendantRelation)
                 foundIdent = true;
             else
                 foundAncestorIdent = true;
-        } else if (!isSkippableComponentForInvalidation(component)) {
+        } else if (!isSkippableComponentForInvalidation(*component)) {
             return false;
         }
         // FIXME: We can probably support ChildTree and DescendantTree.
@@ -80,41 +85,44 @@ static bool supportsClassDescendantInvalidation(const CSSSelector* selector)
     return foundDescendantRelation && foundAncestorIdent && foundIdent;
 }
 
-void extractClassIdOrTag(const CSSSelector& selector, HashSet<AtomicString>& classes, AtomicString& id, AtomicString& tagName)
+void extractClassIdOrTag(const CSSSelector& selector, Vector<AtomicString>& classes, AtomicString& id, AtomicString& tagName)
 {
     if (selector.m_match == CSSSelector::Tag)
         tagName = selector.tagQName().localName();
     else if (selector.m_match == CSSSelector::Id)
         id = selector.value();
     else if (selector.m_match == CSSSelector::Class)
-        classes.add(selector.value());
+        classes.append(selector.value());
 }
 
-bool RuleFeatureSet::updateClassInvalidationSets(const CSSSelector* selector)
+RuleFeatureSet::RuleFeatureSet()
+    : m_targetedStyleRecalcEnabled(RuntimeEnabledFeatures::targetedStyleRecalcEnabled())
+{
+}
+
+bool RuleFeatureSet::updateClassInvalidationSets(const CSSSelector& selector)
 {
-    if (!selector)
-        return false;
     if (!supportsClassDescendantInvalidation(selector))
         return false;
 
-    HashSet<AtomicString> classes;
+    Vector<AtomicString> classes;
     AtomicString id;
     AtomicString tagName;
 
-    const CSSSelector* lastSelector = selector;
+    const CSSSelector* lastSelector = &selector;
     for (; lastSelector->relation() == CSSSelector::SubSelector; lastSelector = lastSelector->tagHistory()) {
-        extractClassIdOrTag(*selector, classes, id, tagName);
+        extractClassIdOrTag(*lastSelector, classes, id, tagName);
     }
-    extractClassIdOrTag(*selector, classes, id, tagName);
+    extractClassIdOrTag(*lastSelector, classes, id, tagName);
 
-    for ( ; selector; selector = selector->tagHistory()) {
-        if (selector->m_match == CSSSelector::Class) {
-            DescendantInvalidationSet& invalidationSet = ensureClassInvalidationSet(selector->value());
+    for (const CSSSelector* current = &selector ; current; current = current->tagHistory()) {
+        if (current->m_match == CSSSelector::Class) {
+            DescendantInvalidationSet& invalidationSet = ensureClassInvalidationSet(current->value());
             if (!id.isEmpty())
                 invalidationSet.addId(id);
             if (!tagName.isEmpty())
                 invalidationSet.addTagName(tagName);
-            for (HashSet<AtomicString>::const_iterator it = classes.begin(); it != classes.end(); ++it) {
+            for (Vector<AtomicString>::const_iterator it = classes.begin(); it != classes.end(); ++it) {
                 invalidationSet.addClass(*it);
             }
         }
@@ -127,70 +135,85 @@ void RuleFeatureSet::addAttributeInASelector(const AtomicString& attributeName)
     m_metadata.attrsInRules.add(attributeName);
 }
 
-
 void RuleFeatureSet::collectFeaturesFromRuleData(const RuleData& ruleData)
 {
-
     FeatureMetadata metadata;
-    collectFeaturesFromSelector(ruleData.selector(), metadata);
+    bool selectorUsesClassInvalidationSet = false;
+    if (m_targetedStyleRecalcEnabled)
+        selectorUsesClassInvalidationSet = updateClassInvalidationSets(ruleData.selector());
+
+    SelectorFeatureCollectionMode collectionMode;
+    if (selectorUsesClassInvalidationSet)
+        collectionMode = DontProcessClasses;
+    else
+        collectionMode = ProcessClasses;
+    collectFeaturesFromSelector(ruleData.selector(), metadata, collectionMode);
     m_metadata.add(metadata);
-    if (RuntimeEnabledFeatures::targetedStyleRecalcEnabled()) {
-        bool selectorUsesClassInvalidationSet = updateClassInvalidationSets(ruleData.selector());
-        if (!selectorUsesClassInvalidationSet) {
-            for (HashSet<AtomicString>::const_iterator it = metadata.classesInRules.begin(); it != metadata.classesInRules.end(); ++it) {
-                DescendantInvalidationSet& invalidationSet = ensureClassInvalidationSet(*it);
-                invalidationSet.setWholeSubtreeInvalid();
-            }
-        }
-    }
+
     if (metadata.foundSiblingSelector)
         siblingRules.append(RuleFeature(ruleData.rule(), ruleData.selectorIndex(), ruleData.hasDocumentSecurityOrigin()));
     if (ruleData.containsUncommonAttributeSelector())
         uncommonAttributeRules.append(RuleFeature(ruleData.rule(), ruleData.selectorIndex(), ruleData.hasDocumentSecurityOrigin()));
 }
 
+bool RuleFeatureSet::classInvalidationRequiresSubtreeRecalc(const AtomicString& className)
+{
+    DescendantInvalidationSet* set = m_classInvalidationSets.get(className);
+    return set && set->wholeSubtreeInvalid();
+}
+
 DescendantInvalidationSet& RuleFeatureSet::ensureClassInvalidationSet(const AtomicString& className)
 {
     InvalidationSetMap::AddResult addResult = m_classInvalidationSets.add(className, 0);
     if (addResult.isNewEntry)
-        addResult.iterator->value = DescendantInvalidationSet::create();
-    return *addResult.iterator->value;
+        addResult.storedValue->value = DescendantInvalidationSet::create();
+    return *addResult.storedValue->value;
 }
 
-void RuleFeatureSet::collectFeaturesFromSelector(const CSSSelector* selector)
+void RuleFeatureSet::collectFeaturesFromSelector(const CSSSelector& selector)
 {
-    collectFeaturesFromSelector(selector, m_metadata);
+    collectFeaturesFromSelector(selector, m_metadata, ProcessClasses);
 }
 
-void RuleFeatureSet::collectFeaturesFromSelector(const CSSSelector* selector, RuleFeatureSet::FeatureMetadata& metadata)
+void RuleFeatureSet::collectFeaturesFromSelector(const CSSSelector& selector, RuleFeatureSet::FeatureMetadata& metadata, SelectorFeatureCollectionMode collectionMode)
 {
-    for (; selector; selector = selector->tagHistory()) {
-        if (selector->m_match == CSSSelector::Id)
-            metadata.idsInRules.add(selector->value());
-        else if (selector->m_match == CSSSelector::Class)
-            metadata.classesInRules.add(selector->value());
-        else if (selector->isAttributeSelector())
-            metadata.attrsInRules.add(selector->attribute().localName());
-
-        if (selector->pseudoType() == CSSSelector::PseudoFirstLine)
+    unsigned maxDirectAdjacentSelectors = 0;
+
+    for (const CSSSelector* current = &selector; current; current = current->tagHistory()) {
+        if (current->m_match == CSSSelector::Id) {
+            metadata.idsInRules.add(current->value());
+        } else if (current->m_match == CSSSelector::Class && collectionMode == ProcessClasses) {
+            DescendantInvalidationSet& invalidationSet = ensureClassInvalidationSet(current->value());
+            invalidationSet.setWholeSubtreeInvalid();
+        } else if (current->isAttributeSelector()) {
+            metadata.attrsInRules.add(current->attribute().localName());
+        }
+        if (current->pseudoType() == CSSSelector::PseudoFirstLine)
             metadata.usesFirstLineRules = true;
-        if (selector->isDirectAdjacentSelector())
-            metadata.maxDirectAdjacentSelectors++;
-        if (selector->isSiblingSelector())
+        if (current->isDirectAdjacentSelector()) {
+            maxDirectAdjacentSelectors++;
+        } else if (maxDirectAdjacentSelectors) {
+            if (maxDirectAdjacentSelectors > metadata.maxDirectAdjacentSelectors)
+                metadata.maxDirectAdjacentSelectors = maxDirectAdjacentSelectors;
+            maxDirectAdjacentSelectors = 0;
+        }
+        if (current->isSiblingSelector())
             metadata.foundSiblingSelector = true;
 
-        collectFeaturesFromSelectorList(selector->selectorList(), metadata);
+        collectFeaturesFromSelectorList(current->selectorList(), metadata, collectionMode);
     }
+
+    ASSERT(!maxDirectAdjacentSelectors);
 }
 
-void RuleFeatureSet::collectFeaturesFromSelectorList(const CSSSelectorList* selectorList, RuleFeatureSet::FeatureMetadata& metadata)
+void RuleFeatureSet::collectFeaturesFromSelectorList(const CSSSelectorList* selectorList, RuleFeatureSet::FeatureMetadata& metadata, SelectorFeatureCollectionMode collectionMode)
 {
     if (!selectorList)
         return;
 
-    for (const CSSSelector* selector = selectorList->first(); selector; selector = CSSSelectorList::next(selector)) {
+    for (const CSSSelector* selector = selectorList->first(); selector; selector = CSSSelectorList::next(*selector)) {
         for (const CSSSelector* subSelector = selector; subSelector; subSelector = subSelector->tagHistory())
-            collectFeaturesFromSelector(subSelector, metadata);
+            collectFeaturesFromSelector(*subSelector, metadata, collectionMode);
     }
 }
 
@@ -202,9 +225,6 @@ void RuleFeatureSet::FeatureMetadata::add(const FeatureMetadata& other)
     HashSet<AtomicString>::const_iterator end = other.idsInRules.end();
     for (HashSet<AtomicString>::const_iterator it = other.idsInRules.begin(); it != end; ++it)
         idsInRules.add(*it);
-    end = other.classesInRules.end();
-    for (HashSet<AtomicString>::const_iterator it = other.classesInRules.begin(); it != end; ++it)
-        classesInRules.add(*it);
     end = other.attrsInRules.end();
     for (HashSet<AtomicString>::const_iterator it = other.attrsInRules.begin(); it != end; ++it)
         attrsInRules.add(*it);
@@ -214,7 +234,6 @@ void RuleFeatureSet::FeatureMetadata::clear()
 {
 
     idsInRules.clear();
-    classesInRules.clear();
     attrsInRules.clear();
     usesFirstLineRules = false;
     foundSiblingSelector = false;
@@ -240,4 +259,170 @@ void RuleFeatureSet::clear()
     uncommonAttributeRules.clear();
 }
 
+void RuleFeatureSet::scheduleStyleInvalidationForClassChange(const SpaceSplitString& changedClasses, Element* element)
+{
+    if (computeInvalidationSetsForClassChange(changedClasses, element)) {
+        // FIXME: remove eager calls to setNeedsStyleRecalc here, and instead reuse the invalidation tree walk.
+        // This code remains for now out of conservatism about avoiding performance regressions before TargetedStyleRecalc is launched.
+        element->setNeedsStyleRecalc(SubtreeStyleChange);
+    }
+}
+
+void RuleFeatureSet::scheduleStyleInvalidationForClassChange(const SpaceSplitString& oldClasses, const SpaceSplitString& newClasses, Element* element)
+{
+    if (computeInvalidationSetsForClassChange(oldClasses, newClasses, element)) {
+        // FIXME: remove eager calls to setNeedsStyleRecalc here, and instead reuse the invalidation tree walk.
+        // This code remains for now out of conservatism about avoiding performance regressions before TargetedStyleRecalc is launched.
+        element->setNeedsStyleRecalc(SubtreeStyleChange);
+    }
+}
+
+bool RuleFeatureSet::computeInvalidationSetsForClassChange(const SpaceSplitString& changedClasses, Element* element)
+{
+    unsigned changedSize = changedClasses.size();
+    for (unsigned i = 0; i < changedSize; ++i) {
+        if (classInvalidationRequiresSubtreeRecalc(changedClasses[i]))
+            return true;
+        addClassToInvalidationSet(changedClasses[i], element);
+    }
+    return false;
+}
+
+bool RuleFeatureSet::computeInvalidationSetsForClassChange(const SpaceSplitString& oldClasses, const SpaceSplitString& newClasses, Element* element)
+{
+    if (!oldClasses.size())
+        return computeInvalidationSetsForClassChange(newClasses, element);
+
+    // Class vectors tend to be very short. This is faster than using a hash table.
+    BitVector remainingClassBits;
+    remainingClassBits.ensureSize(oldClasses.size());
+
+    for (unsigned i = 0; i < newClasses.size(); ++i) {
+        bool found = false;
+        for (unsigned j = 0; j < oldClasses.size(); ++j) {
+            if (newClasses[i] == oldClasses[j]) {
+                // Mark each class that is still in the newClasses so we can skip doing
+                // an n^2 search below when looking for removals. We can't break from
+                // this loop early since a class can appear more than once.
+                remainingClassBits.quickSet(j);
+                found = true;
+            }
+        }
+        // Class was added.
+        if (!found) {
+            if (classInvalidationRequiresSubtreeRecalc(newClasses[i]))
+                return true;
+            addClassToInvalidationSet(newClasses[i], element);
+        }
+    }
+
+    for (unsigned i = 0; i < oldClasses.size(); ++i) {
+        if (remainingClassBits.quickGet(i))
+            continue;
+
+        // Class was removed.
+        if (classInvalidationRequiresSubtreeRecalc(oldClasses[i]))
+            return true;
+        addClassToInvalidationSet(oldClasses[i], element);
+    }
+    return false;
+}
+
+void RuleFeatureSet::addClassToInvalidationSet(const AtomicString& className, Element* element)
+{
+    if (DescendantInvalidationSet* invalidationSet = m_classInvalidationSets.get(className)) {
+        ensurePendingInvalidationList(element).append(invalidationSet);
+        element->setNeedsStyleInvalidation();
+    }
+}
+
+RuleFeatureSet::InvalidationList& RuleFeatureSet::ensurePendingInvalidationList(Element* element)
+{
+    PendingInvalidationMap::AddResult addResult = m_pendingInvalidationMap.add(element, 0);
+    if (addResult.isNewEntry)
+        addResult.storedValue->value = new InvalidationList;
+    return *addResult.storedValue->value;
+}
+
+void RuleFeatureSet::computeStyleInvalidation(Document& document)
+{
+    Vector<AtomicString> invalidationClasses;
+    if (Element* documentElement = document.documentElement()) {
+        if (documentElement->childNeedsStyleInvalidation()) {
+            invalidateStyleForClassChange(documentElement, invalidationClasses, false);
+        }
+    }
+    document.clearChildNeedsStyleInvalidation();
+    m_pendingInvalidationMap.clear();
+}
+
+bool RuleFeatureSet::invalidateStyleForClassChangeOnChildren(Element* element, Vector<AtomicString>& invalidationClasses, bool foundInvalidationSet)
+{
+    bool someChildrenNeedStyleRecalc = false;
+    for (ShadowRoot* root = element->youngestShadowRoot(); root; root = root->olderShadowRoot()) {
+        for (Node* child = root->firstChild(); child; child = child->nextSibling()) {
+            if (child->isElementNode()) {
+                Element* childElement = toElement(child);
+                bool childRecalced = invalidateStyleForClassChange(childElement, invalidationClasses, foundInvalidationSet);
+                someChildrenNeedStyleRecalc = someChildrenNeedStyleRecalc || childRecalced;
+            }
+        }
+    }
+    for (Node* child = element->firstChild(); child; child = child->nextSibling()) {
+        if (child->isElementNode()) {
+            Element* childElement = toElement(child);
+            bool childRecalced = invalidateStyleForClassChange(childElement, invalidationClasses, foundInvalidationSet);
+            someChildrenNeedStyleRecalc = someChildrenNeedStyleRecalc || childRecalced;
+        }
+    }
+    return someChildrenNeedStyleRecalc;
+}
+
+bool RuleFeatureSet::invalidateStyleForClassChange(Element* element, Vector<AtomicString>& invalidationClasses, bool foundInvalidationSet)
+{
+    int oldSize = invalidationClasses.size();
+    if (element->needsStyleInvalidation()) {
+        if (InvalidationList* invalidationList = m_pendingInvalidationMap.get(element)) {
+            foundInvalidationSet = true;
+            for (InvalidationList::const_iterator it = invalidationList->begin(); it != invalidationList->end(); ++it) {
+                if ((*it)->wholeSubtreeInvalid()) {
+                    element->setNeedsStyleRecalc(SubtreeStyleChange);
+                    invalidationClasses.remove(oldSize, invalidationClasses.size() - oldSize);
+                    element->clearChildNeedsStyleInvalidation();
+                    return true;
+                }
+                (*it)->getClasses(invalidationClasses);
+            }
+        }
+    }
+
+    bool thisElementNeedsStyleRecalc = false;
+
+    if (element->hasClass()) {
+        const SpaceSplitString& classNames = element->classNames();
+        for (Vector<AtomicString>::const_iterator it = invalidationClasses.begin(); it != invalidationClasses.end(); ++it) {
+            if (classNames.contains(*it)) {
+                thisElementNeedsStyleRecalc = true;
+                break;
+            }
+        }
+    }
+
+    // foundInvalidationSet will be true if we are in a subtree of a node with a DescendantInvalidationSet on it.
+    // We need to check all nodes in the subtree of such a node.
+    if (foundInvalidationSet || element->childNeedsStyleInvalidation()) {
+        bool someChildrenNeedStyleRecalc = invalidateStyleForClassChangeOnChildren(element, invalidationClasses, foundInvalidationSet);
+        // We only need to possibly recalc style if this node is in the subtree of a node with a DescendantInvalidationSet on it.
+        if (foundInvalidationSet)
+            thisElementNeedsStyleRecalc = thisElementNeedsStyleRecalc || someChildrenNeedStyleRecalc;
+    }
+
+    if (thisElementNeedsStyleRecalc)
+        element->setNeedsStyleRecalc(LocalStyleChange);
+
+    invalidationClasses.remove(oldSize, invalidationClasses.size() - oldSize);
+    element->clearChildNeedsStyleInvalidation();
+    return thisElementNeedsStyleRecalc;
+}
+
 } // namespace WebCore