Do not cause unnecessary node lists invalidation on id/name attribute change

Do not cause unnecessary node lists invalidation on id/name attribute change.
Invalidation now occurs only if there are node lists with a *valid* id / name
cache.

I see a 12% speed up on Dromaeo's dom-attr test. This patch should fix the
performance regression by r166263, which made getElementsByTagName() return an
HTMLCollection instead of a NodeList. According to the profiler, the
performance impact is mainly due to node lists cache invalidation. This is
because HTMLCollection has an id / name cache, while NodeList doesn't.

This CL also improves the id / name cache API naming on HTMLCollection for
clarity.

R=abarth, adamk
BUG=340325

Review URL: https://codereview.chromium.org/159503003

git-svn-id: svn://svn.chromium.org/blink/trunk@167115 bbb929c8-8fbe-4397-9dbb-9b2b20218538
diff --git a/Source/core/dom/Document.cpp b/Source/core/dom/Document.cpp
index 3314e43..69acf2a 100644
--- a/Source/core/dom/Document.cpp
+++ b/Source/core/dom/Document.cpp
@@ -3559,8 +3559,6 @@
 
 void Document::registerNodeList(LiveNodeListBase* list)
 {
-    if (list->hasIdNameCache())
-        m_nodeListCounts[InvalidateOnIdNameAttrChange]++;
     m_nodeListCounts[list->invalidationType()]++;
     if (list->isRootedAtDocument())
         m_listsInvalidatedAtDocument.add(list);
@@ -3568,8 +3566,6 @@
 
 void Document::unregisterNodeList(LiveNodeListBase* list)
 {
-    if (list->hasIdNameCache())
-        m_nodeListCounts[InvalidateOnIdNameAttrChange]--;
     m_nodeListCounts[list->invalidationType()]--;
     if (list->isRootedAtDocument()) {
         ASSERT(m_listsInvalidatedAtDocument.contains(list));
diff --git a/Source/core/dom/Document.h b/Source/core/dom/Document.h
index 7d98dc5..8910015 100644
--- a/Source/core/dom/Document.h
+++ b/Source/core/dom/Document.h
@@ -649,6 +649,8 @@
 
     void registerNodeList(LiveNodeListBase*);
     void unregisterNodeList(LiveNodeListBase*);
+    void incrementNodeListWithIdNameCacheCount();
+    void decrementNodeListWithIdNameCacheCount();
     bool shouldInvalidateNodeListCaches(const QualifiedName* attrName = 0) const;
     void invalidateNodeListCaches(const QualifiedName* attrName);
 
@@ -1367,6 +1369,17 @@
     return origin >= m_legacyViewportDescription.type;
 }
 
+inline void Document::incrementNodeListWithIdNameCacheCount()
+{
+    m_nodeListCounts[InvalidateOnIdNameAttrChange]++;
+}
+
+inline void Document::decrementNodeListWithIdNameCacheCount()
+{
+    ASSERT(m_nodeListCounts[InvalidateOnIdNameAttrChange] > 0);
+    m_nodeListCounts[InvalidateOnIdNameAttrChange]--;
+}
+
 DEFINE_TYPE_CASTS(Document, ExecutionContextClient, client, client->isDocument(), client.isDocument());
 DEFINE_TYPE_CASTS(Document, ExecutionContext, context, context->isDocument(), context.isDocument());
 DEFINE_NODE_TYPE_CASTS(Document, isDocumentNode());
diff --git a/Source/core/dom/LiveNodeList.cpp b/Source/core/dom/LiveNodeList.cpp
index 21d0783..d43ce2f 100644
--- a/Source/core/dom/LiveNodeList.cpp
+++ b/Source/core/dom/LiveNodeList.cpp
@@ -35,6 +35,13 @@
     return *m_ownerNode;
 }
 
+void LiveNodeListBase::didMoveToDocument(Document& oldDocument, Document& newDocument)
+{
+    invalidateCache(&oldDocument);
+    oldDocument.unregisterNodeList(this);
+    newDocument.registerNodeList(this);
+}
+
 void LiveNodeListBase::invalidateIdNameCacheMaps() const
 {
     ASSERT(hasIdNameCache());
@@ -46,7 +53,7 @@
     return ownerNode();
 }
 
-void LiveNodeList::invalidateCache() const
+void LiveNodeList::invalidateCache(Document*) const
 {
     m_collectionIndexCache.invalidate();
 }
diff --git a/Source/core/dom/LiveNodeList.h b/Source/core/dom/LiveNodeList.h
index 60d70b0..422e6bc 100644
--- a/Source/core/dom/LiveNodeList.h
+++ b/Source/core/dom/LiveNodeList.h
@@ -66,6 +66,7 @@
 
     ContainerNode& rootNode() const;
 
+    void didMoveToDocument(Document& oldDocument, Document& newDocument);
     ALWAYS_INLINE bool hasIdNameCache() const { return !isLiveNodeListType(type()); }
     ALWAYS_INLINE bool isRootedAtDocument() const { return m_rootType == NodeListIsRootedAtDocument || m_rootType == NodeListIsRootedAtDocumentIfOwnerHasItemrefAttr; }
     ALWAYS_INLINE NodeListInvalidationType invalidationType() const { return static_cast<NodeListInvalidationType>(m_invalidationType); }
@@ -78,7 +79,7 @@
         else if (hasIdNameCache() && (*attrName == HTMLNames::idAttr || *attrName == HTMLNames::nameAttr))
             invalidateIdNameCacheMaps();
     }
