Optimize matching of common pseudo classes
authorantti@apple.com <antti@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Mon, 26 Sep 2011 18:53:48 +0000 (18:53 +0000)
committerantti@apple.com <antti@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Mon, 26 Sep 2011 18:53:48 +0000 (18:53 +0000)
https://bugs.webkit.org/show_bug.cgi?id=68633

Reviewed by Dave Hyatt, Darin Adler, Dimitri Glazkov.

:link, :visited and :focus are quite common. They often used as univeral selectors (including in our
default stylesheet) so we try to match them for all elements in the document. They take always the
slow matching path. In addition we match link styles twice due to visited link pseudo style generation
so the overhead is doubled. As a result substantial portion of our style matching time is spent
dealing with these pseudo classes.

This patch adds new lists to RuleSet for common pseudo class rules. The rules on the lists are only checked
if the element has approprate type and stat. ases where the rightmost pseudo class can then be rejected immediately.
We can also enable the fast path checking for the rest of the selector in many cases.

This seems to be >30% progression in selector matching performance with typical style sheets. It saves ~0.9s
when loading the full HTML5 spec.

* css/CSSStyleSelector.cpp:
(WebCore::RuleData::hasRightmostSelectorMatchingHTMLBasedOnRuleHash):
(WebCore::RuleSet::idRules):
(WebCore::RuleSet::classRules):
(WebCore::RuleSet::tagRules):
(WebCore::RuleSet::shadowPseudoElementRules):
(WebCore::RuleSet::linkPseudoClassRules):
(WebCore::RuleSet::visitedPseudoClassRules):
(WebCore::RuleSet::focusPseudoClassRules):
(WebCore::RuleSet::universalRules):
(WebCore::RuleSet::pageRules):

    Add a new lists, some stylistic renamings.

(WebCore::CSSStyleSelector::matchRules):

    New link and focus checks.

(WebCore::CSSStyleSelector::matchRulesForList):
(WebCore::CSSStyleSelector::checkSelector):

    Inline the rightmost selector tag checking, skip if unnecessary.

(WebCore::isSelectorMatchingHTMLBasedOnRuleHash):

    Common pseudo classes now match based on early filtering (though it is not a hash in this case).

(WebCore::RuleData::RuleData):
(WebCore::RuleSet::~RuleSet):
(WebCore::RuleSet::addRule):

    Sort pseudo classes to new lists.

(WebCore::RuleSet::collectFeatures):
(WebCore::RuleSet::shrinkToFit):
(WebCore::CSSStyleSelector::matchPageRules):
* css/SelectorChecker.cpp:
(WebCore::SelectorChecker::checkSelector):

    Adopt to expanded fast path (this is used by querySelectorAll).

(WebCore::SelectorChecker::fastCheckRightmostSelector):
(WebCore::SelectorChecker::fastCheckSelector):

    Rightmost selector is now checked differently than the rest. RuleSet based selection in CSSStyleSelector
    is equivalent to fastCheckRightmostSelector().

(WebCore::isFastCheckableRelation):
(WebCore::isFastCheckableMatch):
(WebCore::isFastCheckableRightmostSelector):
(WebCore::SelectorChecker::isFastCheckableSelector):
(WebCore::SelectorChecker::checkOneSelector):
(WebCore::SelectorChecker::commonPseudoClassSelectorMatches):
(WebCore::SelectorChecker::isFrameFocused):
* css/SelectorChecker.h:
(WebCore::SelectorChecker::isCommonPseudoClassSelector):
(WebCore::SelectorChecker::linkMatchesVisitedPseudoClass):
(WebCore::SelectorChecker::matchesFocusPseudoClass):
(WebCore::SelectorChecker::tagMatches):

    Refactor a bunch of shared checks into functions.

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

Source/WebCore/ChangeLog
Source/WebCore/css/CSSStyleSelector.cpp
Source/WebCore/css/SelectorChecker.cpp
Source/WebCore/css/SelectorChecker.h

