2 * Copyright (C) 1999 Lars Knoll (knoll@kde.org)
3 * (C) 1999 Antti Koivisto (koivisto@kde.org)
4 * Copyright (C) 2003-2008, 2011, 2012, 2014 Apple Inc. All rights reserved.
5 * Copyright (C) 2014 Samsung Electronics. All rights reserved.
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.
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.
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.
25 #include "core/html/HTMLCollection.h"
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/HTMLTagCollection.h"
36 #include "core/html/WindowNameCollection.h"
37 #include "wtf/HashSet.h"
41 using namespace HTMLNames;
43 static bool shouldTypeOnlyIncludeDirectChildren(CollectionType type)
46 case ClassCollectionType:
47 case TagCollectionType:
48 case HTMLTagCollectionType:
57 case DocumentNamedItems:
63 case WindowNamedItems:
71 case NameNodeListType:
72 case RadioNodeListType:
73 case RadioImgNodeListType:
74 case LabelsNodeListType:
81 static NodeListRootType rootTypeFromCollectionType(CollectionType type)
92 case WindowNamedItems:
93 case DocumentNamedItems:
95 return NodeListIsRootedAtDocument;
96 case ClassCollectionType:
97 case TagCollectionType:
98 case HTMLTagCollectionType:
105 case SelectedOptions:
106 case DataListOptions:
108 return NodeListIsRootedAtNode;
109 case NameNodeListType:
110 case RadioNodeListType:
111 case RadioImgNodeListType:
112 case LabelsNodeListType:
115 ASSERT_NOT_REACHED();
116 return NodeListIsRootedAtNode;
119 static NodeListInvalidationType invalidationTypeExcludingIdAndNameAttributes(CollectionType type)
122 case TagCollectionType:
123 case HTMLTagCollectionType:
136 return DoNotInvalidateOnAttributeChanges;
138 case SelectedOptions:
139 case DataListOptions:
140 // FIXME: We can do better some day.
141 return InvalidateOnAnyAttrChange;
143 return InvalidateOnNameAttrChange;
145 return InvalidateOnHRefAttrChange;
146 case WindowNamedItems:
147 return InvalidateOnIdNameAttrChange;
148 case DocumentNamedItems:
149 return InvalidateOnIdNameAttrChange;
151 return InvalidateForFormControls;
152 case ClassCollectionType:
153 return InvalidateOnClassAttrChange;
154 case NameNodeListType:
155 case RadioNodeListType:
156 case RadioImgNodeListType:
157 case LabelsNodeListType:
160 ASSERT_NOT_REACHED();
161 return DoNotInvalidateOnAttributeChanges;
164 HTMLCollection::HTMLCollection(ContainerNode& ownerNode, CollectionType type, ItemAfterOverrideType itemAfterOverrideType)
165 : LiveNodeListBase(ownerNode, rootTypeFromCollectionType(type), invalidationTypeExcludingIdAndNameAttributes(type), type)
166 , m_overridesItemAfter(itemAfterOverrideType == OverridesItemAfter)
167 , m_shouldOnlyIncludeDirectChildren(shouldTypeOnlyIncludeDirectChildren(type))
169 ScriptWrappable::init(this);
172 PassRefPtrWillBeRawPtr<HTMLCollection> HTMLCollection::create(ContainerNode& base, CollectionType type)
174 return adoptRefWillBeNoop(new HTMLCollection(base, type, DoesNotOverrideItemAfter));
177 HTMLCollection::~HTMLCollection()
180 if (hasValidIdNameCache())
181 unregisterIdNameCacheFromDocument(document());
182 // Named HTMLCollection types remove cache by themselves.
183 if (isUnnamedHTMLCollectionType(type()))
184 ownerNode().nodeLists()->removeCache(this, type());
188 void HTMLCollection::invalidateCache(Document* oldDocument) const
190 m_collectionIndexCache.invalidate();
191 invalidateIdNameCacheMaps(oldDocument);
194 unsigned HTMLCollection::length() const
196 return m_collectionIndexCache.nodeCount(*this);
199 Element* HTMLCollection::item(unsigned offset) const
201 return m_collectionIndexCache.nodeAt(*this, offset);
204 static inline bool isMatchingHTMLElement(const HTMLCollection& htmlCollection, const HTMLElement& element)
206 switch (htmlCollection.type()) {
208 return element.hasTagName(imgTag);
210 return element.hasTagName(scriptTag);
212 return element.hasTagName(formTag);
214 return element.hasTagName(tbodyTag);
216 return element.hasTagName(tdTag) || element.hasTagName(thTag);
218 return element.hasTagName(trTag);
220 return element.hasTagName(optionTag);
221 case SelectedOptions:
222 return isHTMLOptionElement(element) && toHTMLOptionElement(element).selected();
223 case DataListOptions:
224 if (isHTMLOptionElement(element)) {
225 const HTMLOptionElement& option = toHTMLOptionElement(element);
226 if (!option.isDisabledFormControl() && !option.value().isEmpty())
231 return element.hasTagName(areaTag);
233 return element.hasTagName(appletTag) || (isHTMLObjectElement(element) && toHTMLObjectElement(element).containsJavaApplet());
235 return element.hasTagName(embedTag);
237 return (element.hasTagName(aTag) || element.hasTagName(areaTag)) && element.fastHasAttribute(hrefAttr);
239 return element.hasTagName(aTag) && element.fastHasAttribute(nameAttr);
240 case ClassCollectionType:
241 case TagCollectionType:
242 case HTMLTagCollectionType:
246 case DocumentNamedItems:
248 case WindowNamedItems:
249 case NameNodeListType:
250 case RadioNodeListType:
251 case RadioImgNodeListType:
252 case LabelsNodeListType:
253 ASSERT_NOT_REACHED();
258 inline bool HTMLCollection::elementMatches(const Element& element) const
260 // These collections apply to any kind of Elements, not just HTMLElements.
265 case ClassCollectionType:
266 return toClassCollection(*this).elementMatches(element);
267 case TagCollectionType:
268 return toTagCollection(*this).elementMatches(element);
269 case HTMLTagCollectionType:
270 return toHTMLTagCollection(*this).elementMatches(element);
271 case DocumentNamedItems:
272 return toDocumentNameCollection(*this).elementMatches(element);
273 case WindowNamedItems:
274 return toWindowNameCollection(*this).elementMatches(element);
279 // The following only applies to HTMLElements.
280 return element.isHTMLElement() && isMatchingHTMLElement(*this, toHTMLElement(element));
285 template <class HTMLCollectionType>
288 IsMatch(const HTMLCollectionType& list)
292 bool operator() (const Element& element) const
294 return m_list.elementMatches(element);
298 const HTMLCollectionType& m_list;
303 template <class HTMLCollectionType>
304 static inline IsMatch<HTMLCollectionType> makeIsMatch(const HTMLCollectionType& list) { return IsMatch<HTMLCollectionType>(list); }
306 Element* HTMLCollection::virtualItemAfter(Element*) const
308 ASSERT_NOT_REACHED();
312 static inline bool nameShouldBeVisibleInDocumentAll(const HTMLElement& element)
314 // http://www.whatwg.org/specs/web-apps/current-work/multipage/common-dom-interfaces.html#dom-htmlallcollection-nameditem:
315 // The document.all collection returns only certain types of elements by name,
316 // although it returns any type of element by id.
317 return element.hasTagName(aTag)
318 || element.hasTagName(appletTag)
319 || element.hasTagName(areaTag)
320 || element.hasTagName(embedTag)
321 || element.hasTagName(formTag)
322 || element.hasTagName(frameTag)
323 || element.hasTagName(framesetTag)
324 || element.hasTagName(iframeTag)
325 || element.hasTagName(imgTag)
326 || element.hasTagName(inputTag)
327 || element.hasTagName(objectTag)
328 || element.hasTagName(selectTag);
331 Element* HTMLCollection::traverseToFirstElement() const
334 case HTMLTagCollectionType:
335 return ElementTraversal::firstWithin(rootNode(), makeIsMatch(toHTMLTagCollection(*this)));
336 case ClassCollectionType:
337 return ElementTraversal::firstWithin(rootNode(), makeIsMatch(toClassCollection(*this)));
339 if (overridesItemAfter())
340 return virtualItemAfter(0);
341 if (shouldOnlyIncludeDirectChildren())
342 return ElementTraversal::firstChild(rootNode(), makeIsMatch(*this));
343 return ElementTraversal::firstWithin(rootNode(), makeIsMatch(*this));
347 Element* HTMLCollection::traverseToLastElement() const
349 ASSERT(canTraverseBackward());
350 if (shouldOnlyIncludeDirectChildren())
351 return ElementTraversal::lastChild(rootNode(), makeIsMatch(*this));
352 return ElementTraversal::lastWithin(rootNode(), makeIsMatch(*this));
355 Element* HTMLCollection::traverseForwardToOffset(unsigned offset, Element& currentElement, unsigned& currentOffset) const
357 ASSERT(currentOffset < offset);
359 case HTMLTagCollectionType:
360 return traverseMatchingElementsForwardToOffset(currentElement, &rootNode(), offset, currentOffset, makeIsMatch(toHTMLTagCollection(*this)));
361 case ClassCollectionType:
362 return traverseMatchingElementsForwardToOffset(currentElement, &rootNode(), offset, currentOffset, makeIsMatch(toClassCollection(*this)));
364 if (overridesItemAfter()) {
365 for (Element* next = virtualItemAfter(¤tElement); next; next = virtualItemAfter(next)) {
366 if (++currentOffset == offset)
371 if (shouldOnlyIncludeDirectChildren()) {
372 IsMatch<HTMLCollection> isMatch(*this);
373 for (Element* next = ElementTraversal::nextSibling(currentElement, isMatch); next; next = ElementTraversal::nextSibling(*next, isMatch)) {
374 if (++currentOffset == offset)
379 return traverseMatchingElementsForwardToOffset(currentElement, &rootNode(), offset, currentOffset, makeIsMatch(*this));
383 Element* HTMLCollection::traverseBackwardToOffset(unsigned offset, Element& currentElement, unsigned& currentOffset) const
385 ASSERT(currentOffset > offset);
386 ASSERT(canTraverseBackward());
387 if (shouldOnlyIncludeDirectChildren()) {
388 IsMatch<HTMLCollection> isMatch(*this);
389 for (Element* previous = ElementTraversal::previousSibling(currentElement, isMatch); previous; previous = ElementTraversal::previousSibling(*previous, isMatch)) {
390 if (--currentOffset == offset)
395 return traverseMatchingElementsBackwardToOffset(currentElement, &rootNode(), offset, currentOffset, makeIsMatch(*this));
398 Element* HTMLCollection::namedItem(const AtomicString& name) const
400 // http://msdn.microsoft.com/workshop/author/dhtml/reference/methods/nameditem.asp
401 // This method first searches for an object with a matching id
402 // attribute. If a match is not found, the method then searches for an
403 // object with a matching name attribute, but only on those elements
404 // that are allowed a name attribute.
407 const NamedItemCache& cache = namedItemCache();
408 WillBeHeapVector<RawPtrWillBeMember<Element> >* idResults = cache.getElementsById(name);
409 if (idResults && !idResults->isEmpty())
410 return idResults->first();
412 WillBeHeapVector<RawPtrWillBeMember<Element> >* nameResults = cache.getElementsByName(name);
413 if (nameResults && !nameResults->isEmpty())
414 return nameResults->first();
419 bool HTMLCollection::namedPropertyQuery(const AtomicString& name, ExceptionState&)
421 return namedItem(name);
424 void HTMLCollection::supportedPropertyNames(Vector<String>& names)
426 // As per the specification (http://dom.spec.whatwg.org/#htmlcollection):
427 // The supported property names are the values from the list returned by these steps:
428 // 1. Let result be an empty list.
429 // 2. For each element represented by the collection, in tree order, run these substeps:
430 // 1. If element has an ID which is neither the empty string nor is in result, append element's ID to result.
431 // 2. If element is in the HTML namespace and has a name attribute whose value is neither the empty string
432 // nor is in result, append element's name attribute value to result.
434 HashSet<AtomicString> existingNames;
435 unsigned length = this->length();
436 for (unsigned i = 0; i < length; ++i) {
437 Element* element = item(i);
438 const AtomicString& idAttribute = element->getIdAttribute();
439 if (!idAttribute.isEmpty()) {
440 HashSet<AtomicString>::AddResult addResult = existingNames.add(idAttribute);
441 if (addResult.isNewEntry)
442 names.append(idAttribute);
444 if (!element->isHTMLElement())
446 const AtomicString& nameAttribute = element->getNameAttribute();
447 if (!nameAttribute.isEmpty() && (type() != DocAll || nameShouldBeVisibleInDocumentAll(toHTMLElement(*element)))) {
448 HashSet<AtomicString>::AddResult addResult = existingNames.add(nameAttribute);
449 if (addResult.isNewEntry)
450 names.append(nameAttribute);
455 void HTMLCollection::namedPropertyEnumerator(Vector<String>& names, ExceptionState&)
457 supportedPropertyNames(names);
460 void HTMLCollection::updateIdNameCache() const
462 if (hasValidIdNameCache())
465 OwnPtrWillBeRawPtr<NamedItemCache> cache = NamedItemCache::create();
466 unsigned length = this->length();
467 for (unsigned i = 0; i < length; ++i) {
468 Element* element = item(i);
469 const AtomicString& idAttrVal = element->getIdAttribute();
470 if (!idAttrVal.isEmpty())
471 cache->addElementWithId(idAttrVal, element);
472 if (!element->isHTMLElement())
474 const AtomicString& nameAttrVal = element->getNameAttribute();
475 if (!nameAttrVal.isEmpty() && idAttrVal != nameAttrVal && (type() != DocAll || nameShouldBeVisibleInDocumentAll(toHTMLElement(*element))))
476 cache->addElementWithName(nameAttrVal, element);
478 // Set the named item cache last as traversing the tree may cause cache invalidation.
479 setNamedItemCache(cache.release());
482 void HTMLCollection::namedItems(const AtomicString& name, WillBeHeapVector<RefPtrWillBeMember<Element> >& result) const
484 ASSERT(result.isEmpty());
490 const NamedItemCache& cache = namedItemCache();
491 if (WillBeHeapVector<RawPtrWillBeMember<Element> >* idResults = cache.getElementsById(name)) {
492 for (unsigned i = 0; i < idResults->size(); ++i)
493 result.append(idResults->at(i));
495 if (WillBeHeapVector<RawPtrWillBeMember<Element> >* nameResults = cache.getElementsByName(name)) {
496 for (unsigned i = 0; i < nameResults->size(); ++i)
497 result.append(nameResults->at(i));
501 HTMLCollection::NamedItemCache::NamedItemCache()
505 void HTMLCollection::trace(Visitor* visitor)
507 visitor->trace(m_namedItemCache);
508 visitor->trace(m_collectionIndexCache);
509 LiveNodeListBase::trace(visitor);