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 = ¤t;
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 = ¤tElement;
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 = ¤t;
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 = ¤tElement;
571 while ((next = virtualItemAfter(offsetInArray, next))) {
572 if (++currentOffset == offset)
573 return next;
574 }
575 return 0;
576 }
577 if (shouldOnlyIncludeDirectChildren()) {
578 Element* next = ¤tElement;
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