index e03317b..2846b1f 100644 (file)
@@ -1,3 +1,85 @@
+2011-09-26  Antti Koivisto  <antti@apple.com>
+
+        Optimize matching of common pseudo classes
+        https://bugs.webkit.org/show_bug.cgi?id=68633
+
+        Reviewed by Dave Hyatt, Darin Adler, Dimitri Glazkov.
+        
+        :link, :visited and :focus are quite common. They often used as univeral selectors (including in our
+        default stylesheet) so we try to match them for all elements in the document. They take always the 
+        slow matching path. In addition we match link styles twice due to visited link pseudo style generation
+        so the overhead is doubled. As a result substantial portion of our style matching time is spent 
+        dealing with these pseudo classes. 
+        
+        This patch adds new lists to RuleSet for common pseudo class rules. The rules on the lists are only checked
+        if the element has approprate type and stat. ases where the rightmost pseudo class can then be rejected immediately.
+        We can also enable the fast path checking for the rest of the selector in many cases.
+    
+        This seems to be >30% progression in selector matching performance with typical style sheets. It saves ~0.9s
+        when loading the full HTML5 spec.
+
+        * css/CSSStyleSelector.cpp:
+        (WebCore::RuleData::hasRightmostSelectorMatchingHTMLBasedOnRuleHash):
+        (WebCore::RuleSet::idRules):
+        (WebCore::RuleSet::classRules):
+        (WebCore::RuleSet::tagRules):
+        (WebCore::RuleSet::shadowPseudoElementRules):
+        (WebCore::RuleSet::linkPseudoClassRules):
+        (WebCore::RuleSet::visitedPseudoClassRules):
+        (WebCore::RuleSet::focusPseudoClassRules):
+        (WebCore::RuleSet::universalRules):
+        (WebCore::RuleSet::pageRules):
+        
+            Add a new lists, some stylistic renamings.
+    
+        (WebCore::CSSStyleSelector::matchRules):
+        
+            New link and focus checks.
+
+        (WebCore::CSSStyleSelector::matchRulesForList):
+        (WebCore::CSSStyleSelector::checkSelector):
+        
+            Inline the rightmost selector tag checking, skip if unnecessary.
+        
+        (WebCore::isSelectorMatchingHTMLBasedOnRuleHash):
+        
+            Common pseudo classes now match based on early filtering (though it is not a hash in this case).
+
+        (WebCore::RuleData::RuleData):
+        (WebCore::RuleSet::~RuleSet):
+        (WebCore::RuleSet::addRule):
+        
+            Sort pseudo classes to new lists.
+        
+        (WebCore::RuleSet::collectFeatures):
+        (WebCore::RuleSet::shrinkToFit):
+        (WebCore::CSSStyleSelector::matchPageRules):
+        * css/SelectorChecker.cpp:
+        (WebCore::SelectorChecker::checkSelector):
+        
+            Adopt to expanded fast path (this is used by querySelectorAll).
+
+        (WebCore::SelectorChecker::fastCheckRightmostSelector):
+        (WebCore::SelectorChecker::fastCheckSelector):
+        
+            Rightmost selector is now checked differently than the rest. RuleSet based selection in CSSStyleSelector
+            is equivalent to fastCheckRightmostSelector().
+        
+        (WebCore::isFastCheckableRelation):
+        (WebCore::isFastCheckableMatch):
+        (WebCore::isFastCheckableRightmostSelector):
+        (WebCore::SelectorChecker::isFastCheckableSelector):
+        (WebCore::SelectorChecker::checkOneSelector):
+        (WebCore::SelectorChecker::commonPseudoClassSelectorMatches):
+        (WebCore::SelectorChecker::isFrameFocused):
+        * css/SelectorChecker.h:
+        (WebCore::SelectorChecker::isCommonPseudoClassSelector):
+        (WebCore::SelectorChecker::linkMatchesVisitedPseudoClass):
+        (WebCore::SelectorChecker::matchesFocusPseudoClass):
+        (WebCore::SelectorChecker::tagMatches):
+        
+            Refactor a bunch of shared checks into functions.
+
 2011-09-12  Ryosuke Niwa  <rniwa@webkit.org>
 
         REGRESSION(r74971): Selection doesn't work correctly in BiDi Text
index 182770d..692f46a 100644 (file)
@@ -188,7 +188,7 @@ public:
     
     bool hasFastCheckableSelector() const { return m_hasFastCheckableSelector; }
     bool hasMultipartSelector() const { return m_hasMultipartSelector; }
-    bool hasTopSelectorMatchingHTMLBasedOnRuleHash() const { return m_hasTopSelectorMatchingHTMLBasedOnRuleHash; }
+    bool hasRightmostSelectorMatchingHTMLBasedOnRuleHash() const { return m_hasRightmostSelectorMatchingHTMLBasedOnRuleHash; }
     unsigned specificity() const { return m_specificity; }
     
     // Try to balance between memory usage (there can be lots of RuleData objects) and good filtering performance.
