• 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  * Copyright (C) 2014 Samsung Electronics. All rights reserved.
6  *
7  * This library is free software; you can redistribute it and/or
8  * modify it under the terms of the GNU Library General Public
9  * License as published by the Free Software Foundation; either
10  * version 2 of the License, or (at your option) any later version.
11  *
12  * This library is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15  * Library General Public License for more details.
16  *
17  * You should have received a copy of the GNU Library General Public License
18  * along with this library; see the file COPYING.LIB.  If not, write to
19  * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
20  * Boston, MA 02110-1301, USA.
21  *
22  */
23 
24 #include "config.h"
25 #include "core/html/HTMLCollection.h"
26 
27 #include "core/HTMLNames.h"
28 #include "core/dom/ClassCollection.h"
29 #include "core/dom/ElementTraversal.h"
30 #include "core/dom/NodeRareData.h"
31 #include "core/html/DocumentNameCollection.h"
32 #include "core/html/HTMLElement.h"
33 #include "core/html/HTMLObjectElement.h"
34 #include "core/html/HTMLOptionElement.h"
35 #include "core/html/WindowNameCollection.h"
36 #include "wtf/HashSet.h"
37 
38 namespace WebCore {
39 
40 using namespace HTMLNames;
41 
shouldTypeOnlyIncludeDirectChildren(CollectionType type)42 static bool shouldTypeOnlyIncludeDirectChildren(CollectionType type)
43 {
44     switch (type) {
45     case ClassCollectionType:
46     case TagCollectionType:
47     case HTMLTagCollectionType:
48     case DocAll:
49     case DocAnchors:
50     case DocApplets:
51     case DocEmbeds:
52     case DocForms:
53     case DocImages:
54     case DocLinks:
55     case DocScripts:
56     case DocumentNamedItems:
57     case MapAreas:
58     case TableRows:
59     case SelectOptions:
60     case SelectedOptions:
61     case DataListOptions:
62     case WindowNamedItems:
63     case FormControls:
64         return false;
65     case NodeChildren:
66     case TRCells:
67     case TSectionRows:
68     case TableTBodies:
69         return true;
70     case NameNodeListType:
71     case RadioNodeListType:
72     case RadioImgNodeListType:
73     case LabelsNodeListType:
74         break;
75     }
76     ASSERT_NOT_REACHED();
77     return false;
78 }
79 
rootTypeFromCollectionType(CollectionType type)80 static NodeListRootType rootTypeFromCollectionType(CollectionType type)
81 {
82     switch (type) {
83     case DocImages:
84     case DocApplets:
85     case DocEmbeds:
86     case DocForms:
87     case DocLinks:
88     case DocAnchors:
89     case DocScripts:
90     case DocAll:
91     case WindowNamedItems:
92     case DocumentNamedItems:
93     case FormControls:
94         return NodeListIsRootedAtDocument;
95     case ClassCollectionType:
96     case TagCollectionType:
97     case HTMLTagCollectionType:
98     case NodeChildren:
99     case TableTBodies:
100     case TSectionRows:
101     case TableRows:
102     case TRCells:
103     case SelectOptions:
104     case SelectedOptions:
105     case DataListOptions:
106     case MapAreas:
107         return NodeListIsRootedAtNode;
108     case NameNodeListType:
109     case RadioNodeListType:
110     case RadioImgNodeListType:
111     case LabelsNodeListType:
112         break;
113     }
114     ASSERT_NOT_REACHED();
115     return NodeListIsRootedAtNode;
116 }
117 
invalidationTypeExcludingIdAndNameAttributes(CollectionType type)118 static NodeListInvalidationType invalidationTypeExcludingIdAndNameAttributes(CollectionType type)
119 {
120     switch (type) {
121     case TagCollectionType:
122     case HTMLTagCollectionType:
123     case DocImages:
124     case DocEmbeds:
125     case DocForms:
126     case DocScripts:
127     case DocAll:
128     case NodeChildren:
129     case TableTBodies:
130     case TSectionRows:
131     case TableRows:
132     case TRCells:
133     case SelectOptions:
134     case MapAreas:
135         return DoNotInvalidateOnAttributeChanges;
136     case DocApplets:
137     case SelectedOptions:
138     case DataListOptions:
139         // FIXME: We can do better some day.
140         return InvalidateOnAnyAttrChange;
141     case DocAnchors:
142         return InvalidateOnNameAttrChange;
143     case DocLinks:
144         return InvalidateOnHRefAttrChange;
145     case WindowNamedItems:
146         return InvalidateOnIdNameAttrChange;
147     case DocumentNamedItems:
148         return InvalidateOnIdNameAttrChange;
149     case FormControls:
150         return InvalidateForFormControls;
151     case ClassCollectionType:
152         return InvalidateOnClassAttrChange;
153     case NameNodeListType:
154     case RadioNodeListType:
155     case RadioImgNodeListType:
156     case LabelsNodeListType:
157         break;
158     }
159     ASSERT_NOT_REACHED();
160     return DoNotInvalidateOnAttributeChanges;
161 }
162 
HTMLCollection(ContainerNode & ownerNode,CollectionType type,ItemAfterOverrideType itemAfterOverrideType)163 HTMLCollection::HTMLCollection(ContainerNode& ownerNode, CollectionType type, ItemAfterOverrideType itemAfterOverrideType)
164     : LiveNodeListBase(ownerNode, rootTypeFromCollectionType(type), invalidationTypeExcludingIdAndNameAttributes(type), type)
165     , m_overridesItemAfter(itemAfterOverrideType == OverridesItemAfter)
166     , m_shouldOnlyIncludeDirectChildren(shouldTypeOnlyIncludeDirectChildren(type))
167 {
168     ScriptWrappable::init(this);
169 }
170 
create(ContainerNode & base,CollectionType type)171 PassRefPtrWillBeRawPtr<HTMLCollection> HTMLCollection::create(ContainerNode& base, CollectionType type)
172 {
173     return adoptRefWillBeNoop(new HTMLCollection(base, type, DoesNotOverrideItemAfter));
174 }
175 
~HTMLCollection()176 HTMLCollection::~HTMLCollection()
177 {
178 #if !ENABLE(OILPAN)
179     if (hasValidIdNameCache())
180         unregisterIdNameCacheFromDocument(document());
181     // Named HTMLCollection types remove cache by themselves.
182     if (isUnnamedHTMLCollectionType(type()))
183         ownerNode().nodeLists()->removeCache(this, type());
184 #endif
185 }
186 
invalidateCache(Document * oldDocument) const187 void HTMLCollection::invalidateCache(Document* oldDocument) const
188 {
189     m_collectionIndexCache.invalidate();
190     invalidateIdNameCacheMaps(oldDocument);
191 }
192 
193 template <class NodeListType>
194 inline bool isMatchingElement(const NodeListType&, const Element&);
195 
isMatchingElement(const HTMLCollection & htmlCollection,const Element & element)196 template <> inline bool isMatchingElement(const HTMLCollection& htmlCollection, const Element& element)
197 {
198     CollectionType type = htmlCollection.type();
199 
200     // These collections apply to any kind of Elements, not just HTMLElements.
201     switch (type) {
202     case DocAll:
203     case NodeChildren:
204         return true;
205     case ClassCollectionType:
206         return toClassCollection(htmlCollection).elementMatches(element);
207     case TagCollectionType:
208         return toTagCollection(htmlCollection).elementMatches(element);
209     case HTMLTagCollectionType:
210         return toHTMLTagCollection(htmlCollection).elementMatches(element);
211     case DocumentNamedItems:
212         return toDocumentNameCollection(htmlCollection).elementMatches(element);
213     case WindowNamedItems:
214         return toWindowNameCollection(htmlCollection).elementMatches(element);
215     default:
216         break;
217     }
218 
219     // The following only applies to HTMLElements.
220     if (!element.isHTMLElement())
221         return false;
222 
223     switch (type) {
224     case DocImages:
225         return element.hasLocalName(imgTag);
226     case DocScripts:
227         return element.hasLocalName(scriptTag);
228     case DocForms:
229         return element.hasLocalName(formTag);
230     case TableTBodies:
231         return element.hasLocalName(tbodyTag);
232     case TRCells:
233         return element.hasLocalName(tdTag) || element.hasLocalName(thTag);
234     case TSectionRows:
235         return element.hasLocalName(trTag);
236     case SelectOptions:
237         return element.hasLocalName(optionTag);
238     case SelectedOptions:
239         return element.hasLocalName(optionTag) && toHTMLOptionElement(element).selected();
240     case DataListOptions:
241         if (element.hasLocalName(optionTag)) {
242             const HTMLOptionElement& option = toHTMLOptionElement(element);
243             if (!option.isDisabledFormControl() && !option.value().isEmpty())
244                 return true;
245         }
246         return false;
247     case MapAreas:
248         return element.hasLocalName(areaTag);
249     case DocApplets:
250         return element.hasLocalName(appletTag) || (element.hasLocalName(objectTag) && toHTMLObjectElement(element).containsJavaApplet());
251     case DocEmbeds:
252         return element.hasLocalName(embedTag);
253     case DocLinks:
254         return (element.hasLocalName(aTag) || element.hasLocalName(areaTag)) && element.fastHasAttribute(hrefAttr);
255     case DocAnchors:
256         return element.hasLocalName(aTag) && element.fastHasAttribute(nameAttr);
257     case ClassCollectionType:
258     case TagCollectionType:
259     case HTMLTagCollectionType:
260     case DocAll:
261     case NodeChildren:
262     case FormControls:
263     case DocumentNamedItems:
264     case TableRows:
265     case WindowNamedItems:
266     case NameNodeListType:
267     case RadioNodeListType:
268     case RadioImgNodeListType:
269     case LabelsNodeListType:
270         ASSERT_NOT_REACHED();
271     }
272     return false;
273 }
274 
isMatchingElement(const ClassCollection & collection,const Element & element)275 template <> inline bool isMatchingElement(const ClassCollection& collection, const Element& element)
276 {
277     return collection.elementMatches(element);
278 }
279 
isMatchingElement(const HTMLTagCollection & collection,const Element & element)280 template <> inline bool isMatchingElement(const HTMLTagCollection& collection, const Element& element)
281 {
282     return collection.elementMatches(element);
283 }
284 
virtualItemAfter(Element *) const285 Element* HTMLCollection::virtualItemAfter(Element*) const
286 {
287     ASSERT_NOT_REACHED();
288     return 0;
289 }
290 
nameShouldBeVisibleInDocumentAll(const HTMLElement & element)291 static inline bool nameShouldBeVisibleInDocumentAll(const HTMLElement& element)
292 {
293     // http://www.whatwg.org/specs/web-apps/current-work/multipage/common-dom-interfaces.html#dom-htmlallcollection-nameditem:
294     // The document.all collection returns only certain types of elements by name,
295     // although it returns any type of element by id.
296     return element.hasLocalName(aTag)
297         || element.hasLocalName(appletTag)
298         || element.hasLocalName(areaTag)
299         || element.hasLocalName(embedTag)
300         || element.hasLocalName(formTag)
301         || element.hasLocalName(frameTag)
302         || element.hasLocalName(framesetTag)
303         || element.hasLocalName(iframeTag)
304         || element.hasLocalName(imgTag)
305         || element.hasLocalName(inputTag)
306         || element.hasLocalName(objectTag)
307         || element.hasLocalName(selectTag);
308 }
309 
firstMatchingChildElement(const HTMLCollection & nodeList)310 inline Element* firstMatchingChildElement(const HTMLCollection& nodeList)
311 {
312     Element* element = ElementTraversal::firstChild(nodeList.rootNode());
313     while (element && !isMatchingElement(nodeList, *element))
314         element = ElementTraversal::nextSibling(*element);
315     return element;
316 }
317 
lastMatchingChildElement(const HTMLCollection & nodeList)318 inline Element* lastMatchingChildElement(const HTMLCollection& nodeList)
319 {
320     Element* element = ElementTraversal::lastChild(nodeList.rootNode());
321     while (element && !isMatchingElement(nodeList, *element))
322         element = ElementTraversal::previousSibling(*element);
323     return element;
324 }
325 
nextMatchingChildElement(const HTMLCollection & nodeList,Element & current)326 inline Element* nextMatchingChildElement(const HTMLCollection& nodeList, Element& current)
327 {
328     Element* next = &current;
329     do {
330         next = ElementTraversal::nextSibling(*next);
331     } while (next && !isMatchingElement(nodeList, *next));
332     return next;
333 }
334 
previousMatchingChildElement(const HTMLCollection & nodeList,Element & current)335 inline Element* previousMatchingChildElement(const HTMLCollection& nodeList, Element& current)
336 {
337     Element* previous = &current;
338     do {
339         previous = ElementTraversal::previousSibling(*previous);
340     } while (previous && !isMatchingElement(nodeList, *previous));
341     return previous;
342 }
343 
traverseToFirstElement() const344 Element* HTMLCollection::traverseToFirstElement() const
345 {
346     switch (type()) {
347     case HTMLTagCollectionType:
348         return firstMatchingElement(toHTMLTagCollection(*this));
349     case ClassCollectionType:
350         return firstMatchingElement(toClassCollection(*this));
351     default:
352         if (overridesItemAfter())
353             return virtualItemAfter(0);
354         if (shouldOnlyIncludeDirectChildren())
355             return firstMatchingChildElement(*this);
356         return firstMatchingElement(*this);
357     }
358 }
359 
traverseToLastElement() const360 Element* HTMLCollection::traverseToLastElement() const
361 {
362     ASSERT(canTraverseBackward());
363     if (shouldOnlyIncludeDirectChildren())
364         return lastMatchingChildElement(*this);
365     return lastMatchingElement(*this);
366 }
367 
traverseForwardToOffset(unsigned offset,Element & currentElement,unsigned & currentOffset) const368 Element* HTMLCollection::traverseForwardToOffset(unsigned offset, Element& currentElement, unsigned& currentOffset) const
369 {
370     ASSERT(currentOffset < offset);
371     switch (type()) {
372     case HTMLTagCollectionType:
373         return traverseMatchingElementsForwardToOffset(toHTMLTagCollection(*this), offset, currentElement, currentOffset);
374     case ClassCollectionType:
375         return traverseMatchingElementsForwardToOffset(toClassCollection(*this), offset, currentElement, currentOffset);
376     default:
377         if (overridesItemAfter()) {
378             Element* next = &currentElement;
379             while ((next = virtualItemAfter(next))) {
380                 if (++currentOffset == offset)
381                     return next;
382             }
383             return 0;
384         }
385         if (shouldOnlyIncludeDirectChildren()) {
386             Element* next = &currentElement;
387             while ((next = nextMatchingChildElement(*this, *next))) {
388                 if (++currentOffset == offset)
389                     return next;
390             }
391             return 0;
392         }
393         return traverseMatchingElementsForwardToOffset(*this, offset, currentElement, currentOffset);
394     }
395 }
396 
traverseBackwardToOffset(unsigned offset,Element & currentElement,unsigned & currentOffset) const397 Element* HTMLCollection::traverseBackwardToOffset(unsigned offset, Element& currentElement, unsigned& currentOffset) const
398 {
399     ASSERT(currentOffset > offset);
400     ASSERT(canTraverseBackward());
401     if (shouldOnlyIncludeDirectChildren()) {
402         Element* previous = &currentElement;
403         while ((previous = previousMatchingChildElement(*this, *previous))) {
404             if (--currentOffset == offset)
405                 return previous;
406         }
407         return 0;
408     }
409     return traverseMatchingElementsBackwardToOffset(*this, offset, currentElement, currentOffset);
410 }
411 
namedItem(const AtomicString & name) const412 Element* HTMLCollection::namedItem(const AtomicString& name) const
413 {
414     // http://msdn.microsoft.com/workshop/author/dhtml/reference/methods/nameditem.asp
415     // This method first searches for an object with a matching id
416     // attribute. If a match is not found, the method then searches for an
417     // object with a matching name attribute, but only on those elements
418     // that are allowed a name attribute.
419     updateIdNameCache();
420 
421     const NamedItemCache& cache = namedItemCache();
422     WillBeHeapVector<RawPtrWillBeMember<Element> >* idResults = cache.getElementsById(name);
423     if (idResults && !idResults->isEmpty())
424         return idResults->first();
425 
426     WillBeHeapVector<RawPtrWillBeMember<Element> >* nameResults = cache.getElementsByName(name);
427     if (nameResults && !nameResults->isEmpty())
428         return nameResults->first();
429 
430     return 0;
431 }
432 
namedPropertyQuery(const AtomicString & name,ExceptionState &)433 bool HTMLCollection::namedPropertyQuery(const AtomicString& name, ExceptionState&)
434 {
435     return namedItem(name);
436 }
437 
supportedPropertyNames(Vector<String> & names)438 void HTMLCollection::supportedPropertyNames(Vector<String>& names)
439 {
440     // As per the specification (http://dom.spec.whatwg.org/#htmlcollection):
441     // The supported property names are the values from the list returned by these steps:
442     // 1. Let result be an empty list.
443     // 2. For each element represented by the collection, in tree order, run these substeps:
444     //   1. If element has an ID which is neither the empty string nor is in result, append element's ID to result.
445     //   2. If element is in the HTML namespace and has a name attribute whose value is neither the empty string
446     //      nor is in result, append element's name attribute value to result.
447     // 3. Return result.
448     HashSet<AtomicString> existingNames;
449     unsigned length = this->length();
450     for (unsigned i = 0; i < length; ++i) {
451         Element* element = item(i);
452         const AtomicString& idAttribute = element->getIdAttribute();
453         if (!idAttribute.isEmpty()) {
454             HashSet<AtomicString>::AddResult addResult = existingNames.add(idAttribute);
455             if (addResult.isNewEntry)
456                 names.append(idAttribute);
457         }
458         if (!element->isHTMLElement())
459             continue;
460         const AtomicString& nameAttribute = element->getNameAttribute();
461         if (!nameAttribute.isEmpty() && (type() != DocAll || nameShouldBeVisibleInDocumentAll(toHTMLElement(*element)))) {
462             HashSet<AtomicString>::AddResult addResult = existingNames.add(nameAttribute);
463             if (addResult.isNewEntry)
464                 names.append(nameAttribute);
465         }
466     }
467 }
468 
namedPropertyEnumerator(Vector<String> & names,ExceptionState &)469 void HTMLCollection::namedPropertyEnumerator(Vector<String>& names, ExceptionState&)
470 {
471     supportedPropertyNames(names);
472 }
473 
updateIdNameCache() const474 void HTMLCollection::updateIdNameCache() const
475 {
476     if (hasValidIdNameCache())
477         return;
478 
479     OwnPtrWillBeRawPtr<NamedItemCache> cache = NamedItemCache::create();
480     unsigned length = this->length();
481     for (unsigned i = 0; i < length; ++i) {
482         Element* element = item(i);
483         const AtomicString& idAttrVal = element->getIdAttribute();
484         if (!idAttrVal.isEmpty())
485             cache->addElementWithId(idAttrVal, element);
486         if (!element->isHTMLElement())
487             continue;
488         const AtomicString& nameAttrVal = element->getNameAttribute();
489         if (!nameAttrVal.isEmpty() && idAttrVal != nameAttrVal && (type() != DocAll || nameShouldBeVisibleInDocumentAll(toHTMLElement(*element))))
490             cache->addElementWithName(nameAttrVal, element);
491     }
492     // Set the named item cache last as traversing the tree may cause cache invalidation.
493     setNamedItemCache(cache.release());
494 }
495 
namedItems(const AtomicString & name,WillBeHeapVector<RefPtrWillBeMember<Element>> & result) const496 void HTMLCollection::namedItems(const AtomicString& name, WillBeHeapVector<RefPtrWillBeMember<Element> >& result) const
497 {
498     ASSERT(result.isEmpty());
499     if (name.isEmpty())
500         return;
501 
502     updateIdNameCache();
503 
504     const NamedItemCache& cache = namedItemCache();
505     if (WillBeHeapVector<RawPtrWillBeMember<Element> >* idResults = cache.getElementsById(name)) {
506         for (unsigned i = 0; i < idResults->size(); ++i)
507             result.append(idResults->at(i));
508     }
509     if (WillBeHeapVector<RawPtrWillBeMember<Element> >* nameResults = cache.getElementsByName(name)) {
510         for (unsigned i = 0; i < nameResults->size(); ++i)
511             result.append(nameResults->at(i));
512     }
513 }
514 
NamedItemCache()515 HTMLCollection::NamedItemCache::NamedItemCache()
516 {
517 }
518 
trace(Visitor * visitor)519 void HTMLCollection::trace(Visitor* visitor)
520 {
521     visitor->trace(m_namedItemCache);
522     visitor->trace(m_collectionIndexCache);
523     LiveNodeListBase::trace(visitor);
524 }
525 
526 } // namespace WebCore
527