-    virtual void invalidateCache() const = 0;
+    virtual void invalidateCache(Document* oldDocument = 0) const = 0;
 
     static bool shouldInvalidateTypeOnAttributeChange(NodeListInvalidationType, const QualifiedName&);
 
@@ -136,7 +137,7 @@
     virtual Node* item(unsigned offset) const OVERRIDE FINAL { return m_collectionIndexCache.nodeAt(*this, offset); }
     virtual bool nodeMatches(const Element&) const = 0;
 
-    virtual void invalidateCache() const OVERRIDE FINAL;
+    virtual void invalidateCache(Document* oldDocument) const OVERRIDE FINAL;
     bool shouldOnlyIncludeDirectChildren() const { return false; }
 
     // Collection IndexCache API.
diff --git a/Source/core/dom/NodeRareData.h b/Source/core/dom/NodeRareData.h
index 65f903c..62974e5 100644
--- a/Source/core/dom/NodeRareData.h
+++ b/Source/core/dom/NodeRareData.h
@@ -174,21 +174,18 @@
     void adoptDocument(Document& oldDocument, Document& newDocument)
     {
         ASSERT(oldDocument != newDocument);
-        invalidateCaches();
 
         NodeListAtomicNameCacheMap::const_iterator atomicNameCacheEnd = m_atomicNameCaches.end();
         for (NodeListAtomicNameCacheMap::const_iterator it = m_atomicNameCaches.begin(); it != atomicNameCacheEnd; ++it) {
             LiveNodeListBase* list = it->value;
-            oldDocument.unregisterNodeList(list);
-            newDocument.registerNodeList(list);
+            list->didMoveToDocument(oldDocument, newDocument);
         }
 
         TagCollectionCacheNS::const_iterator tagEnd = m_tagCollectionCacheNS.end();
         for (TagCollectionCacheNS::const_iterator it = m_tagCollectionCacheNS.begin(); it != tagEnd; ++it) {
             LiveNodeListBase* list = it->value;
             ASSERT(!list->isRootedAtDocument());
-            oldDocument.unregisterNodeList(list);
-            newDocument.registerNodeList(list);
+            list->didMoveToDocument(oldDocument, newDocument);
         }
     }
 