@@ -202,7 +202,7 @@ private:
     unsigned m_position : 29;
     bool m_hasFastCheckableSelector : 1;
     bool m_hasMultipartSelector : 1;
-    bool m_hasTopSelectorMatchingHTMLBasedOnRuleHash : 1;
+    bool m_hasRightmostSelectorMatchingHTMLBasedOnRuleHash : 1;
     // Use plain array instead of a Vector to minimize memory overhead.
     unsigned m_descendantSelectorIdentifierHashes[maximumIdentifierCount];
 };
@@ -227,18 +227,24 @@ public:
 
     void collectFeatures(CSSStyleSelector::Features&) const;
     
-    const Vector<RuleData>* getIDRules(AtomicStringImpl* key) const { return m_idRules.get(key); }
-    const Vector<RuleData>* getClassRules(AtomicStringImpl* key) const { return m_classRules.get(key); }
-    const Vector<RuleData>* getTagRules(AtomicStringImpl* key) const { return m_tagRules.get(key); }
-    const Vector<RuleData>* getPseudoRules(AtomicStringImpl* key) const { return m_pseudoRules.get(key); }
-    const Vector<RuleData>* getUniversalRules() const { return &m_universalRules; }
-    const Vector<RuleData>* getPageRules() const { return &m_pageRules; }
+    const Vector<RuleData>* idRules(AtomicStringImpl* key) const { return m_idRules.get(key); }
+    const Vector<RuleData>* classRules(AtomicStringImpl* key) const { return m_classRules.get(key); }
+    const Vector<RuleData>* tagRules(AtomicStringImpl* key) const { return m_tagRules.get(key); }
+    const Vector<RuleData>* shadowPseudoElementRules(AtomicStringImpl* key) const { return m_shadowPseudoElementRules.get(key); }
+    const Vector<RuleData>* linkPseudoClassRules() const { return &m_linkPseudoClassRules; }
+    const Vector<RuleData>* visitedPseudoClassRules() const { return &m_visitedPseudoClassRules; }
+    const Vector<RuleData>* focusPseudoClassRules() const { return &m_focusPseudoClassRules; }
+    const Vector<RuleData>* universalRules() const { return &m_universalRules; }
+    const Vector<RuleData>* pageRules() const { return &m_pageRules; }
     
 public:
     AtomRuleMap m_idRules;
     AtomRuleMap m_classRules;
     AtomRuleMap m_tagRules;
-    AtomRuleMap m_pseudoRules;
+    AtomRuleMap m_shadowPseudoElementRules;
+    Vector<RuleData> m_linkPseudoClassRules;
+    Vector<RuleData> m_visitedPseudoClassRules;
+    Vector<RuleData> m_focusPseudoClassRules;
     Vector<RuleData> m_universalRules;
     Vector<RuleData> m_pageRules;
     unsigned m_ruleCount;
