Filter attribute selectors with selector filter
authorantti@apple.com <antti@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Wed, 28 Feb 2018 15:50:00 +0000 (15:50 +0000)
committerantti@apple.com <antti@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Wed, 28 Feb 2018 15:50:00 +0000 (15:50 +0000)
https://bugs.webkit.org/show_bug.cgi?id=183200

Reviewed by Zalan Bujtas.

Currently selector filtering is done based on tags, classes and ids. We should include attributes too.

This patch adds filtering based on attribute name (but not content).

* css/SelectorFilter.cpp:
(WebCore::isExcludedAttribute):

    Ignore id, class and style attributes. First two are already handled and the last is common but is rarely
    used in selectors.

(WebCore::collectElementIdentifierHashes):

    Collect attributes.
    Remove the unnecessary StyledElement casting.

(WebCore::collectSimpleSelectorHash):

    Collect attribute selectors.

(WebCore::chooseSelectorHashesForFilter):

    Pick attributes with high priority for the filter as it is likely a good signal.

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

Source/WebCore/ChangeLog
Source/WebCore/css/SelectorFilter.cpp

index 8dee284b74ec48e3e49483f45fbe95e7d667a30e..736ac24b8d372e542a7e60a3e89bc5f8c22c37b4 100644 (file)
@@ -1,3 +1,33 @@
+2018-02-28  Antti Koivisto  <antti@apple.com>
+
+        Filter attribute selectors with selector filter
+        https://bugs.webkit.org/show_bug.cgi?id=183200
+
+        Reviewed by Zalan Bujtas.
+
+        Currently selector filtering is done based on tags, classes and ids. We should include attributes too.
+
+        This patch adds filtering based on attribute name (but not content).
+
+        * css/SelectorFilter.cpp:
+        (WebCore::isExcludedAttribute):
+
+            Ignore id, class and style attributes. First two are already handled and the last is common but is rarely
+            used in selectors.
+
+        (WebCore::collectElementIdentifierHashes):
+
+            Collect attributes.
+            Remove the unnecessary StyledElement casting.
+
+        (WebCore::collectSimpleSelectorHash):
+
+            Collect attribute selectors.
+
+        (WebCore::chooseSelectorHashesForFilter):
+
+            Pick attributes with high priority for the filter as it is likely a good signal.
+
 2018-02-27  Sergio Villar Senin  <svillar@igalia.com>
 
         [WebVR] Convert VRPlatformDisplayInfo into a class
