Update To 11.40.268.0
[platform/framework/web/crosswalk.git] / src / third_party / WebKit / Source / core / html / HTMLCollection.cpp
index 103efab..c373595 100644 (file)
@@ -1,7 +1,8 @@
 /*
  * Copyright (C) 1999 Lars Knoll (knoll@kde.org)
  *           (C) 1999 Antti Koivisto (koivisto@kde.org)
- * Copyright (C) 2003, 2004, 2005, 2006, 2007, 2008, 2011, 2012 Apple Inc. All rights reserved.
+ * Copyright (C) 2003-2008, 2011, 2012, 2014 Apple Inc. All rights reserved.
+ * Copyright (C) 2014 Samsung Electronics. All rights reserved.
  *
  * This library is free software; you can redistribute it and/or
  * modify it under the terms of the GNU Library General Public
 #include "config.h"
 #include "core/html/HTMLCollection.h"
 
-#include "HTMLNames.h"
-#include "core/dom/ClassNodeList.h"
+#include "core/HTMLNames.h"
+#include "core/dom/ClassCollection.h"
 #include "core/dom/ElementTraversal.h"
-#include "core/dom/NodeList.h"
 #include "core/dom/NodeRareData.h"
-#include "core/dom/NodeTraversal.h"
+#include "core/html/DocumentNameCollection.h"
+#include "core/html/HTMLDataListOptionsCollection.h"
 #include "core/html/HTMLElement.h"
 #include "core/html/HTMLObjectElement.h"
 #include "core/html/HTMLOptionElement.h"
+#include "core/html/HTMLOptionsCollection.h"
+#include "core/html/HTMLTagCollection.h"
+#include "core/html/WindowNameCollection.h"
+#include "wtf/HashSet.h"
 
-namespace WebCore {
+namespace blink {
 
 using namespace HTMLNames;
 
-static bool shouldOnlyIncludeDirectChildren(CollectionType type)
+static bool shouldTypeOnlyIncludeDirectChildren(CollectionType type)
 {
     switch (type) {
+    case ClassCollectionType:
+    case TagCollectionType:
+    case HTMLTagCollectionType:
     case DocAll:
     case DocAnchors:
     case DocApplets:
@@ -62,11 +70,7 @@ static bool shouldOnlyIncludeDirectChildren(CollectionType type)
     case TSectionRows:
     case TableTBodies:
         return true;
-    case ChildNodeListType:
-    case ClassNodeListType:
     case NameNodeListType:
-    case TagNodeListType:
-    case HTMLTagNodeListType:
     case RadioNodeListType:
     case RadioImgNodeListType:
     case LabelsNodeListType:
@@ -91,6 +95,9 @@ static NodeListRootType rootTypeFromCollectionType(CollectionType type)
     case DocumentNamedItems:
     case FormControls:
         return NodeListIsRootedAtDocument;
+    case ClassCollectionType:
+    case TagCollectionType:
+    case HTMLTagCollectionType:
     case NodeChildren:
     case TableTBodies:
     case TSectionRows:
@@ -101,11 +108,7 @@ static NodeListRootType rootTypeFromCollectionType(CollectionType type)
     case DataListOptions:
     case MapAreas:
         return NodeListIsRootedAtNode;
-    case ChildNodeListType:
-    case ClassNodeListType:
     case NameNodeListType:
-    case TagNodeListType:
-    case HTMLTagNodeListType:
     case RadioNodeListType:
     case RadioImgNodeListType:
     case LabelsNodeListType:
@@ -118,6 +121,8 @@ static NodeListRootType rootTypeFromCollectionType(CollectionType type)
 static NodeListInvalidationType invalidationTypeExcludingIdAndNameAttributes(CollectionType type)
 {
     switch (type) {
+    case TagCollectionType:
+    case HTMLTagCollectionType:
     case DocImages:
     case DocEmbeds:
     case DocForms:
@@ -146,11 +151,9 @@ static NodeListInvalidationType invalidationTypeExcludingIdAndNameAttributes(Col
         return InvalidateOnIdNameAttrChange;
     case FormControls:
         return InvalidateForFormControls;
-    case ChildNodeListType:
-    case ClassNodeListType:
+    case ClassCollectionType:
+        return InvalidateOnClassAttrChange;
     case NameNodeListType:
-    case TagNodeListType:
-    case HTMLTagNodeListType:
     case RadioNodeListType:
     case RadioImgNodeListType:
     case LabelsNodeListType:
@@ -160,87 +163,87 @@ static NodeListInvalidationType invalidationTypeExcludingIdAndNameAttributes(Col
     return DoNotInvalidateOnAttributeChanges;
 }
 
-HTMLCollection::HTMLCollection(ContainerNode* ownerNode, CollectionType type, ItemAfterOverrideType itemAfterOverrideType)
-    : LiveNodeListBase(ownerNode, rootTypeFromCollectionType(type), invalidationTypeExcludingIdAndNameAttributes(type),
-        WebCore::shouldOnlyIncludeDirectChildren(type), type, itemAfterOverrideType)
-    , m_isNameCacheValid(false)
+HTMLCollection::HTMLCollection(ContainerNode& ownerNode, CollectionType type, ItemAfterOverrideType itemAfterOverrideType)
+    : LiveNodeListBase(ownerNode, rootTypeFromCollectionType(type), invalidationTypeExcludingIdAndNameAttributes(type), type)
+    , m_overridesItemAfter(itemAfterOverrideType == OverridesItemAfter)
+    , m_shouldOnlyIncludeDirectChildren(shouldTypeOnlyIncludeDirectChildren(type))
 {
-    ScriptWrappable::init(this);
 }
 
-PassRefPtr<HTMLCollection> HTMLCollection::create(ContainerNode* base, CollectionType type)
+PassRefPtrWillBeRawPtr<HTMLCollection> HTMLCollection::create(ContainerNode& base, CollectionType type)
 {
-    return adoptRef(new HTMLCollection(base, type, DoesNotOverrideItemAfter));
+    return adoptRefWillBeNoop(new HTMLCollection(base, type, DoesNotOverrideItemAfter));
 }
 
 HTMLCollection::~HTMLCollection()
 {
-    // HTMLNameCollection removes cache by itself.
-    if (type() != WindowNamedItems && type() != DocumentNamedItems)
-        ownerNode()->nodeLists()->removeCacheWithAtomicName(this, type());
+#if !ENABLE(OILPAN)
+    if (hasValidIdNameCache())
+        unregisterIdNameCacheFromDocument(document());
+    // Named HTMLCollection types remove cache by themselves.
+    if (isUnnamedHTMLCollectionType(type()))
+        ownerNode().nodeLists()->removeCache(this, type());
+#endif
 }
 
-void HTMLCollection::invalidateCache() const
+void HTMLCollection::invalidateCache(Document* oldDocument) const
 {
-    LiveNodeListBase::invalidateCache();
-    invalidateIdNameCacheMaps();
+    m_collectionItemsCache.invalidate();
+    invalidateIdNameCacheMaps(oldDocument);
 }
 
-template <class NodeListType>
-inline bool isMatchingElement(const NodeListType&, const Element&);
+unsigned HTMLCollection::length() const
+{
+    return m_collectionItemsCache.nodeCount(*this);
+}
 
-template <> inline bool isMatchingElement(const HTMLCollection& htmlCollection, const Element& element)
+Element* HTMLCollection::item(unsigned offset) const
 {
-    CollectionType type = htmlCollection.type();
-    if (!element.isHTMLElement() && !(type == DocAll || type == NodeChildren || type == WindowNamedItems))
-        return false;
+    return m_collectionItemsCache.nodeAt(*this, offset);
+}
 
-    switch (type) {
+static inline bool isMatchingHTMLElement(const HTMLCollection& htmlCollection, const HTMLElement& element)
+{
+    switch (htmlCollection.type()) {
     case DocImages:
-        return element.hasLocalName(imgTag);
+        return element.hasTagName(imgTag);
     case DocScripts:
-        return element.hasLocalName(scriptTag);
+        return element.hasTagName(scriptTag);
     case DocForms:
-        return element.hasLocalName(formTag);
+        return element.hasTagName(formTag);
+    case DocumentNamedItems:
+        return toDocumentNameCollection(htmlCollection).elementMatches(element);
     case TableTBodies:
-        return element.hasLocalName(tbodyTag);
+        return element.hasTagName(tbodyTag);
     case TRCells:
-        return element.hasLocalName(tdTag) || element.hasLocalName(thTag);
+        return element.hasTagName(tdTag) || element.hasTagName(thTag);
     case TSectionRows:
-        return element.hasLocalName(trTag);
+        return element.hasTagName(trTag);
     case SelectOptions:
-        return element.hasLocalName(optionTag);
+        return toHTMLOptionsCollection(htmlCollection).elementMatches(element);
     case SelectedOptions:
-        return element.hasLocalName(optionTag) && toHTMLOptionElement(element).selected();
+        return isHTMLOptionElement(element) && toHTMLOptionElement(element).selected();
     case DataListOptions:
-        if (element.hasLocalName(optionTag)) {
-            const HTMLOptionElement& option = toHTMLOptionElement(element);
-            if (!option.isDisabledFormControl() && !option.value().isEmpty())
-                return true;
-        }
-        return false;
+        return toHTMLDataListOptionsCollection(htmlCollection).elementMatches(element);
     case MapAreas:
-        return element.hasLocalName(areaTag);
+        return element.hasTagName(areaTag);
     case DocApplets:
-        return element.hasLocalName(appletTag) || (element.hasLocalName(objectTag) && toHTMLObjectElement(element).containsJavaApplet());
+        return element.hasTagName(appletTag) || (isHTMLObjectElement(element) && toHTMLObjectElement(element).containsJavaApplet());
     case DocEmbeds:
-        return element.hasLocalName(embedTag);
+        return element.hasTagName(embedTag);
     case DocLinks:
-        return (element.hasLocalName(aTag) || element.hasLocalName(areaTag)) && element.fastHasAttribute(hrefAttr);
+        return (element.hasTagName(aTag) || element.hasTagName(areaTag)) && element.fastHasAttribute(hrefAttr);
     case DocAnchors:
-        return element.hasLocalName(aTag) && element.fastHasAttribute(nameAttr);
+        return element.hasTagName(aTag) && element.fastHasAttribute(nameAttr);
+    case ClassCollectionType:
+    case TagCollectionType:
+    case HTMLTagCollectionType:
     case DocAll:
     case NodeChildren:
-        return true;
     case FormControls:
-    case DocumentNamedItems:
     case TableRows:
     case WindowNamedItems:
-    case ChildNodeListType:
-    case ClassNodeListType:
     case NameNodeListType:
-    case TagNodeListType:
-    case HTMLTagNodeListType:
     case RadioNodeListType:
     case RadioImgNodeListType:
     case LabelsNodeListType:
@@ -249,409 +252,256 @@ template <> inline bool isMatchingElement(const HTMLCollection& htmlCollection,
     return false;
 }
 
-template <> inline bool isMatchingElement(const LiveNodeList& nodeList, const Element& element)
-{
-    return nodeList.nodeMatches(element);
-}
-
-template <> inline bool isMatchingElement(const HTMLTagNodeList& nodeList, const Element& element)
-{
-    return nodeList.nodeMatchesInlined(element);
-}
-
-template <> inline bool isMatchingElement(const ClassNodeList& nodeList, const Element& element)
-{
-    return nodeList.nodeMatchesInlined(element);
-}
-
-static Node* previousNode(const Node& base, const Node& previous, bool onlyIncludeDirectChildren)
-{
-    return onlyIncludeDirectChildren ? previous.previousSibling() : NodeTraversal::previous(previous, &base);
-}
-
-static inline Node* lastDescendant(const Node& node)
-{
-    Node* descendant = node.lastChild();
-    for (Node* current = descendant; current; current = current->lastChild())
-        descendant = current;
-    return descendant;
-}
-
-static Node* lastNode(const Node& rootNode, bool onlyIncludeDirectChildren)
-{
-    return onlyIncludeDirectChildren ? rootNode.lastChild() : lastDescendant(rootNode);
-}
-
-ALWAYS_INLINE Node* LiveNodeListBase::iterateForPreviousNode(Node* current) const
-{
-    bool onlyIncludeDirectChildren = shouldOnlyIncludeDirectChildren();
-    CollectionType collectionType = type();
-    Node& rootNode = this->rootNode();
-    for (; current; current = previousNode(rootNode, *current, onlyIncludeDirectChildren)) {
-        if (isLiveNodeListType(collectionType)) {
-            if (current->isElementNode() && isMatchingElement(static_cast<const LiveNodeList&>(*this), toElement(*current)))
-                return toElement(current);
-        } else {
-            if (current->isElementNode() && isMatchingElement(static_cast<const HTMLCollection&>(*this), toElement(*current)))
-                return toElement(current);
-        }
-    }
-    return 0;
-}
-
-ALWAYS_INLINE Node* LiveNodeListBase::itemBefore(const Node* previous) const
+inline bool HTMLCollection::elementMatches(const Element& element) const
 {
-    Node* current;
-    if (LIKELY(!!previous)) // Without this LIKELY, length() and item() can be 10% slower.
-        current = previousNode(rootNode(), *previous, shouldOnlyIncludeDirectChildren());
-    else
-        current = lastNode(rootNode(), shouldOnlyIncludeDirectChildren());
-
-    if (type() == ChildNodeListType)
-        return current;
-    return iterateForPreviousNode(current);
-}
-
-template <class NodeListType>
-inline Element* firstMatchingElement(const NodeListType& nodeList, const ContainerNode& root)
-{
-    Element* element = ElementTraversal::firstWithin(root);
-    while (element && !isMatchingElement(nodeList, *element))
-        element = ElementTraversal::next(*element, &root);
-    return element;
-}
-
-template <class NodeListType>
-inline Element* nextMatchingElement(const NodeListType& nodeList, Element& current, const ContainerNode& root)
-{
-    Element* next = &current;
-    do {
-        next = ElementTraversal::next(*next, &root);
-    } while (next && !isMatchingElement(nodeList, *next));
-    return next;
-}
-
-template <class NodeListType>
-inline Element* traverseMatchingElementsForwardToOffset(const NodeListType& nodeList, unsigned offset, Element& currentElement, unsigned& currentOffset, const ContainerNode& root)
-{
-    ASSERT(currentOffset < offset);
-    Element* next = &currentElement;
-    while ((next = nextMatchingElement(nodeList, *next, root))) {
-        if (++currentOffset == offset)
-            return next;
-    }
-    return 0;
-}
-
-static inline Node* traverseSiblingsForwardToOffset(unsigned offset, Node& currentNode, unsigned& currentOffset)
-{
-    ASSERT(currentOffset < offset);
-    Node* next = &currentNode;
-    while ((next = next->nextSibling())) {
-        if (++currentOffset == offset)
-            return next;
-    }
-    return 0;
-}
-
-// FIXME: This should be in LiveNodeList.cpp but it needs to stay here until firstMatchingElement()
-// and others are moved to a separate header.
-inline Node* LiveNodeList::traverseToFirstElement(const ContainerNode& root) const
-{
-    ASSERT(isLiveNodeListType(type()));
+    // These collections apply to any kind of Elements, not just HTMLElements.
     switch (type()) {
-    case ChildNodeListType:
-        return root.firstChild();
-    case HTMLTagNodeListType:
-        return firstMatchingElement(static_cast<const HTMLTagNodeList&>(*this), root);
-    case ClassNodeListType:
-        return firstMatchingElement(static_cast<const ClassNodeList&>(*this), root);
+    case DocAll:
+    case NodeChildren:
+        return true;
+    case ClassCollectionType:
+        return toClassCollection(*this).elementMatches(element);
+    case TagCollectionType:
+        return toTagCollection(*this).elementMatches(element);
+    case HTMLTagCollectionType:
+        return toHTMLTagCollection(*this).elementMatches(element);
+    case WindowNamedItems:
+        return toWindowNameCollection(*this).elementMatches(element);
     default:
-        return firstMatchingElement(static_cast<const LiveNodeList&>(*this), root);
+        break;
     }
-}
 
-// FIXME: This should be in LiveNodeList.cpp but it needs to stay here until traverseMatchingElementsForwardToOffset()
-// and others are moved to a separate header.
-inline Node* LiveNodeList::traverseForwardToOffset(unsigned offset, Node& currentNode, unsigned& currentOffset, const ContainerNode& root) const
-{
-    switch (type()) {
-    case ChildNodeListType:
-        return traverseSiblingsForwardToOffset(offset, currentNode, currentOffset);
-    case HTMLTagNodeListType:
-        return traverseMatchingElementsForwardToOffset(static_cast<const HTMLTagNodeList&>(*this), offset, toElement(currentNode), currentOffset, root);
-    case ClassNodeListType:
-        return traverseMatchingElementsForwardToOffset(static_cast<const ClassNodeList&>(*this), offset, toElement(currentNode), currentOffset, root);
-    default:
-        return traverseMatchingElementsForwardToOffset(*this, offset, toElement(currentNode), currentOffset, root);
-    }
+    // The following only applies to HTMLElements.
+    return element.isHTMLElement() && isMatchingHTMLElement(*this, toHTMLElement(element));
 }
 
-bool ALWAYS_INLINE LiveNodeListBase::isLastItemCloserThanLastOrCachedItem(unsigned offset) const
-{
-    ASSERT(isLengthCacheValid());
-    unsigned distanceFromLastItem = cachedLength() - offset;
-    if (!cachedItem())
-        return distanceFromLastItem < offset;
-
-    return cachedItemOffset() < offset && distanceFromLastItem < offset - cachedItemOffset();
-}
+namespace {
 
-bool ALWAYS_INLINE LiveNodeListBase::isFirstItemCloserThanCachedItem(unsigned offset) const
-{
-    if (cachedItemOffset() < offset)
-        return false;
+template <class HTMLCollectionType>
+class IsMatch {
+public:
+    IsMatch(const HTMLCollectionType& list)
+        : m_list(list)
+    { }
 
-    unsigned distanceFromCachedItem = cachedItemOffset() - offset;
-    return offset < distanceFromCachedItem;
-}
-
-unsigned LiveNodeListBase::length() const
-{
-    if (isLengthCacheValid())
-        return cachedLength();
-
-    item(UINT_MAX);
-    ASSERT(isLengthCacheValid());
-
-    return cachedLength();
-}
-
-// FIXME: It is silly that these functions are in HTMLCollection.cpp.
-Node* LiveNodeListBase::item(unsigned offset) const
-{
-    if (cachedItem() && cachedItemOffset() == offset)
-        return cachedItem();
-
-    if (isLengthCacheValid() && cachedLength() <= offset)
-        return 0;
-
-    ContainerNode& root = rootNode();
-    if (isLengthCacheValid() && !overridesItemAfter() && isLastItemCloserThanLastOrCachedItem(offset)) {
-        Node* lastItem = itemBefore(0);
-        ASSERT(lastItem);
-        setItemCache(lastItem, cachedLength() - 1);
-    } else if (!cachedItem() || isFirstItemCloserThanCachedItem(offset) || (overridesItemAfter() && offset < cachedItemOffset())) {
-        Node* firstItem;
-        if (isLiveNodeListType(type()))
-            firstItem = static_cast<const LiveNodeList*>(this)->traverseToFirstElement(root);
-        else
-            firstItem = static_cast<const HTMLCollection*>(this)->traverseToFirstElement(root);
-
-        if (!firstItem) {
-            setLengthCache(0);
-            return 0;
-        }
-        setItemCache(firstItem, 0);
-        ASSERT(!cachedItemOffset());
+    bool operator() (const Element& element) const
+    {
+        return m_list.elementMatches(element);
     }
 
-    if (cachedItemOffset() == offset)
-        return cachedItem();
-
-    return itemBeforeOrAfterCachedItem(offset, root);
-}
-
-inline Node* LiveNodeListBase::itemBeforeOrAfterCachedItem(unsigned offset, const ContainerNode& root) const
-{
-    unsigned currentOffset = cachedItemOffset();
-    Node* currentItem = cachedItem();
-    ASSERT(currentItem);
-    ASSERT(currentOffset != offset);
-
-    if (offset < cachedItemOffset()) {
-        ASSERT(!overridesItemAfter());
-        while ((currentItem = itemBefore(currentItem))) {
-            ASSERT(currentOffset);
-            currentOffset--;
-            if (currentOffset == offset) {
-                setItemCache(currentItem, currentOffset);
-                return currentItem;
-            }
-        }
-        ASSERT_NOT_REACHED();
-        return 0;
-    }
+private:
+    const HTMLCollectionType& m_list;
+};
 
-    if (isLiveNodeListType(type()))
-        currentItem = static_cast<const LiveNodeList*>(this)->traverseForwardToOffset(offset, *currentItem, currentOffset, root);
-    else
-        currentItem = static_cast<const HTMLCollection*>(this)->traverseForwardToOffset(offset, toElement(*currentItem), currentOffset, root);
+} // namespace
 
-    if (!currentItem) {
-        // Did not find the item. On plus side, we now know the length.
-        setLengthCache(currentOffset + 1);
-        return 0;
-    }
-    setItemCache(currentItem, currentOffset);
-    return currentItem;
-}
+template <class HTMLCollectionType>
+static inline IsMatch<HTMLCollectionType> makeIsMatch(const HTMLCollectionType& list) { return IsMatch<HTMLCollectionType>(list); }
 
 Element* HTMLCollection::virtualItemAfter(Element*) const
 {
     ASSERT_NOT_REACHED();
-    return 0;
+    return nullptr;
 }
 
 static inline bool nameShouldBeVisibleInDocumentAll(const HTMLElement& element)
 {
+    // http://www.whatwg.org/specs/web-apps/current-work/multipage/common-dom-interfaces.html#dom-htmlallcollection-nameditem:
     // The document.all collection returns only certain types of elements by name,
     // although it returns any type of element by id.
-    return element.hasLocalName(appletTag)
-        || element.hasLocalName(embedTag)
-        || element.hasLocalName(formTag)
-        || element.hasLocalName(imgTag)
-        || element.hasLocalName(inputTag)
-        || element.hasLocalName(objectTag)
-        || element.hasLocalName(selectTag);
-}
-
-bool HTMLCollection::checkForNameMatch(const Element& element, bool checkName, const AtomicString& name) const
+    return element.hasTagName(aTag)
+        || element.hasTagName(appletTag)
+        || element.hasTagName(areaTag)
+        || element.hasTagName(embedTag)
+        || element.hasTagName(formTag)
+        || element.hasTagName(frameTag)
+        || element.hasTagName(framesetTag)
+        || element.hasTagName(iframeTag)
+        || element.hasTagName(imgTag)
+        || element.hasTagName(inputTag)
+        || element.hasTagName(objectTag)
+        || element.hasTagName(selectTag);
+}
+
+Element* HTMLCollection::traverseToFirst() const
 {
-    if (!element.isHTMLElement())
-        return false;
-
-    const HTMLElement& e = toHTMLElement(element);
-    if (!checkName)
-        return e.getIdAttribute() == name;
-
-    if (type() == DocAll && !nameShouldBeVisibleInDocumentAll(e))
-        return false;
-
-    return e.getNameAttribute() == name && e.getIdAttribute() != name;
-}
-
-inline Element* firstMatchingChildElement(const HTMLCollection& nodeList, const ContainerNode& root)
-{
-    Element* element = ElementTraversal::firstWithin(root);
-    while (element && !isMatchingElement(nodeList, *element))
-        element = ElementTraversal::nextSkippingChildren(*element, &root);
-    return element;
-}
-
-inline Element* nextMatchingChildElement(const HTMLCollection& nodeList, Element& current, const ContainerNode& root)
-{
-    Element* next = &current;
-    do {
-        next = ElementTraversal::nextSkippingChildren(*next, &root);
-    } while (next && !isMatchingElement(nodeList, *next));
-    return next;
-}
-
-inline Element* HTMLCollection::traverseToFirstElement(const ContainerNode& root) const
-{
-    if (overridesItemAfter())
-        return virtualItemAfter(0);
-    if (shouldOnlyIncludeDirectChildren())
-        return firstMatchingChildElement(*this, root);
-    return firstMatchingElement(*this, root);
+    switch (type()) {
+    case HTMLTagCollectionType:
+        return ElementTraversal::firstWithin(rootNode(), makeIsMatch(toHTMLTagCollection(*this)));
+    case ClassCollectionType:
+        return ElementTraversal::firstWithin(rootNode(), makeIsMatch(toClassCollection(*this)));
+    default:
+        if (overridesItemAfter())
+            return virtualItemAfter(0);
+        if (shouldOnlyIncludeDirectChildren())
+            return ElementTraversal::firstChild(rootNode(), makeIsMatch(*this));
+        return ElementTraversal::firstWithin(rootNode(), makeIsMatch(*this));
+    }
 }
 
-inline Element* HTMLCollection::traverseNextElement(Element& previous, const ContainerNode& root) const
+Element* HTMLCollection::traverseToLast() const
 {
-    if (overridesItemAfter())
-        return virtualItemAfter(&previous);
+    ASSERT(canTraverseBackward());
     if (shouldOnlyIncludeDirectChildren())
-        return nextMatchingChildElement(*this, previous, root);
-    return nextMatchingElement(*this, previous, root);
+        return ElementTraversal::lastChild(rootNode(), makeIsMatch(*this));
+    return ElementTraversal::lastWithin(rootNode(), makeIsMatch(*this));
 }
 
-inline Element* HTMLCollection::traverseForwardToOffset(unsigned offset, Element& currentElement, unsigned& currentOffset, const ContainerNode& root) const
+Element* HTMLCollection::traverseForwardToOffset(unsigned offset, Element& currentElement, unsigned& currentOffset) const
 {
     ASSERT(currentOffset < offset);
-    if (overridesItemAfter()) {
-        Element* next = &currentElement;
-        while ((next = virtualItemAfter(next))) {
-            if (++currentOffset == offset)
-                return next;
+    switch (type()) {
+    case HTMLTagCollectionType:
+        return traverseMatchingElementsForwardToOffset(currentElement, &rootNode(), offset, currentOffset, makeIsMatch(toHTMLTagCollection(*this)));
+    case ClassCollectionType:
+        return traverseMatchingElementsForwardToOffset(currentElement, &rootNode(), offset, currentOffset, makeIsMatch(toClassCollection(*this)));
+    default:
+        if (overridesItemAfter()) {
+            for (Element* next = virtualItemAfter(&currentElement); next; next = virtualItemAfter(next)) {
+                if (++currentOffset == offset)
+                    return next;
+            }
+            return nullptr;
         }
-        return 0;
+        if (shouldOnlyIncludeDirectChildren()) {
+            IsMatch<HTMLCollection> isMatch(*this);
+            for (Element* next = ElementTraversal::nextSibling(currentElement, isMatch); next; next = ElementTraversal::nextSibling(*next, isMatch)) {
+                if (++currentOffset == offset)
+                    return next;
+            }
+            return nullptr;
+        }
+        return traverseMatchingElementsForwardToOffset(currentElement, &rootNode(), offset, currentOffset, makeIsMatch(*this));
     }
+}
+
+Element* HTMLCollection::traverseBackwardToOffset(unsigned offset, Element& currentElement, unsigned& currentOffset) const
+{
+    ASSERT(currentOffset > offset);
+    ASSERT(canTraverseBackward());
     if (shouldOnlyIncludeDirectChildren()) {
-        Element* next = &currentElement;
-        while ((next = nextMatchingChildElement(*this, *next, root))) {
-            if (++currentOffset == offset)
-                return next;
+        IsMatch<HTMLCollection> isMatch(*this);
+        for (Element* previous = ElementTraversal::previousSibling(currentElement, isMatch); previous; previous = ElementTraversal::previousSibling(*previous, isMatch)) {
+            if (--currentOffset == offset)
+                return previous;
         }
-        return 0;
+        return nullptr;
     }
-    return traverseMatchingElementsForwardToOffset(*this, offset, currentElement, currentOffset, root);
+    return traverseMatchingElementsBackwardToOffset(currentElement, &rootNode(), offset, currentOffset, makeIsMatch(*this));
 }
 
-Node* HTMLCollection::namedItem(const AtomicString& name) const
+Element* HTMLCollection::namedItem(const AtomicString& name) const
 {
     // http://msdn.microsoft.com/workshop/author/dhtml/reference/methods/nameditem.asp
     // This method first searches for an object with a matching id
     // 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.
+    updateIdNameCache();
 
-    ContainerNode& root = rootNode();
-    unsigned i = 0;
-    for (Element* element = traverseToFirstElement(root); element; element = traverseNextElement(*element, root)) {
-        if (checkForNameMatch(*element, /* checkName */ false, name)) {
-            setItemCache(element, i);
-            return element;
-        }
-        i++;
-    }
+    const NamedItemCache& cache = namedItemCache();
+    WillBeHeapVector<RawPtrWillBeMember<Element>>* idResults = cache.getElementsById(name);
+    if (idResults && !idResults->isEmpty())
+        return idResults->first();
 