@@ -520,21 +526,29 @@ void CSSStyleSelector::matchRules(RuleSet* rules, int& firstRuleIndex, int& last
     // We need to collect the rules for id, class, tag, and everything else into a buffer and
     // then sort the buffer.
     if (m_element->hasID())
-        matchRulesForList(rules->getIDRules(m_element->idForStyleResolution().impl()), firstRuleIndex, lastRuleIndex, includeEmptyRules);
+        matchRulesForList(rules->idRules(m_element->idForStyleResolution().impl()), firstRuleIndex, lastRuleIndex, includeEmptyRules);
     if (m_element->hasClass()) {
         ASSERT(m_styledElement);
         const SpaceSplitString& classNames = m_styledElement->classNames();
         size_t size = classNames.size();
         for (size_t i = 0; i < size; ++i)
-            matchRulesForList(rules->getClassRules(classNames[i].impl()), firstRuleIndex, lastRuleIndex, includeEmptyRules);
+            matchRulesForList(rules->classRules(classNames[i].impl()), firstRuleIndex, lastRuleIndex, includeEmptyRules);
     }
     const AtomicString& pseudoId = m_element->shadowPseudoId();
     if (!pseudoId.isEmpty()) {
         ASSERT(m_styledElement);
-        matchRulesForList(rules->getPseudoRules(pseudoId.impl()), firstRuleIndex, lastRuleIndex, includeEmptyRules);
+        matchRulesForList(rules->shadowPseudoElementRules(pseudoId.impl()), firstRuleIndex, lastRuleIndex, includeEmptyRules);
     }
-    matchRulesForList(rules->getTagRules(m_element->localName().impl()), firstRuleIndex, lastRuleIndex, includeEmptyRules);
-    matchRulesForList(rules->getUniversalRules(), firstRuleIndex, lastRuleIndex, includeEmptyRules);
+    if (m_element->isLink()) {
+        if (m_checker.linkMatchesVisitedPseudoClass(m_element))
+            matchRulesForList(rules->visitedPseudoClassRules(), firstRuleIndex, lastRuleIndex, includeEmptyRules);
+        else
+            matchRulesForList(rules->linkPseudoClassRules(), firstRuleIndex, lastRuleIndex, includeEmptyRules);
+    }
+    if (m_checker.matchesFocusPseudoClass(m_element))
+        matchRulesForList(rules->focusPseudoClassRules(), firstRuleIndex, lastRuleIndex, includeEmptyRules);
+    matchRulesForList(rules->tagRules(m_element->localName().impl()), firstRuleIndex, lastRuleIndex, includeEmptyRules);
+    matchRulesForList(rules->universalRules(), firstRuleIndex, lastRuleIndex, includeEmptyRules);
     
     // If we didn't match any rules, we're done.
     if (m_matchedRules.isEmpty())
@@ -1786,14 +1800,17 @@ inline bool CSSStyleSelector::checkSelector(const RuleData& ruleData)
 
     // Let the slow path handle SVG as it has some additional rules regarding shadow trees.
     if (ruleData.hasFastCheckableSelector() && !m_element->isSVGElement()) {
-        // We know this selector does not include any pseudo selectors.
+        // We know this selector does not include any pseudo elements.
         if (m_checker.pseudoStyle() != NOPSEUDO)
             return false;
         // We know a sufficiently simple single part selector matches simply because we found it from the rule hash.
         // This is limited to HTML only so we don't need to check the namespace.
-        if (ruleData.hasTopSelectorMatchingHTMLBasedOnRuleHash() && !ruleData.hasMultipartSelector() && m_element->isHTMLElement())
-            return true;
-        return SelectorChecker::fastCheckSelector(ruleData.selector(), m_element);
+        if (ruleData.hasRightmostSelectorMatchingHTMLBasedOnRuleHash() && m_element->isHTMLElement()) {
+            if (!ruleData.hasMultipartSelector())
+                return true;
+        } else if (!SelectorChecker::tagMatches(m_element, ruleData.selector()))
+            return false;
+        return m_checker.fastCheckSelector(ruleData.selector(), m_element);
     }
 
     // Slow path.
@@ -1814,9 +1831,11 @@ static inline bool isSelectorMatchingHTMLBasedOnRuleHash(const CSSSelector* sele
         return false;
     if (selector->m_match == CSSSelector::None)
         return true;
-    if (selector->m_match != CSSSelector::Id && selector->m_match != CSSSelector::Class)
+    if (selector->tag() != starAtom)
         return false;
-    return selector->tag() == starAtom;
+    if (SelectorChecker::isCommonPseudoClassSelector(selector))
+        return true;
+    return selector->m_match == CSSSelector::Id || selector->m_match == CSSSelector::Class;
 }
 
 RuleData::RuleData(CSSStyleRule* rule, CSSSelector* selector, unsigned position)
@@ -1826,7 +1845,7 @@ RuleData::RuleData(CSSStyleRule* rule, CSSSelector* selector, unsigned position)
     , m_position(position)
     , m_hasFastCheckableSelector(SelectorChecker::isFastCheckableSelector(selector))
     , m_hasMultipartSelector(selector->tagHistory())
-    , m_hasTopSelectorMatchingHTMLBasedOnRuleHash(isSelectorMatchingHTMLBasedOnRuleHash(selector))
+    , m_hasRightmostSelectorMatchingHTMLBasedOnRuleHash(isSelectorMatchingHTMLBasedOnRuleHash(selector))
 {
     SelectorChecker::collectIdentifierHashes(m_selector, m_descendantSelectorIdentifierHashes, maximumIdentifierCount);
 }
@@ -1841,7 +1860,7 @@ RuleSet::~RuleSet()
 { 
     deleteAllValues(m_idRules);
     deleteAllValues(m_classRules);
-    deleteAllValues(m_pseudoRules);
+    deleteAllValues(m_shadowPseudoElementRules);
     deleteAllValues(m_tagRules);
 }
 
@@ -1868,12 +1887,32 @@ void RuleSet::addRule(CSSStyleRule* rule, CSSSelector* sel)
         addToRuleSet(sel->value().impl(), m_classRules, rule, sel);
         return;
     }