index 14303db53aa124098abb7424f69348b5a149e889..8862c69042bbd1d1581ed5f75d2cc4b90bab8d30 100644 (file)
 namespace WebCore {
 
 // Salt to separate otherwise identical string hashes so a class-selector like .article won't match <article> elements.
-enum { TagNameSalt = 13, IdAttributeSalt = 17, ClassAttributeSalt = 19 };
+enum { TagNameSalt = 13, IdSalt = 17, ClassSalt = 19, AttributeSalt = 23 };
 
-static inline void collectElementIdentifierHashes(const Element* element, Vector<unsigned, 4>& identifierHashes)
+static bool isExcludedAttribute(const AtomicString& name)
 {
-    AtomicString tagLowercaseLocalName = element->localName().convertToASCIILowercase();
+    return name == HTMLNames::classAttr->localName() || name == HTMLNames::idAttr->localName() || name == HTMLNames::styleAttr->localName();
+}
+
+static inline void collectElementIdentifierHashes(const Element& element, Vector<unsigned, 4>& identifierHashes)
+{
+    AtomicString tagLowercaseLocalName = element.localName().convertToASCIILowercase();
     identifierHashes.append(tagLowercaseLocalName.impl()->existingHash() * TagNameSalt);
 
-    auto& id = element->idForStyleResolution();
+    auto& id = element.idForStyleResolution();
     if (!id.isNull())
-        identifierHashes.append(id.impl()->existingHash() * IdAttributeSalt);
-    const StyledElement* styledElement = element->isStyledElement() ? static_cast<const StyledElement*>(element) : 0;
-    if (styledElement && styledElement->hasClass()) {
-        const SpaceSplitString& classNames = styledElement->classNames();
+        identifierHashes.append(id.impl()->existingHash() * IdSalt);
+
+    if (element.hasClass()) {
+        const SpaceSplitString& classNames = element.classNames();
         size_t count = classNames.size();
         for (size_t i = 0; i < count; ++i)
-            identifierHashes.append(classNames[i].impl()->existingHash() * ClassAttributeSalt);
+            identifierHashes.append(classNames[i].impl()->existingHash() * ClassSalt);
+    }
+    
+    if (element.hasAttributesWithoutUpdate()) {
+        for (auto& attribute : element.attributesIterator()) {
+            auto attributeName = element.isHTMLElement() ? attribute.localName() : attribute.localName().convertToASCIILowercase();
+            if (isExcludedAttribute(attributeName))
+                continue;
+            identifierHashes.append(attributeName.impl()->existingHash() * AttributeSalt);
+        }
     }
 }
 
@@ -80,7 +94,7 @@ void SelectorFilter::pushParent(Element* parent)
     ParentStackFrame& parentFrame = m_parentStack.last();
     // Mix tags, class names and ids into some sort of weird bouillabaisse.
     // The filter is used for fast rejection of child and descendant selectors.
-    collectElementIdentifierHashes(parent, parentFrame.identifierHashes);
+    collectElementIdentifierHashes(*parent, parentFrame.identifierHashes);
     size_t count = parentFrame.identifierHashes.size();
     for (size_t i = 0; i < count; ++i)
         m_ancestorIdentifierFilter.add(parentFrame.identifierHashes[i]);
@@ -123,6 +137,7 @@ struct CollectedSelectorHashes {
     HashVector ids;
     HashVector classes;
     HashVector tags;
+    HashVector attributes;
 };
 
 static inline void collectSimpleSelectorHash(CollectedSelectorHashes& collectedHashes, const CSSSelector& selector)
@@ -130,11 +145,11 @@ static inline void collectSimpleSelectorHash(CollectedSelectorHashes& collectedH
     switch (selector.match()) {
     case CSSSelector::Id:
         if (!selector.value().isEmpty())
-            collectedHashes.ids.append(selector.value().impl()->existingHash() * IdAttributeSalt);
+            collectedHashes.ids.append(selector.value().impl()->existingHash() * IdSalt);
         break;
     case CSSSelector::Class:
         if (!selector.value().isEmpty())
-            collectedHashes.classes.append(selector.value().impl()->existingHash() * ClassAttributeSalt);
+            collectedHashes.classes.append(selector.value().impl()->existingHash() * ClassSalt);
         break;
     case CSSSelector::Tag: {
         auto& tagLowercaseLocalName = selector.tagLowercaseLocalName();
@@ -142,6 +157,18 @@ static inline void collectSimpleSelectorHash(CollectedSelectorHashes& collectedH
             collectedHashes.tags.append(tagLowercaseLocalName.impl()->existingHash() * TagNameSalt);
         break;
     }
+    case CSSSelector::Exact:
+    case CSSSelector::Set:
+    case CSSSelector::List:
+    case CSSSelector::Hyphen:
+    case CSSSelector::Contain:
+    case CSSSelector::Begin:
+    case CSSSelector::End: {
+        auto attributeName = selector.attributeCanonicalLocalName().convertToASCIILowercase();
+        if (!isExcludedAttribute(attributeName))
+            collectedHashes.attributes.append(attributeName.impl()->existingHash() * AttributeSalt);
+        break;
+    }
     default:
         break;
     }
@@ -204,6 +231,8 @@ static SelectorFilter::Hashes chooseSelectorHashesForFilter(const CollectedSelec
     // There is a limited number of slots. Prefer more specific selector types.
     if (copyHashes(collectedSelectorHashes.ids))
         return resultHashes;
+    if (copyHashes(collectedSelectorHashes.attributes))
+        return resultHashes;
     if (copyHashes(collectedSelectorHashes.classes))
         return resultHashes;
     if (copyHashes(collectedSelectorHashes.tags))