-    i = 0;
-    for (Element* element = traverseToFirstElement(root); element; element = traverseNextElement(*element, root)) {
-        if (checkForNameMatch(*element, /* checkName */ true, name)) {
-            setItemCache(element, i);
-            return element;
+    WillBeHeapVector<RawPtrWillBeMember<Element>>* nameResults = cache.getElementsByName(name);
+    if (nameResults && !nameResults->isEmpty())
+        return nameResults->first();
+
+    return nullptr;
+}
+
+bool HTMLCollection::namedPropertyQuery(const AtomicString& name, ExceptionState&)
+{
+    return namedItem(name);
+}
+
+void HTMLCollection::supportedPropertyNames(Vector<String>& names)
+{
+    // As per the specification (http://dom.spec.whatwg.org/#htmlcollection):
+    // The supported property names are the values from the list returned by these steps:
+    // 1. Let result be an empty list.
+    // 2. For each element represented by the collection, in tree order, run these substeps:
+    //   1. If element has an ID which is neither the empty string nor is in result, append element's ID to result.
+    //   2. If element is in the HTML namespace and has a name attribute whose value is neither the empty string
+    //      nor is in result, append element's name attribute value to result.
+    // 3. Return result.
+    HashSet<AtomicString> existingNames;
+    unsigned length = this->length();
+    for (unsigned i = 0; i < length; ++i) {
+        Element* element = item(i);
+        const AtomicString& idAttribute = element->getIdAttribute();
+        if (!idAttribute.isEmpty()) {
+            HashSet<AtomicString>::AddResult addResult = existingNames.add(idAttribute);
+            if (addResult.isNewEntry)
+                names.append(idAttribute);
+        }
+        if (!element->isHTMLElement())
+            continue;
+        const AtomicString& nameAttribute = element->getNameAttribute();
+        if (!nameAttribute.isEmpty() && (type() != DocAll || nameShouldBeVisibleInDocumentAll(toHTMLElement(*element)))) {
+            HashSet<AtomicString>::AddResult addResult = existingNames.add(nameAttribute);
+            if (addResult.isNewEntry)
+                names.append(nameAttribute);
         }
-        i++;
     }
+}
 
-    return 0;
+void HTMLCollection::namedPropertyEnumerator(Vector<String>& names, ExceptionState&)
+{
+    supportedPropertyNames(names);
 }
 
-void HTMLCollection::updateNameCache() const
+void HTMLCollection::updateIdNameCache() const
 {
-    if (hasNameCache())
+    if (hasValidIdNameCache())
         return;
 
-    ContainerNode& root = rootNode();
-    for (Element* element = traverseToFirstElement(root); element; element = traverseNextElement(*element, root)) {
+    OwnPtrWillBeRawPtr<NamedItemCache> cache = NamedItemCache::create();
+    unsigned length = this->length();
+    for (unsigned i = 0; i < length; ++i) {
+        Element* element = item(i);
         const AtomicString& idAttrVal = element->getIdAttribute();
         if (!idAttrVal.isEmpty())
-            appendIdCache(idAttrVal, element);
+            cache->addElementWithId(idAttrVal, element);
         if (!element->isHTMLElement())
             continue;
         const AtomicString& nameAttrVal = element->getNameAttribute();
         if (!nameAttrVal.isEmpty() && idAttrVal != nameAttrVal && (type() != DocAll || nameShouldBeVisibleInDocumentAll(toHTMLElement(*element))))
-            appendNameCache(nameAttrVal, element);
+            cache->addElementWithName(nameAttrVal, element);
     }
-
-    setHasNameCache();
+    // Set the named item cache last as traversing the tree may cause cache invalidation.
+    setNamedItemCache(cache.release());
 }
 
-void HTMLCollection::namedItems(const AtomicString& name, Vector<RefPtr<Node> >& result) const
+void HTMLCollection::namedItems(const AtomicString& name, WillBeHeapVector<RefPtrWillBeMember<Element>>& result) const
 {
     ASSERT(result.isEmpty());
     if (name.isEmpty())
         return;
 
-    updateNameCache();
-
-    Vector<Element*>* idResults = idCache(name);
-    Vector<Element*>* nameResults = nameCache(name);
+    updateIdNameCache();
 
-    for (unsigned i = 0; idResults && i < idResults->size(); ++i)
-        result.append(idResults->at(i));
+    const NamedItemCache& cache = namedItemCache();
+    if (WillBeHeapVector<RawPtrWillBeMember<Element>>* idResults = cache.getElementsById(name)) {
+        for (unsigned i = 0; i < idResults->size(); ++i)
+            result.append(idResults->at(i));
+    }
+    if (WillBeHeapVector<RawPtrWillBeMember<Element>>* nameResults = cache.getElementsByName(name)) {
+        for (unsigned i = 0; i < nameResults->size(); ++i)
+            result.append(nameResults->at(i));
+    }
+}
 
-    for (unsigned i = 0; nameResults && i < nameResults->size(); ++i)
-        result.append(nameResults->at(i));
+HTMLCollection::NamedItemCache::NamedItemCache()
+{
 }
 
-void HTMLCollection::append(NodeCacheMap& map, const AtomicString& key, Element* element)
+void HTMLCollection::trace(Visitor* visitor)
 {
-    OwnPtr<Vector<Element*> >& vector = map.add(key.impl(), nullptr).iterator->value;
-    if (!vector)
-        vector = adoptPtr(new Vector<Element*>);
-    vector->append(element);
+    visitor->trace(m_namedItemCache);
+    visitor->trace(m_collectionItemsCache);
+    LiveNodeListBase::trace(visitor);
 }
 
-} // namespace WebCore
+} // namespace blink