-     
     if (sel->isUnknownPseudoElement()) {
-        addToRuleSet(sel->value().impl(), m_pseudoRules, rule, sel);
+        addToRuleSet(sel->value().impl(), m_shadowPseudoElementRules, rule, sel);
+        return;
+    }
+    if (SelectorChecker::isCommonPseudoClassSelector(sel)) {
+        RuleData ruleData(rule, sel, m_ruleCount++);
+        switch (sel->pseudoType()) {
+        case CSSSelector::PseudoLink:
+            m_linkPseudoClassRules.append(ruleData);
+            return;
+        case CSSSelector::PseudoVisited:
+            m_visitedPseudoClassRules.append(ruleData);
+            return;
+        case CSSSelector::PseudoAnyLink:
+            // :link and :visited are mutually exclusive. :any-link will apply if and only if one of them applies.
+            m_linkPseudoClassRules.append(ruleData);
+            m_visitedPseudoClassRules.append(ruleData);
+            return;
+        case CSSSelector::PseudoFocus:
+            m_focusPseudoClassRules.append(ruleData);
+            return;
+        default:
+            ASSERT_NOT_REACHED();
+        }
         return;
     }
-
     const AtomicString& localName = sel->tag().localName();
     if (localName != starAtom) {
         addToRuleSet(localName.impl(), m_tagRules, rule, sel);
@@ -2025,9 +2064,12 @@ void RuleSet::collectFeatures(CSSStyleSelector::Features& features) const
     end = m_tagRules.end();
     for (AtomRuleMap::const_iterator it = m_tagRules.begin(); it != end; ++it)
         collectFeaturesFromList(features, *it->second);
-    end = m_pseudoRules.end();
-    for (AtomRuleMap::const_iterator it = m_pseudoRules.begin(); it != end; ++it)
+    end = m_shadowPseudoElementRules.end();
+    for (AtomRuleMap::const_iterator it = m_shadowPseudoElementRules.begin(); it != end; ++it)
         collectFeaturesFromList(features, *it->second);
+    collectFeaturesFromList(features, m_linkPseudoClassRules);
+    collectFeaturesFromList(features, m_visitedPseudoClassRules);
+    collectFeaturesFromList(features, m_focusPseudoClassRules);
     collectFeaturesFromList(features, m_universalRules);
 }
     
@@ -2043,7 +2085,10 @@ void RuleSet::shrinkToFit()
     shrinkMapVectorsToFit(m_idRules);
     shrinkMapVectorsToFit(m_classRules);
     shrinkMapVectorsToFit(m_tagRules);
-    shrinkMapVectorsToFit(m_pseudoRules);
+    shrinkMapVectorsToFit(m_shadowPseudoElementRules);
+    m_linkPseudoClassRules.shrinkToFit();
+    m_visitedPseudoClassRules.shrinkToFit();
+    m_focusPseudoClassRules.shrinkToFit();
     m_universalRules.shrinkToFit();
     m_pageRules.shrinkToFit();
 }