diff --git a/Source/core/html/HTMLAllCollection.cpp b/Source/core/html/HTMLAllCollection.cpp
index dfbc466..4010931 100644
--- a/Source/core/html/HTMLAllCollection.cpp
+++ b/Source/core/html/HTMLAllCollection.cpp
@@ -48,7 +48,7 @@
 
 Element* HTMLAllCollection::namedItemWithIndex(const AtomicString& name, unsigned index) const
 {
-    updateNameCache();
+    updateIdNameCache();
 
     if (Vector<Element*>* cache = idCache(name)) {
         if (index < cache->size())
diff --git a/Source/core/html/HTMLCollection.cpp b/Source/core/html/HTMLCollection.cpp
index a6f239f..0fb783f 100644
--- a/Source/core/html/HTMLCollection.cpp
+++ b/Source/core/html/HTMLCollection.cpp
@@ -164,7 +164,7 @@
     : LiveNodeListBase(ownerNode, rootTypeFromCollectionType(type), invalidationTypeExcludingIdAndNameAttributes(type), type)
     , m_overridesItemAfter(itemAfterOverrideType == OverridesItemAfter)
     , m_shouldOnlyIncludeDirectChildren(shouldTypeOnlyIncludeDirectChildren(type))
-    , m_isNameCacheValid(false)
+    , m_hasValidIdNameCache(false)
 {
     ScriptWrappable::init(this);
 }
@@ -176,6 +176,8 @@
 
 HTMLCollection::~HTMLCollection()
 {
+    if (hasValidIdNameCache())
+        unregisterIdNameCacheFromDocument(document());
     // HTMLNameCollection, ClassCollection and TagCollection remove cache by themselves.
     if (type() != WindowNamedItems && type() != DocumentNamedItems && type() != ClassCollectionType
         && type() != HTMLTagCollectionType && type() != TagCollectionType) {
@@ -183,10 +185,10 @@
     }
 }
 
-void HTMLCollection::invalidateCache() const
+void HTMLCollection::invalidateCache(Document* oldDocument) const
 {
     m_collectionIndexCache.invalidate();
-    invalidateIdNameCacheMaps();
+    invalidateIdNameCacheMaps(oldDocument);
 }
 
 template <class NodeListType>
@@ -476,7 +478,7 @@
     // attribute. If a match is not found, the method then searches for an
     // object with a matching name attribute, but only on those elements
     // that are allowed a name attribute.
-    updateNameCache();
+    updateIdNameCache();
 
     Vector<Element*>* idResults = idCache(name);
     if (idResults && !idResults->isEmpty())
@@ -529,9 +531,9 @@
     supportedPropertyNames(names);
 }
 
-void HTMLCollection::updateNameCache() const
+void HTMLCollection::updateIdNameCache() const
 {
-    if (hasNameCache())
+    if (hasValidIdNameCache())
         return;
 
     ContainerNode& root = rootNode();
@@ -546,7 +548,7 @@
             appendNameCache(nameAttrVal, element);
     }
 
-    setHasNameCache();
+    setHasValidIdNameCache();
 }
 
 void HTMLCollection::namedItems(const AtomicString& name, Vector<RefPtr<Element> >& result) const
@@ -555,7 +557,7 @@
     if (name.isEmpty())
         return;
 
-    updateNameCache();
+    updateIdNameCache();
 
     Vector<Element*>* idResults = idCache(name);
     Vector<Element*>* nameResults = nameCache(name);
diff --git a/Source/core/html/HTMLCollection.h b/Source/core/html/HTMLCollection.h
index 2ec859c..29f039e 100644
--- a/Source/core/html/HTMLCollection.h
+++ b/Source/core/html/HTMLCollection.h
@@ -41,7 +41,7 @@
 
     static PassRefPtr<HTMLCollection> create(ContainerNode* base, CollectionType);
     virtual ~HTMLCollection();
-    virtual void invalidateCache() const OVERRIDE;
+    virtual void invalidateCache(Document* oldDocument = 0) const OVERRIDE;
 
     // DOM API
     unsigned length() const { return m_collectionIndexCache.nodeCount(*this); }
@@ -69,9 +69,14 @@
     bool shouldOnlyIncludeDirectChildren() const { return m_shouldOnlyIncludeDirectChildren; }
     virtual void supportedPropertyNames(Vector<String>& names);
 
-    virtual void updateNameCache() const;
-    bool hasNameCache() const { return m_isNameCacheValid; }
-    void setHasNameCache() const { m_isNameCacheValid = true; }
+    virtual void updateIdNameCache() const;
+    bool hasValidIdNameCache() const { return m_hasValidIdNameCache; }
+    void setHasValidIdNameCache() const
+    {
+        ASSERT(!m_hasValidIdNameCache);
+        m_hasValidIdNameCache = true;
+        document().incrementNodeListWithIdNameCacheCount();
+    }
 
     typedef HashMap<StringImpl*, OwnPtr<Vector<Element*> > > NodeCacheMap;
     Vector<Element*>* idCache(const AtomicString& name) const { return m_idCache.get(name.impl()); }
