• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 1999 Lars Knoll (knoll@kde.org)
3  *           (C) 1999 Antti Koivisto (koivisto@kde.org)
4  * Copyright (C) 2003, 2004, 2005, 2006, 2007, 2008, 2011, 2012 Apple Inc. All rights reserved.
5  *
6  * This library is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU Library General Public
8  * License as published by the Free Software Foundation; either
9  * version 2 of the License, or (at your option) any later version.
10  *
11  * This library is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14  * Library General Public License for more details.
15  *
16  * You should have received a copy of the GNU Library General Public License
17  * along with this library; see the file COPYING.LIB.  If not, write to
18  * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
19  * Boston, MA 02110-1301, USA.
20  *
21  */
22 
23 #include "config.h"
24 #include "core/html/HTMLCollection.h"
25 
26 #include "HTMLNames.h"
27 #include "core/dom/ClassNodeList.h"
28 #include "core/dom/ElementTraversal.h"
29 #include "core/dom/NodeList.h"
30 #include "core/dom/NodeRareData.h"
31 #include "core/dom/NodeTraversal.h"
32 #include "core/html/HTMLElement.h"
33 #include "core/html/HTMLObjectElement.h"
34 #include "core/html/HTMLOptionElement.h"
35 
36 namespace WebCore {
37 
38 using namespace HTMLNames;
39 
shouldOnlyIncludeDirectChildren(CollectionType type)40 static bool shouldOnlyIncludeDirectChildren(CollectionType type)
41 {
42     switch (type) {
43     case DocAll:
44     case DocAnchors:
45     case DocApplets:
46     case DocEmbeds:
47     case DocForms:
48     case DocImages:
49     case DocLinks:
50     case DocScripts:
51     case DocumentNamedItems:
52     case MapAreas:
53     case TableRows:
54     case SelectOptions:
55     case SelectedOptions:
56     case DataListOptions:
57     case WindowNamedItems:
58     case FormControls:
59         return false;
60     case NodeChildren:
61     case TRCells:
62     case TSectionRows:
63     case TableTBodies:
64         return true;
65     case ChildNodeListType:
66     case ClassNodeListType:
67     case NameNodeListType:
68     case TagNodeListType:
69     case HTMLTagNodeListType:
70     case RadioNodeListType:
71     case LabelsNodeListType:
72         break;
73     }
74     ASSERT_NOT_REACHED();
75     return false;
76 }
77 
rootTypeFromCollectionType(CollectionType type)78 static NodeListRootType rootTypeFromCollectionType(CollectionType type)
79 {
80     switch (type) {
81     case DocImages:
82     case DocApplets:
83     case DocEmbeds:
84     case DocForms:
85     case DocLinks:
86     case DocAnchors:
87     case DocScripts:
88     case DocAll:
89     case WindowNamedItems:
90     case DocumentNamedItems:
91     case FormControls:
92         return NodeListIsRootedAtDocument;
93     case NodeChildren:
94     case TableTBodies:
95     case TSectionRows:
96     case TableRows:
97     case TRCells:
98     case SelectOptions:
99     case SelectedOptions:
100     case DataListOptions:
101     case MapAreas:
102         return NodeListIsRootedAtNode;
103     case ChildNodeListType:
104     case ClassNodeListType:
105     case NameNodeListType:
106     case TagNodeListType:
107     case HTMLTagNodeListType:
108     case RadioNodeListType:
109     case LabelsNodeListType:
110         break;
111     }
112     ASSERT_NOT_REACHED();
113     return NodeListIsRootedAtNode;
114 }
115 
invalidationTypeExcludingIdAndNameAttributes(CollectionType type)116 static NodeListInvalidationType invalidationTypeExcludingIdAndNameAttributes(CollectionType type)
117 {
118     switch (type) {
119     case DocImages:
120     case DocEmbeds:
121     case DocForms:
122     case DocScripts:
123     case DocAll:
124     case NodeChildren:
125     case TableTBodies:
126     case TSectionRows:
127     case TableRows:
128     case TRCells:
129     case SelectOptions:
130     case MapAreas:
131         return DoNotInvalidateOnAttributeChanges;
132     case DocApplets:
133     case SelectedOptions:
134     case DataListOptions:
135         // FIXME: We can do better some day.
136         return InvalidateOnAnyAttrChange;
137     case DocAnchors:
138         return InvalidateOnNameAttrChange;
139     case DocLinks:
140         return InvalidateOnHRefAttrChange;
141     case WindowNamedItems:
142         return InvalidateOnIdNameAttrChange;
143     case DocumentNamedItems:
144         return InvalidateOnIdNameAttrChange;
145     case FormControls:
146         return InvalidateForFormControls;
147     case ChildNodeListType:
148     case ClassNodeListType:
149     case NameNodeListType:
150     case TagNodeListType:
151     case HTMLTagNodeListType:
152     case RadioNodeListType:
153     case LabelsNodeListType:
154         break;
155     }
156     ASSERT_NOT_REACHED();
157     return DoNotInvalidateOnAttributeChanges;
158 }
159 
HTMLCollection(Node * ownerNode,CollectionType type,ItemAfterOverrideType itemAfterOverrideType)160 HTMLCollection::HTMLCollection(Node* ownerNode, CollectionType type, ItemAfterOverrideType itemAfterOverrideType)
161     : LiveNodeListBase(ownerNode, rootTypeFromCollectionType(type), invalidationTypeExcludingIdAndNameAttributes(type),
162         WebCore::shouldOnlyIncludeDirectChildren(type), type, itemAfterOverrideType)
163 {
164     ScriptWrappable::init(this);
165 }
166 
create(Node * base,CollectionType type)167 PassRefPtr<HTMLCollection> HTMLCollection::create(Node* base, CollectionType type)
168 {
169     return adoptRef(new HTMLCollection(base, type, DoesNotOverrideItemAfter));
170 }
171 
~HTMLCollection()172 HTMLCollection::~HTMLCollection()
173 {
174     // HTMLNameCollection removes cache by itself.
175     if (type() != WindowNamedItems && type() != DocumentNamedItems)
176         ownerNode()->nodeLists()->removeCacheWithAtomicName(this, type());
177 }
178 
179 template <class NodeListType>
180 inline bool isMatchingElement(const NodeListType*, Element*);
181 
isMatchingElement(const HTMLCollection * htmlCollection,Element * element)182 template <> inline bool isMatchingElement(const HTMLCollection* htmlCollection, Element* element)
183 {
184     CollectionType type = htmlCollection->type();
185     if (!element->isHTMLElement() && !(type == DocAll || type == NodeChildren || type == WindowNamedItems))
186         return false;
187 
188     switch (type) {
189     case DocImages:
190         return element->hasLocalName(imgTag);
191     case DocScripts:
192         return element->hasLocalName(scriptTag);
193     case DocForms:
194         return element->hasLocalName(formTag);
195     case TableTBodies:
196         return element->hasLocalName(tbodyTag);
197     case TRCells:
198         return element->hasLocalName(tdTag) || element->hasLocalName(thTag);
199     case TSectionRows:
200         return element->hasLocalName(trTag);
201     case SelectOptions:
202         return element->hasLocalName(optionTag);
203     case SelectedOptions:
204         return element->hasLocalName(optionTag) && toHTMLOptionElement(element)->selected();
205     case DataListOptions:
206         if (element->hasLocalName(optionTag)) {
207             HTMLOptionElement* option = toHTMLOptionElement(element);
208             if (!option->isDisabledFormControl() && !option->value().isEmpty())
209                 return true;
210         }
211         return false;
212     case MapAreas:
213         return element->hasLocalName(areaTag);
214     case DocApplets:
215         return element->hasLocalName(appletTag) || (element->hasLocalName(objectTag) && toHTMLObjectElement(element)->containsJavaApplet());
216     case DocEmbeds:
217         return element->hasLocalName(embedTag);
218     case DocLinks:
219         return (element->hasLocalName(aTag) || element->hasLocalName(areaTag)) && element->fastHasAttribute(hrefAttr);
220     case DocAnchors:
221         return element->hasLocalName(aTag) && element->fastHasAttribute(nameAttr);
222     case DocAll:
223     case NodeChildren:
224         return true;
225     case FormControls:
226     case DocumentNamedItems:
227     case TableRows:
228     case WindowNamedItems:
229     case ChildNodeListType:
230     case ClassNodeListType:
231     case NameNodeListType:
232     case TagNodeListType:
233     case HTMLTagNodeListType:
234     case RadioNodeListType:
235     case LabelsNodeListType:
236         ASSERT_NOT_REACHED();
237     }
238     return false;
239 }
240 
isMatchingElement(const LiveNodeList * nodeList,Element * element)241 template <> inline bool isMatchingElement(const LiveNodeList* nodeList, Element* element)
242 {
243     return nodeList->nodeMatches(element);
244 }
245 
isMatchingElement(const HTMLTagNodeList * nodeList,Element * element)246 template <> inline bool isMatchingElement(const HTMLTagNodeList* nodeList, Element* element)
247 {
248     return nodeList->nodeMatchesInlined(element);
249 }
250 
isMatchingElement(const ClassNodeList * nodeList,Element * element)251 template <> inline bool isMatchingElement(const ClassNodeList* nodeList, Element* element)
252 {
253     return nodeList->nodeMatchesInlined(element);
254 }
255 
previousNode(Node & base,Node & previous,bool onlyIncludeDirectChildren)256 static Node* previousNode(Node& base, Node& previous, bool onlyIncludeDirectChildren)
257 {
258     return onlyIncludeDirectChildren ? previous.previousSibling() : NodeTraversal::previous(previous, &base);
259 }
260 
lastDescendent(Node & node)261 static inline Node* lastDescendent(Node& node)
262 {
263     Node* descendent = node.lastChild();
264     for (Node* current = descendent; current; current = current->lastChild())
265         descendent = current;
266     return descendent;
267 }
268 
lastNode(Node & rootNode,bool onlyIncludeDirectChildren)269 static Node* lastNode(Node& rootNode, bool onlyIncludeDirectChildren)
270 {
271     return onlyIncludeDirectChildren ? rootNode.lastChild() : lastDescendent(rootNode);
272 }
273 
iterateForPreviousNode(Node * current) const274 ALWAYS_INLINE Node* LiveNodeListBase::iterateForPreviousNode(Node* current) const
275 {
276     bool onlyIncludeDirectChildren = shouldOnlyIncludeDirectChildren();
277     CollectionType collectionType = type();
278     Node& rootNode = this->rootNode();
279     for (; current; current = previousNode(rootNode, *current, onlyIncludeDirectChildren)) {
280         if (isNodeList(collectionType)) {
281             if (current->isElementNode() && isMatchingElement(static_cast<const LiveNodeList*>(this), toElement(current)))
282                 return toElement(current);
283         } else {
284             if (current->isElementNode() && isMatchingElement(static_cast<const HTMLCollection*>(this), toElement(current)))
285                 return toElement(current);
286         }
287     }
288     return 0;
289 }
290 
itemBefore(Node * previous) const291 ALWAYS_INLINE Node* LiveNodeListBase::itemBefore(Node* previous) const
292 {
293     Node* current;
294     if (LIKELY(!!previous)) // Without this LIKELY, length() and item() can be 10% slower.
295         current = previousNode(rootNode(), *previous, shouldOnlyIncludeDirectChildren());
296     else
297         current = lastNode(rootNode(), shouldOnlyIncludeDirectChildren());
298 
299     if (type() == ChildNodeListType)
300         return current;
301     return iterateForPreviousNode(current);
302 }
303 
304 template <class NodeListType>
firstMatchingElement(const NodeListType * nodeList,ContainerNode & root)305 inline Element* firstMatchingElement(const NodeListType* nodeList, ContainerNode& root)
306 {
307     Element* element = ElementTraversal::firstWithin(root);
308     while (element && !isMatchingElement(nodeList, element))
309         element = ElementTraversal::next(*element, &root);
310     return element;
311 }
312 
313 template <class NodeListType>
nextMatchingElement(const NodeListType * nodeList,Element & current,ContainerNode * root)314 inline Element* nextMatchingElement(const NodeListType* nodeList, Element& current, ContainerNode* root)
315 {
316     Element* next = &current;
317     do {
318         next = ElementTraversal::next(*next, root);
319     } while (next && !isMatchingElement(nodeList, next));
320     return next;
321 }
322 
323 template <class NodeListType>
traverseMatchingElementsForwardToOffset(const NodeListType * nodeList,unsigned offset,Element & currentElement,unsigned & currentOffset,ContainerNode * root)324 inline Element* traverseMatchingElementsForwardToOffset(const NodeListType* nodeList, unsigned offset, Element& currentElement, unsigned& currentOffset, ContainerNode* root)
325 {
326     ASSERT(currentOffset < offset);
327     Element* next = &currentElement;
328     while ((next = nextMatchingElement(nodeList, *next, root))) {
329         if (++currentOffset == offset)
330             return next;
331     }
332     return 0;
333 }
334 
335 // FIXME: This should be in ChildNodeList
traverseChildNodeListForwardToOffset(unsigned offset,Node * currentNode,unsigned & currentOffset) const336 inline Node* LiveNodeListBase::traverseChildNodeListForwardToOffset(unsigned offset, Node* currentNode, unsigned& currentOffset) const
337 {
338     ASSERT(type() == ChildNodeListType);
339     ASSERT(currentOffset < offset);
340     while ((currentNode = currentNode->nextSibling())) {
341         if (++currentOffset == offset)
342             return currentNode;
343     }
344     return 0;
345 }
346 
347 // FIXME: This should be in LiveNodeList
traverseLiveNodeListFirstElement(ContainerNode & root) const348 inline Element* LiveNodeListBase::traverseLiveNodeListFirstElement(ContainerNode& root) const
349 {
350     ASSERT(isNodeList(type()));
351     ASSERT(type() != ChildNodeListType);
352     if (type() == HTMLTagNodeListType)
353         return firstMatchingElement(static_cast<const HTMLTagNodeList*>(this), root);
354     if (type() == ClassNodeListType)
355         return firstMatchingElement(static_cast<const ClassNodeList*>(this), root);
356     return firstMatchingElement(static_cast<const LiveNodeList*>(this), root);
357 }
358 
359 // FIXME: This should be in LiveNodeList
traverseLiveNodeListForwardToOffset(unsigned offset,Element & currentElement,unsigned & currentOffset,ContainerNode * root) const360 inline Element* LiveNodeListBase::traverseLiveNodeListForwardToOffset(unsigned offset, Element& currentElement, unsigned& currentOffset, ContainerNode* root) const
361 {
362     ASSERT(isNodeList(type()));
363     ASSERT(type() != ChildNodeListType);
364     if (type() == HTMLTagNodeListType)
365         return traverseMatchingElementsForwardToOffset(static_cast<const HTMLTagNodeList*>(this), offset, currentElement, currentOffset, root);
366     if (type() == ClassNodeListType)
367         return traverseMatchingElementsForwardToOffset(static_cast<const ClassNodeList*>(this), offset, currentElement, currentOffset, root);
368     return traverseMatchingElementsForwardToOffset(static_cast<const LiveNodeList*>(this), offset, currentElement, currentOffset, root);
369 }
370 
isLastItemCloserThanLastOrCachedItem(unsigned offset) const371 bool ALWAYS_INLINE LiveNodeListBase::isLastItemCloserThanLastOrCachedItem(unsigned offset) const
372 {
373     ASSERT(isLengthCacheValid());
374     unsigned distanceFromLastItem = cachedLength() - offset;
375     if (!isItemCacheValid())
376         return distanceFromLastItem < offset;
377 
378     return cachedItemOffset() < offset && distanceFromLastItem < offset - cachedItemOffset();
379 }
380 
isFirstItemCloserThanCachedItem(unsigned offset) const381 bool ALWAYS_INLINE LiveNodeListBase::isFirstItemCloserThanCachedItem(unsigned offset) const
382 {
383     ASSERT(isItemCacheValid());
384     if (cachedItemOffset() < offset)
385         return false;
386 
387     unsigned distanceFromCachedItem = cachedItemOffset() - offset;
388     return offset < distanceFromCachedItem;
389 }
390 
setItemCache(Node * item,unsigned offset,unsigned elementsArrayOffset) const391 ALWAYS_INLINE void LiveNodeListBase::setItemCache(Node* item, unsigned offset, unsigned elementsArrayOffset) const
392 {
393     setItemCache(item, offset);
394     if (overridesItemAfter()) {
395         ASSERT_WITH_SECURITY_IMPLICATION(item->isElementNode());
396         static_cast<const HTMLCollection*>(this)->m_cachedElementsArrayOffset = elementsArrayOffset;
397     } else
398         ASSERT(!elementsArrayOffset);
399 }
400 
length() const401 unsigned LiveNodeListBase::length() const
402 {
403     if (isLengthCacheValid())
404         return cachedLength();
405 
406     item(UINT_MAX);
407     ASSERT(isLengthCacheValid());
408 
409     return cachedLength();
410 }
411 
412 // FIXME: It is silly that these functions are in HTMLCollection.cpp.
item(unsigned offset) const413 Node* LiveNodeListBase::item(unsigned offset) const
414 {
415     if (isItemCacheValid() && cachedItemOffset() == offset)
416         return cachedItem();
417 
418     if (isLengthCacheValid() && cachedLength() <= offset)
419         return 0;
420 
421     ContainerNode* root = rootContainerNode();
422     if (!root) {
423         // FIMXE: In someTextNode.childNodes case the root is Text. We shouldn't even make a LiveNodeList for that.
424         setLengthCache(0);
425         return 0;
426     }
427 
428     if (isLengthCacheValid() && !overridesItemAfter() && isLastItemCloserThanLastOrCachedItem(offset)) {
429         Node* lastItem = itemBefore(0);
430         ASSERT(lastItem);
431         setItemCache(lastItem, cachedLength() - 1, 0);
432     } else if (!isItemCacheValid() || isFirstItemCloserThanCachedItem(offset) || (overridesItemAfter() && offset < cachedItemOffset())) {
433         unsigned offsetInArray = 0;
434         Node* firstItem;
435         if (type() == ChildNodeListType)
436             firstItem = root->firstChild();
437         else if (isNodeList(type()))
438             firstItem = traverseLiveNodeListFirstElement(*root);
439         else
440             firstItem = static_cast<const HTMLCollection*>(this)->traverseFirstElement(offsetInArray, *root);
441 
442         if (!firstItem) {
443             setLengthCache(0);
444             return 0;
445         }
446         setItemCache(firstItem, 0, offsetInArray);
447         ASSERT(!cachedItemOffset());
448     }
449 
450     if (cachedItemOffset() == offset)
451         return cachedItem();
452 
453     return itemBeforeOrAfterCachedItem(offset, root);
454 }
455 
itemBeforeOrAfterCachedItem(unsigned offset,ContainerNode * root) const456 inline Node* LiveNodeListBase::itemBeforeOrAfterCachedItem(unsigned offset, ContainerNode* root) const
457 {
458     unsigned currentOffset = cachedItemOffset();
459     Node* currentItem = cachedItem();
460     ASSERT(currentItem);
461     ASSERT(currentOffset != offset);
462 
463     if (offset < cachedItemOffset()) {
464         ASSERT(!overridesItemAfter());
465         while ((currentItem = itemBefore(currentItem))) {
466             ASSERT(currentOffset);
467             currentOffset--;
468             if (currentOffset == offset) {
469                 setItemCache(currentItem, currentOffset, 0);
470                 return currentItem;
471             }
472         }
473         ASSERT_NOT_REACHED();
474         return 0;
475     }
476 
477     unsigned offsetInArray = 0;
478     if (type() == ChildNodeListType)
479         currentItem = traverseChildNodeListForwardToOffset(offset, currentItem, currentOffset);
480     else if (isNodeList(type()))
481         currentItem = traverseLiveNodeListForwardToOffset(offset, toElement(*currentItem), currentOffset, root);
482     else
483         currentItem = static_cast<const HTMLCollection*>(this)->traverseForwardToOffset(offset, toElement(*currentItem), currentOffset, offsetInArray, root);
484 
485     if (!currentItem) {
486         // Did not find the item. On plus side, we now know the length.
487         setLengthCache(currentOffset + 1);
488         return 0;
489     }
490     setItemCache(currentItem, currentOffset, offsetInArray);
491     return currentItem;
492 }
493 
virtualItemAfter(unsigned &,Element *) const494 Element* HTMLCollection::virtualItemAfter(unsigned&, Element*) const
495 {
496     ASSERT_NOT_REACHED();
497     return 0;
498 }
499 
nameShouldBeVisibleInDocumentAll(HTMLElement * element)500 static inline bool nameShouldBeVisibleInDocumentAll(HTMLElement* element)
501 {
502     // The document.all collection returns only certain types of elements by name,
503     // although it returns any type of element by id.
504     return element->hasLocalName(appletTag)
505         || element->hasLocalName(embedTag)
506         || element->hasLocalName(formTag)
507         || element->hasLocalName(imgTag)
508         || element->hasLocalName(inputTag)
509         || element->hasLocalName(objectTag)
510         || element->hasLocalName(selectTag);
511 }
512 
checkForNameMatch(Element * element,bool checkName,const AtomicString & name) const513 bool HTMLCollection::checkForNameMatch(Element* element, bool checkName, const AtomicString& name) const
514 {
515     if (!element->isHTMLElement())
516         return false;
517 
518     HTMLElement* e = toHTMLElement(element);
519     if (!checkName)
520         return e->getIdAttribute() == name;
521 
522     if (type() == DocAll && !nameShouldBeVisibleInDocumentAll(e))
523         return false;
524 
525     return e->getNameAttribute() == name && e->getIdAttribute() != name;
526 }
527 
firstMatchingChildElement(const HTMLCollection * nodeList,ContainerNode & root)528 inline Element* firstMatchingChildElement(const HTMLCollection* nodeList, ContainerNode& root)
529 {
530     Element* element = ElementTraversal::firstWithin(root);
531     while (element && !isMatchingElement(nodeList, element))
532         element = ElementTraversal::nextSkippingChildren(*element, &root);
533     return element;
534 }
535 
nextMatchingChildElement(const HTMLCollection * nodeList,Element & current,ContainerNode * root)536 inline Element* nextMatchingChildElement(const HTMLCollection* nodeList, Element& current, ContainerNode* root)
537 {
538     Element* next = &current;
539     do {
540         next = ElementTraversal::nextSkippingChildren(*next, root);
541     } while (next && !isMatchingElement(nodeList, next));
542     return next;
543 }
544 
traverseFirstElement(unsigned & offsetInArray,ContainerNode & root) const545 inline Element* HTMLCollection::traverseFirstElement(unsigned& offsetInArray, ContainerNode& root) const
546 {
547     if (overridesItemAfter())
548         return virtualItemAfter(offsetInArray, 0);
549     ASSERT(!offsetInArray);
550     if (shouldOnlyIncludeDirectChildren())
551         return firstMatchingChildElement(static_cast<const HTMLCollection*>(this), root);
552     return firstMatchingElement(static_cast<const HTMLCollection*>(this), root);
553 }
554 
traverseNextElement(unsigned & offsetInArray,Element & previous,ContainerNode * root) const555 inline Element* HTMLCollection::traverseNextElement(unsigned& offsetInArray, Element& previous, ContainerNode* root) const
556 {
557     if (overridesItemAfter())
558         return virtualItemAfter(offsetInArray, &previous);
559     ASSERT(!offsetInArray);
560     if (shouldOnlyIncludeDirectChildren())
561         return nextMatchingChildElement(this, previous, root);
562     return nextMatchingElement(this, previous, root);
563 }
564 
traverseForwardToOffset(unsigned offset,Element & currentElement,unsigned & currentOffset,unsigned & offsetInArray,ContainerNode * root) const565 inline Element* HTMLCollection::traverseForwardToOffset(unsigned offset, Element& currentElement, unsigned& currentOffset, unsigned& offsetInArray, ContainerNode* root) const
566 {
567     ASSERT(currentOffset < offset);
568     if (overridesItemAfter()) {
569         offsetInArray = m_cachedElementsArrayOffset;
570         Element* next = &currentElement;
571         while ((next = virtualItemAfter(offsetInArray, next))) {
572             if (++currentOffset == offset)
573                 return next;
574         }
575         return 0;
576     }
577     if (shouldOnlyIncludeDirectChildren()) {
578         Element* next = &currentElement;
579         while ((next = nextMatchingChildElement(this, *next, root))) {
580             if (++currentOffset == offset)
581                 return next;
582         }
583         return 0;
584     }
585     return traverseMatchingElementsForwardToOffset(this, offset, currentElement, currentOffset, root);
586 }
587 
namedItem(const AtomicString & name) const588 Node* HTMLCollection::namedItem(const AtomicString& name) const
589 {
590     // http://msdn.microsoft.com/workshop/author/dhtml/reference/methods/nameditem.asp
591     // This method first searches for an object with a matching id
592     // attribute. If a match is not found, the method then searches for an
593     // object with a matching name attribute, but only on those elements
594     // that are allowed a name attribute.
595 
596     ContainerNode* root = rootContainerNode();
597     if (!root)
598         return 0;
599 
600     unsigned arrayOffset = 0;
601     unsigned i = 0;
602     for (Element* element = traverseFirstElement(arrayOffset, *root); element; element = traverseNextElement(arrayOffset, *element, root)) {
603         if (checkForNameMatch(element, /* checkName */ false, name)) {
604             setItemCache(element, i, arrayOffset);
605             return element;
606         }
607         i++;
608     }
609 
610     i = 0;
611     for (Element* element = traverseFirstElement(arrayOffset, *root); element; element = traverseNextElement(arrayOffset, *element, root)) {
612         if (checkForNameMatch(element, /* checkName */ true, name)) {
613             setItemCache(element, i, arrayOffset);
614             return element;
615         }
616         i++;
617     }
618 
619     return 0;
620 }
621 
updateNameCache() const622 void HTMLCollection::updateNameCache() const
623 {
624     if (hasNameCache())
625         return;
626 
627     ContainerNode* root = rootContainerNode();
628     if (!root)
629         return;
630 
631     unsigned arrayOffset = 0;
632     for (Element* element = traverseFirstElement(arrayOffset, *root); element; element = traverseNextElement(arrayOffset, *element, root)) {
633         const AtomicString& idAttrVal = element->getIdAttribute();
634         if (!idAttrVal.isEmpty())
635             appendIdCache(idAttrVal, element);
636         if (!element->isHTMLElement())
637             continue;
638         const AtomicString& nameAttrVal = element->getNameAttribute();
639         if (!nameAttrVal.isEmpty() && idAttrVal != nameAttrVal && (type() != DocAll || nameShouldBeVisibleInDocumentAll(toHTMLElement(element))))
640             appendNameCache(nameAttrVal, element);
641     }
642 
643     setHasNameCache();
644 }
645 
hasNamedItem(const AtomicString & name) const646 bool HTMLCollection::hasNamedItem(const AtomicString& name) const
647 {
648     if (name.isEmpty())
649         return false;
650 
651     updateNameCache();
652 
653     if (Vector<Element*>* cache = idCache(name)) {
654         if (!cache->isEmpty())
655             return true;
656     }
657 
658     if (Vector<Element*>* cache = nameCache(name)) {
659         if (!cache->isEmpty())
660             return true;
661     }
662 
663     return false;
664 }
665 
namedItems(const AtomicString & name,Vector<RefPtr<Node>> & result) const666 void HTMLCollection::namedItems(const AtomicString& name, Vector<RefPtr<Node> >& result) const
667 {
668     ASSERT(result.isEmpty());
669     if (name.isEmpty())
670         return;
671 
672     updateNameCache();
673 
674     Vector<Element*>* idResults = idCache(name);
675     Vector<Element*>* nameResults = nameCache(name);
676 
677     for (unsigned i = 0; idResults && i < idResults->size(); ++i)
678         result.append(idResults->at(i));
679 
680     for (unsigned i = 0; nameResults && i < nameResults->size(); ++i)
681         result.append(nameResults->at(i));
682 }
683 
append(NodeCacheMap & map,const AtomicString & key,Element * element)684 void HTMLCollection::append(NodeCacheMap& map, const AtomicString& key, Element* element)
685 {
686     OwnPtr<Vector<Element*> >& vector = map.add(key.impl(), nullptr).iterator->value;
687     if (!vector)
688         vector = adoptPtr(new Vector<Element*>);
689     vector->append(element);
690 }
691 
692 } // namespace WebCore
693