@@ -2135,7 +2180,7 @@ void CSSStyleSelector::matchPageRules(RuleSet* rules, bool isLeftPage, bool isFi
     if (!rules)
         return;
 
-    matchPageRulesForList(rules->getPageRules(), isLeftPage, isFirstPage, pageName);
+    matchPageRulesForList(rules->pageRules(), isLeftPage, isFirstPage, pageName);
 
     // If we didn't match any rules, we're done.
     if (m_matchedRules.isEmpty())
index 3aeffe4..733ae11 100644 (file)
@@ -33,6 +33,7 @@
 #include "Document.h"
 #include "FocusController.h"
 #include "Frame.h"
+#include "FrameSelection.h"
 #include "HTMLFrameElementBase.h"
 #include "HTMLInputElement.h"
 #include "HTMLNames.h"
@@ -251,38 +252,21 @@ bool SelectorChecker::checkSelector(CSSSelector* sel, Element* element, bool isF
 {
     PseudoId dynamicPseudo = NOPSEUDO;
     if (isFastCheckableSelector && !element->isSVGElement()) {
-        // fastCheckSelector assumes class and id match for the top selector.
-        if (sel->m_match == CSSSelector::Class) {
-            if (!(element->hasClass() && static_cast<StyledElement*>(element)->classNames().contains(sel->value())))
-                return false;
-        } else if (sel->m_match == CSSSelector::Id) {
-            if (!(element->hasID() && element->idForStyleResolution() == sel->value()))
-                return false;
-        }
+        if (!fastCheckRightmostSelector(sel, element))
+            return false;
         return fastCheckSelector(sel, element);
     }
     return checkSelector(sel, element, dynamicPseudo, false, false) == SelectorMatches;
 }
 
 namespace {
-    
-inline bool selectorTagMatches(const Element* element, const CSSSelector* selector)
-{
-    if (!selector->hasTag())
-        return true;
-    const AtomicString& localName = selector->tag().localName();
-    if (localName != starAtom && localName != element->localName())
-        return false;
-    const AtomicString& namespaceURI = selector->tag().namespaceURI();
-    return namespaceURI == starAtom || namespaceURI == element->namespaceURI();
-}
 
 template <bool checkValue(const Element*, AtomicStringImpl*)>
 inline bool fastCheckSingleSelector(const CSSSelector*& selector, const Element*& element, const CSSSelector*& topChildOrSubselector, const Element*& topChildOrSubselectorMatchElement)
 {
     AtomicStringImpl* value = selector->value().impl();
     for (; element; element = element->parentElement()) {
-        if (checkValue(element, value) && selectorTagMatches(element, selector)) {
+        if (checkValue(element, value) && SelectorChecker::tagMatches(element, selector)) {
             if (selector->relation() == CSSSelector::Descendant)
                 topChildOrSubselector = 0;
             else if (!topChildOrSubselector) {
@@ -329,14 +313,31 @@ inline bool checkTagValue(const Element*, AtomicStringImpl*)
     
 }
 
-bool SelectorChecker::fastCheckSelector(const CSSSelector* selector, const Element* element)
+inline bool SelectorChecker::fastCheckRightmostSelector(const CSSSelector* selector, const Element* element) const
 {
     ASSERT(isFastCheckableSelector(selector));
-    
-    // The top selector requires tag check only as rule hashes have already handled id and class matches.
-    if (!selectorTagMatches(element, selector))
+
+    if (!SelectorChecker::tagMatches(element, selector))
         return false;
-    
+    switch (selector->m_match) {
+    case CSSSelector::None:
+        return true;
+    case CSSSelector::Class:
+        return element->hasClass() && static_cast<const StyledElement*>(element)->classNames().contains(selector->value());
+    case CSSSelector::Id:
+        return element->hasID() && element->idForStyleResolution() == selector->value();
+    case CSSSelector::PseudoClass:
+        return commonPseudoClassSelectorMatches(element, selector);
+    default:
+        ASSERT_NOT_REACHED();
+    }
+    return false;
+}
+
+bool SelectorChecker::fastCheckSelector(const CSSSelector* selector, const Element* element) const
+{
+    ASSERT(fastCheckRightmostSelector(selector, element));
+
     const CSSSelector* topChildOrSubselector = 0;
     const Element* topChildOrSubselectorMatchElement = 0;
     if (selector->relation() == CSSSelector::Child || selector->relation() == CSSSelector::SubSelector)
@@ -369,12 +370,31 @@ bool SelectorChecker::fastCheckSelector(const CSSSelector* selector, const Eleme
     return true;
 }
 
+static inline bool isFastCheckableRelation(CSSSelector::Relation relation)
+{
+    return relation == CSSSelector::Descendant || relation == CSSSelector::Child || relation == CSSSelector::SubSelector;
+}
+
+static inline bool isFastCheckableMatch(CSSSelector::Match match)
+{
+    return match == CSSSelector::None || match == CSSSelector::Id || match == CSSSelector::Class;
+}
+
+static inline bool isFastCheckableRightmostSelector(const CSSSelector* selector)
+{
+    if (!isFastCheckableRelation(selector->relation()))
+        return false;
+    return isFastCheckableMatch(static_cast<CSSSelector::Match>(selector->m_match)) || SelectorChecker::isCommonPseudoClassSelector(selector);
+}
+
 bool SelectorChecker::isFastCheckableSelector(const CSSSelector* selector)
 {
-    for (; selector; selector = selector->tagHistory()) {
-        if (selector->relation() != CSSSelector::Descendant && selector->relation() != CSSSelector::Child && selector->relation() != CSSSelector::SubSelector)
+    if (!isFastCheckableRightmostSelector(selector))
+        return false;
+    for (selector = selector->tagHistory(); selector; selector = selector->tagHistory()) {
+        if (!isFastCheckableRelation(selector->relation()))
             return false;
-        if (selector->m_match != CSSSelector::None && selector->m_match != CSSSelector::Id && selector->m_match != CSSSelector::Class)
+        if (!isFastCheckableMatch(static_cast<CSSSelector::Match>(selector->m_match)))
             return false;
     }
     return true;
@@ -393,7 +413,7 @@ SelectorChecker::SelectorMatch SelectorChecker::checkSelector(CSSSelector* sel,
     if (e->isSVGShadowRoot())
         return SelectorFailsCompletely;
 #endif
-    
+
     // first selector has to match
     if (!checkOneSelector(sel, e, dynamicPseudo, isSubSelector, encounteredLink, elementStyle, elementParentStyle))
         return SelectorFailsLocally;
@@ -654,10 +674,7 @@ static bool anyAttributeMatches(NamedNodeMap* attributes, CSSSelector::Match mat
 bool SelectorChecker::checkOneSelector(CSSSelector* sel, Element* e, PseudoId& dynamicPseudo, bool isSubSelector, bool encounteredLink, RenderStyle* elementStyle, RenderStyle* elementParentStyle) const
 {
     ASSERT(e);
-    if (!e)
-        return false;
-    
-    if (!selectorTagMatches(e, sel))
+    if (!SelectorChecker::tagMatches(e, sel))
         return false;
     
     if (sel->hasAttribute()) {
@@ -1009,13 +1026,9 @@ bool SelectorChecker::checkOneSelector(CSSSelector* sel, Element* e, PseudoId& d
                 return inputElement->isAutofilled();
             break;
         case CSSSelector::PseudoLink:
-            if (e && e->isLink())
-                return !m_isMatchingVisitedPseudoClass;
-            break;
+            return e->isLink() && !linkMatchesVisitedPseudoClass(e);
         case CSSSelector::PseudoVisited:
-            if (e && e->isLink())
-                return m_isMatchingVisitedPseudoClass || InspectorInstrumentation::forcePseudoState(e, CSSSelector::PseudoVisited);
-            break;
+            return e->isLink() && linkMatchesVisitedPseudoClass(e);
         case CSSSelector::PseudoDrag:
             if (elementStyle)
                 elementStyle->setAffectedByDragRules(true);
@@ -1025,9 +1038,7 @@ bool SelectorChecker::checkOneSelector(CSSSelector* sel, Element* e, PseudoId& d
                 return true;
             break;
         case CSSSelector::PseudoFocus:
-            if (e && ((e->focused() && e->document()->frame() && e->document()->frame()->selection()->isFocusedAndActive()) || InspectorInstrumentation::forcePseudoState(e, CSSSelector::PseudoFocus)))
-                return true;
-            break;
+            return matchesFocusPseudoClass(e);
         case CSSSelector::PseudoHover:
             // If we're in quirks mode, then hover should never match anchors with no
             // href and *:hover should not match anything. This is important for sites like wsj.com.
@@ -1295,4 +1306,27 @@ void SelectorChecker::visitedStateChanged(LinkHash visitedHash)
     }
 }
 
+bool SelectorChecker::commonPseudoClassSelectorMatches(const Element* element, const CSSSelector* selector) const
+{
+    ASSERT(isCommonPseudoClassSelector(selector));
+    switch (selector->pseudoType()) {
+    case CSSSelector::PseudoLink:
+        return element->isLink() && !linkMatchesVisitedPseudoClass(element);
+    case CSSSelector::PseudoVisited:
+        return element->isLink() && linkMatchesVisitedPseudoClass(element);
+    case CSSSelector::PseudoAnyLink:
+        return element->isLink();
+    case CSSSelector::PseudoFocus:
+        return matchesFocusPseudoClass(element);
+    default:
+        ASSERT_NOT_REACHED();
+    }
+    return true;
+}
+
+bool SelectorChecker::isFrameFocused(const Element* element)
+{
+    return element->document()->frame() && element->document()->frame()->selection()->isFocusedAndActive();
+}
+
 }
index dc7e778..314fed8 100644 (file)
@@ -28,7 +28,9 @@
 #ifndef SelectorChecker_h
 #define SelectorChecker_h
 
+#include "CSSSelector.h"
 #include "Element.h"
+#include "InspectorInstrumentation.h"
 #include "LinkHash.h"
 #include "RenderStyleConstants.h"
 #include <wtf/BloomFilter.h>
@@ -51,7 +53,7 @@ public:
     bool checkSelector(CSSSelector*, Element*, bool isFastCheckableSelector = false) const;
     SelectorMatch checkSelector(CSSSelector*, Element*, PseudoId& dynamicPseudo, bool isSubSelector, bool encounteredLink, RenderStyle* = 0, RenderStyle* elementParentStyle = 0) const;
     static bool isFastCheckableSelector(const CSSSelector*);
-    static bool fastCheckSelector(const CSSSelector*, const Element*);
+    bool fastCheckSelector(const CSSSelector*, const Element*) const;
 
     template <unsigned maximumIdentifierCount>
     inline bool fastRejectSelector(const unsigned* identifierHashes) const;
@@ -79,10 +81,19 @@ public:
 
     bool hasUnknownPseudoElements() const { return m_hasUnknownPseudoElements; }
     void clearHasUnknownPseudoElements() { m_hasUnknownPseudoElements = false; }
+    
+    static bool tagMatches(const Element*, const CSSSelector*);
+    static bool isCommonPseudoClassSelector(const CSSSelector*);
+    bool commonPseudoClassSelectorMatches(const Element*, const CSSSelector*) const;
+    bool linkMatchesVisitedPseudoClass(const Element*) const;
+    bool matchesFocusPseudoClass(const Element*) const;
 
 private:
     bool checkOneSelector(CSSSelector*, Element*, PseudoId& dynamicPseudo, bool isSubSelector, bool encounteredLink, RenderStyle*, RenderStyle* elementParentStyle) const;
     bool checkScrollbarPseudoClass(CSSSelector*, PseudoId& dynamicPseudo) const;
+    static bool isFrameFocused(const Element*);
+    
+    bool fastCheckRightmostSelector(const CSSSelector*, const Element*) const;
 
     EInsideLink determineLinkStateSlowCase(Element*) const;
 
@@ -128,6 +139,41 @@ inline bool SelectorChecker::fastRejectSelector(const unsigned* identifierHashes
     }
     return false;
 }
+    
+inline bool SelectorChecker::isCommonPseudoClassSelector(const CSSSelector* selector)
+{
+    if (selector->m_match != CSSSelector::PseudoClass)
+        return false;
+    CSSSelector::PseudoType pseudoType = selector->pseudoType();
+    return pseudoType == CSSSelector::PseudoLink
+        || pseudoType == CSSSelector::PseudoAnyLink
+        || pseudoType == CSSSelector::PseudoVisited
+        || pseudoType == CSSSelector::PseudoFocus;
+}
+
+inline bool SelectorChecker::linkMatchesVisitedPseudoClass(const Element* element) const
+{
+    ASSERT(element->isLink());
+    return m_isMatchingVisitedPseudoClass || InspectorInstrumentation::forcePseudoState(const_cast<Element*>(element), CSSSelector::PseudoVisited);
+}
+
+inline bool SelectorChecker::matchesFocusPseudoClass(const Element* element) const
+{
+    if (InspectorInstrumentation::forcePseudoState(const_cast<Element*>(element), CSSSelector::PseudoFocus))
+        return true;
+    return element->focused() && isFrameFocused(element);
+}
+    
+inline bool SelectorChecker::tagMatches(const Element* element, const CSSSelector* selector)
+{
+    if (!selector->hasTag())
+        return true;
+    const AtomicString& localName = selector->tag().localName();
+    if (localName != starAtom && localName != element->localName())
+        return false;
+    const AtomicString& namespaceURI = selector->tag().namespaceURI();
+    return namespaceURI == starAtom || namespaceURI == element->namespaceURI();
+}
 
 }