@@ -83,16 +88,30 @@
     Element* traverseNextElement(Element& previous, const ContainerNode& root) const;
 
     static void append(NodeCacheMap&, const AtomicString&, Element*);
-    void invalidateIdNameCacheMaps() const
+    void invalidateIdNameCacheMaps(Document* oldDocument = 0) const
     {
+        if (!m_hasValidIdNameCache)
+            return;
+
+        // Make sure we decrement the NodeListWithIdNameCache count from
+        // the old document instead of the new one in the case the collection
+        // is moved to a new document.
+        unregisterIdNameCacheFromDocument(oldDocument ? *oldDocument : document());
+
         m_idCache.clear();
         m_nameCache.clear();
-        m_isNameCacheValid = false;
+        m_hasValidIdNameCache = false;
+    }
+
+    void unregisterIdNameCacheFromDocument(Document& document) const
+    {
+        ASSERT(m_hasValidIdNameCache);
+        document.decrementNodeListWithIdNameCacheCount();
     }
 
     const unsigned m_overridesItemAfter : 1;
     const unsigned m_shouldOnlyIncludeDirectChildren : 1;
-    mutable unsigned m_isNameCacheValid : 1;
+    mutable unsigned m_hasValidIdNameCache : 1;
     mutable NodeCacheMap m_idCache;
     mutable NodeCacheMap m_nameCache;
     mutable CollectionIndexCache<HTMLCollection, Element> m_collectionIndexCache;
diff --git a/Source/core/html/HTMLFormControlsCollection.cpp b/Source/core/html/HTMLFormControlsCollection.cpp
index 491efe0..6b2a290 100644
--- a/Source/core/html/HTMLFormControlsCollection.cpp
+++ b/Source/core/html/HTMLFormControlsCollection.cpp
@@ -104,9 +104,9 @@
     return 0;
 }
 
-void HTMLFormControlsCollection::invalidateCache() const
+void HTMLFormControlsCollection::invalidateCache(Document* oldDocument) const
 {
-    HTMLCollection::invalidateCache();
+    HTMLCollection::invalidateCache(oldDocument);
     m_cachedElement = 0;
     m_cachedElementOffsetInArray = 0;
 }
@@ -150,9 +150,9 @@
     return firstNamedItem(formControlElements(), imagesElements, nameAttr, name);
 }
 
-void HTMLFormControlsCollection::updateNameCache() const
+void HTMLFormControlsCollection::updateIdNameCache() const
 {
-    if (hasNameCache())
+    if (hasValidIdNameCache())
         return;
 
     HashSet<StringImpl*> foundInputElements;
@@ -189,7 +189,7 @@
         }
     }
 
-    setHasNameCache();
+    setHasValidIdNameCache();
 }
 
 void HTMLFormControlsCollection::namedGetter(const AtomicString& name, bool& radioNodeListEnabled, RefPtr<RadioNodeList>& radioNodeList, bool& elementEnabled, RefPtr<Element>& element)
diff --git a/Source/core/html/HTMLFormControlsCollection.h b/Source/core/html/HTMLFormControlsCollection.h
index 84d6e97..d5d7029 100644
--- a/Source/core/html/HTMLFormControlsCollection.h
+++ b/Source/core/html/HTMLFormControlsCollection.h
@@ -49,13 +49,13 @@
 private:
     explicit HTMLFormControlsCollection(ContainerNode*);
 
-    virtual void updateNameCache() const OVERRIDE;
+    virtual void updateIdNameCache() const OVERRIDE;
     virtual void supportedPropertyNames(Vector<String>& names) OVERRIDE;
 
     const Vector<FormAssociatedElement*>& formControlElements() const;
     const Vector<HTMLImageElement*>& formImageElements() const;
     virtual Element* virtualItemAfter(Element*) const OVERRIDE;
-    virtual void invalidateCache() const OVERRIDE;
+    virtual void invalidateCache(Document* oldDocument = 0) const OVERRIDE;
 
     mutable Element* m_cachedElement;
     mutable unsigned m_cachedElementOffsetInArray;