| Index: Source/core/css/RuleFeature.cpp
 | 
| diff --git a/Source/core/css/RuleFeature.cpp b/Source/core/css/RuleFeature.cpp
 | 
| index 8dfb8cdac6adf67b7ea6c45c4c51d41b4e9311b2..9f337db19e92ff2bbabe0d8b82478a91aee50312 100644
 | 
| --- a/Source/core/css/RuleFeature.cpp
 | 
| +++ b/Source/core/css/RuleFeature.cpp
 | 
| @@ -34,6 +34,12 @@
 | 
|  #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 {
 | 
|  
 | 
| @@ -55,9 +61,8 @@ static bool supportsClassDescendantInvalidation(const CSSSelector* selector)
 | 
|          // 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
 | 
| @@ -240,4 +245,175 @@ void RuleFeatureSet::clear()
 | 
|      uncommonAttributeRules.clear();
 | 
|  }
 | 
|  
 | 
| +void RuleFeatureSet::scheduleStyleInvalidationForClassChange(const SpaceSplitString& changedClasses, Element* element)
 | 
| +{
 | 
| +    if (scheduleStyleInvalidationForClassChangeInternal(changedClasses, element)
 | 
| +        && !RuntimeEnabledFeatures::targetedStyleRecalcEnabled())
 | 
| +        element->setNeedsStyleRecalc();
 | 
| +}
 | 
| +
 | 
| +void RuleFeatureSet::scheduleStyleInvalidationForClassChange(const SpaceSplitString& oldClasses, const SpaceSplitString& newClasses, Element* element)
 | 
| +{
 | 
| +    if (scheduleStyleInvalidationForClassChangeInternal(oldClasses, newClasses, element)
 | 
| +        && !RuntimeEnabledFeatures::targetedStyleRecalcEnabled())
 | 
| +        element->setNeedsStyleRecalc();
 | 
| +}
 | 
| +
 | 
| +bool RuleFeatureSet::scheduleStyleInvalidationForClassChangeInternal(const SpaceSplitString& changedClasses, Element* element)
 | 
| +{
 | 
| +    unsigned changedSize = changedClasses.size();
 | 
| +    for (unsigned i = 0; i < changedSize; ++i) {
 | 
| +        if (RuntimeEnabledFeatures::targetedStyleRecalcEnabled())
 | 
| +            addClassToInvalidationSet(changedClasses[i], element);
 | 
| +        else if (m_metadata.classesInRules.contains(changedClasses[i]))
 | 
| +            return true;
 | 
| +    }
 | 
| +    return false;
 | 
| +}
 | 
| +
 | 
| +bool RuleFeatureSet::scheduleStyleInvalidationForClassChangeInternal(const SpaceSplitString& oldClasses, const SpaceSplitString& newClasses, Element* element)
 | 
| +{
 | 
| +    if (!oldClasses.size())
 | 
| +        return scheduleStyleInvalidationForClassChangeInternal(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 (RuntimeEnabledFeatures::targetedStyleRecalcEnabled())
 | 
| +                addClassToInvalidationSet(newClasses[i], element);
 | 
| +            else if (m_metadata.classesInRules.contains(newClasses[i]))
 | 
| +                return true;
 | 
| +        }
 | 
| +    }
 | 
| +
 | 
| +    for (unsigned i = 0; i < oldClasses.size(); ++i) {
 | 
| +        if (remainingClassBits.quickGet(i))
 | 
| +            continue;
 | 
| +
 | 
| +        // Class was removed.
 | 
| +        if (RuntimeEnabledFeatures::targetedStyleRecalcEnabled())
 | 
| +            addClassToInvalidationSet(oldClasses[i], element);
 | 
| +        else if (m_metadata.classesInRules.contains(oldClasses[i]))
 | 
| +            return true;
 | 
| +    }
 | 
| +    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.iterator->value = new InvalidationList;
 | 
| +    return *addResult.iterator->value;
 | 
| +}
 | 
| +
 | 
| +void RuleFeatureSet::computeStyleInvalidation(Document& document)
 | 
| +{
 | 
| +    Vector<AtomicString> invalidationClasses;
 | 
| +    for (Node* child = document.firstChild(); child; child = child->nextSibling()) {
 | 
| +        if (child->isElementNode() && child->childNeedsStyleInvalidation()) {
 | 
| +            Element* childElement = toElement(child);
 | 
| +            computeStyleInvalidationInternal(childElement, invalidationClasses, false);
 | 
| +        }
 | 
| +    }
 | 
| +    document.clearChildNeedsStyleInvalidation();
 | 
| +    m_pendingInvalidationMap.clear();
 | 
| +}
 | 
| +
 | 
| +bool RuleFeatureSet::computeStyleInvalidationForChildren(Element* element, Vector<AtomicString>& invalidationClasses, bool foundInvalidationSet)
 | 
| +{
 | 
| +    bool someChildrenNeedStyleRecalc = false;
 | 
| +    for (Node* child = element->firstChild(); child; child = child->nextSibling()) {
 | 
| +        if (child->isElementNode()) {
 | 
| +            Element* childElement = toElement(child);
 | 
| +            bool childRecalced = computeStyleInvalidationInternal(childElement, invalidationClasses, foundInvalidationSet);
 | 
| +            someChildrenNeedStyleRecalc = someChildrenNeedStyleRecalc || childRecalced;
 | 
| +        }
 | 
| +    }
 | 
| +    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 = computeStyleInvalidationInternal(childElement, invalidationClasses, foundInvalidationSet);
 | 
| +                someChildrenNeedStyleRecalc = someChildrenNeedStyleRecalc || childRecalced;
 | 
| +            }
 | 
| +        }
 | 
| +    }
 | 
| +    return someChildrenNeedStyleRecalc;
 | 
| +}
 | 
| +
 | 
| +bool RuleFeatureSet::computeStyleInvalidationInternal(Element* element, Vector<AtomicString>& invalidationClasses, bool foundInvalidationSet)
 | 
| +{
 | 
| +    int oldSize = invalidationClasses.size();
 | 
| +    if (element->needsStyleInvalidation()) {
 | 
| +        if (InvalidationList* invalidationList = m_pendingInvalidationMap.get(element)) {
 | 
| +            foundInvalidationSet = true;
 | 
| +            bool recalcWholeSubtree = false;
 | 
| +            for (InvalidationList::const_iterator it = invalidationList->begin(); it != invalidationList->end(); ++it) {
 | 
| +                if ((*it)->wholeSubtreeInvalid()) {
 | 
| +                    recalcWholeSubtree = true;
 | 
| +                    break;
 | 
| +                }
 | 
| +                (*it)->getClasses(invalidationClasses);
 | 
| +            }
 | 
| +            if (recalcWholeSubtree) {
 | 
| +                element->setNeedsStyleRecalc(SubtreeStyleChange);
 | 
| +                invalidationClasses.remove(oldSize, invalidationClasses.size() - oldSize);
 | 
| +                element->clearChildNeedsStyleInvalidation();
 | 
| +                return true;
 | 
| +            }
 | 
| +        }
 | 
| +    }
 | 
| +
 | 
| +    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 = computeStyleInvalidationForChildren(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
 | 
| 
 |