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.
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 "HTMLNames.h"
28 #include "core/dom/ClassCollection.h"
29 #include "core/dom/ElementTraversal.h"
30 #include "core/dom/NodeRareData.h"
31 #include "core/html/HTMLElement.h"
32 #include "core/html/HTMLObjectElement.h"
33 #include "core/html/HTMLOptionElement.h"
34 #include "wtf/HashSet.h"
38 using namespace HTMLNames;
40 static bool shouldTypeOnlyIncludeDirectChildren(CollectionType type)
43 case ClassCollectionType:
44 case TagCollectionType:
45 case HTMLTagCollectionType:
54 case DocumentNamedItems:
60 case WindowNamedItems:
68 case NameNodeListType:
69 case RadioNodeListType:
70 case RadioImgNodeListType:
71 case LabelsNodeListType:
78 static NodeListRootType rootTypeFromCollectionType(CollectionType type)
89 case WindowNamedItems:
90 case DocumentNamedItems:
92 return NodeListIsRootedAtDocument;
93 case ClassCollectionType:
94 case TagCollectionType:
95 case HTMLTagCollectionType:
102 case SelectedOptions:
103 case DataListOptions:
105 return NodeListIsRootedAtNode;
106 case NameNodeListType:
107 case RadioNodeListType:
108 case RadioImgNodeListType:
109 case LabelsNodeListType:
112 ASSERT_NOT_REACHED();
113 return NodeListIsRootedAtNode;
116 static NodeListInvalidationType invalidationTypeExcludingIdAndNameAttributes(CollectionType type)
119 case TagCollectionType:
120 case HTMLTagCollectionType:
133 return DoNotInvalidateOnAttributeChanges;
135 case SelectedOptions:
136 case DataListOptions:
137 // FIXME: We can do better some day.
138 return InvalidateOnAnyAttrChange;
140 return InvalidateOnNameAttrChange;
142 return InvalidateOnHRefAttrChange;
143 case WindowNamedItems:
144 return InvalidateOnIdNameAttrChange;
145 case DocumentNamedItems:
146 return InvalidateOnIdNameAttrChange;
148 return InvalidateForFormControls;
149 case ClassCollectionType:
150 return InvalidateOnClassAttrChange;
151 case NameNodeListType:
152 case RadioNodeListType:
153 case RadioImgNodeListType:
154 case LabelsNodeListType:
157 ASSERT_NOT_REACHED();
158 return DoNotInvalidateOnAttributeChanges;
161 HTMLCollection::HTMLCollection(ContainerNode& ownerNode, CollectionType type, ItemAfterOverrideType itemAfterOverrideType)
162 : LiveNodeListBase(ownerNode, rootTypeFromCollectionType(type), invalidationTypeExcludingIdAndNameAttributes(type), type)
163 , m_overridesItemAfter(itemAfterOverrideType == OverridesItemAfter)
164 , m_shouldOnlyIncludeDirectChildren(shouldTypeOnlyIncludeDirectChildren(type))
165 , m_hasValidIdNameCache(false)
167 ScriptWrappable::init(this);
170 PassRefPtr<HTMLCollection> HTMLCollection::create(ContainerNode& base, CollectionType type)
172 return adoptRef(new HTMLCollection(base, type, DoesNotOverrideItemAfter));
175 HTMLCollection::~HTMLCollection()
177 if (hasValidIdNameCache())
178 unregisterIdNameCacheFromDocument(document());
179 // HTMLNameCollection, ClassCollection and TagCollection remove cache by themselves.
180 if (type() != WindowNamedItems && type() != DocumentNamedItems && type() != ClassCollectionType
181 && type() != HTMLTagCollectionType && type() != TagCollectionType) {
182 ownerNode().nodeLists()->removeCache(this, type());
186 void HTMLCollection::invalidateCache(Document* oldDocument) const
188 m_collectionIndexCache.invalidate();
189 invalidateIdNameCacheMaps(oldDocument);
192 template <class NodeListType>
193 inline bool isMatchingElement(const NodeListType&, const Element&);
195 template <> inline bool isMatchingElement(const HTMLCollection& htmlCollection, const Element& element)
197 CollectionType type = htmlCollection.type();
199 // These collections apply to any kind of Elements, not just HTMLElements.
204 case ClassCollectionType:
205 return static_cast<const ClassCollection&>(htmlCollection).elementMatches(element);
206 case TagCollectionType:
207 return static_cast<const TagCollection&>(htmlCollection).elementMatches(element);
208 case HTMLTagCollectionType:
209 return static_cast<const HTMLTagCollection&>(htmlCollection).elementMatches(element);
214 // The following only applies to HTMLElements.
215 if (!element.isHTMLElement())
220 return element.hasLocalName(imgTag);
222 return element.hasLocalName(scriptTag);
224 return element.hasLocalName(formTag);
226 return element.hasLocalName(tbodyTag);
228 return element.hasLocalName(tdTag) || element.hasLocalName(thTag);
230 return element.hasLocalName(trTag);
232 return element.hasLocalName(optionTag);
233 case SelectedOptions:
234 return element.hasLocalName(optionTag) && toHTMLOptionElement(element).selected();
235 case DataListOptions:
236 if (element.hasLocalName(optionTag)) {
237 const HTMLOptionElement& option = toHTMLOptionElement(element);
238 if (!option.isDisabledFormControl() && !option.value().isEmpty())
243 return element.hasLocalName(areaTag);
245 return element.hasLocalName(appletTag) || (element.hasLocalName(objectTag) && toHTMLObjectElement(element).containsJavaApplet());
247 return element.hasLocalName(embedTag);
249 return (element.hasLocalName(aTag) || element.hasLocalName(areaTag)) && element.fastHasAttribute(hrefAttr);
251 return element.hasLocalName(aTag) && element.fastHasAttribute(nameAttr);
252 case ClassCollectionType:
253 case TagCollectionType:
254 case HTMLTagCollectionType:
258 case DocumentNamedItems:
260 case WindowNamedItems:
261 case NameNodeListType:
262 case RadioNodeListType:
263 case RadioImgNodeListType:
264 case LabelsNodeListType:
265 ASSERT_NOT_REACHED();
270 template <> inline bool isMatchingElement(const ClassCollection& collection, const Element& element)
272 return collection.elementMatches(element);
275 template <> inline bool isMatchingElement(const HTMLTagCollection& collection, const Element& element)
277 return collection.elementMatches(element);
280 Element* HTMLCollection::itemBefore(const Element* previous) const
282 return LiveNodeListBase::itemBefore(*this, previous);
285 Element* HTMLCollection::virtualItemAfter(Element*) const
287 ASSERT_NOT_REACHED();
291 static inline bool nameShouldBeVisibleInDocumentAll(const HTMLElement& element)
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);
310 inline Element* firstMatchingChildElement(const HTMLCollection& nodeList, const ContainerNode& root)
312 Element* element = ElementTraversal::firstChild(root);
313 while (element && !isMatchingElement(nodeList, *element))
314 element = ElementTraversal::nextSibling(*element);
318 inline Element* nextMatchingChildElement(const HTMLCollection& nodeList, Element& current)
320 Element* next = ¤t;
322 next = ElementTraversal::nextSibling(*next);
323 } while (next && !isMatchingElement(nodeList, *next));
327 Element* HTMLCollection::traverseToFirstElement(const ContainerNode& root) const
330 case HTMLTagCollectionType:
331 return firstMatchingElement(static_cast<const HTMLTagCollection&>(*this), root);
332 case ClassCollectionType:
333 return firstMatchingElement(static_cast<const ClassCollection&>(*this), root);
335 if (overridesItemAfter())
336 return virtualItemAfter(0);
337 if (shouldOnlyIncludeDirectChildren())
338 return firstMatchingChildElement(*this, root);
339 return firstMatchingElement(*this, root);
343 inline Element* HTMLCollection::traverseNextElement(Element& previous, const ContainerNode& root) const
345 if (overridesItemAfter())
346 return virtualItemAfter(&previous);
347 if (shouldOnlyIncludeDirectChildren())
348 return nextMatchingChildElement(*this, previous);
349 return nextMatchingElement(*this, previous, root);
352 Element* HTMLCollection::traverseForwardToOffset(unsigned offset, Element& currentElement, unsigned& currentOffset, const ContainerNode& root) const
354 ASSERT(currentOffset < offset);
356 case HTMLTagCollectionType:
357 return traverseMatchingElementsForwardToOffset(static_cast<const HTMLTagCollection&>(*this), offset, currentElement, currentOffset, root);
358 case ClassCollectionType:
359 return traverseMatchingElementsForwardToOffset(static_cast<const ClassCollection&>(*this), offset, currentElement, currentOffset, root);
361 if (overridesItemAfter()) {
362 Element* next = ¤tElement;
363 while ((next = virtualItemAfter(next))) {
364 if (++currentOffset == offset)
369 if (shouldOnlyIncludeDirectChildren()) {
370 Element* next = ¤tElement;
371 while ((next = nextMatchingChildElement(*this, *next))) {
372 if (++currentOffset == offset)
377 return traverseMatchingElementsForwardToOffset(*this, offset, currentElement, currentOffset, root);
381 Element* HTMLCollection::namedItem(const AtomicString& name) const
383 // http://msdn.microsoft.com/workshop/author/dhtml/reference/methods/nameditem.asp
384 // This method first searches for an object with a matching id
385 // attribute. If a match is not found, the method then searches for an
386 // object with a matching name attribute, but only on those elements
387 // that are allowed a name attribute.
390 Vector<Element*>* idResults = idCache(name);
391 if (idResults && !idResults->isEmpty())
392 return idResults->first();
394 Vector<Element*>* nameResults = nameCache(name);
395 if (nameResults && !nameResults->isEmpty())
396 return nameResults->first();
401 bool HTMLCollection::namedPropertyQuery(const AtomicString& name, ExceptionState&)
403 return namedItem(name);
406 void HTMLCollection::supportedPropertyNames(Vector<String>& names)
408 // As per the specification (http://dom.spec.whatwg.org/#htmlcollection):
409 // The supported property names are the values from the list returned by these steps:
410 // 1. Let result be an empty list.
411 // 2. For each element represented by the collection, in tree order, run these substeps:
412 // 1. If element has an ID which is neither the empty string nor is in result, append element's ID to result.
413 // 2. If element is in the HTML namespace and has a name attribute whose value is neither the empty string
414 // nor is in result, append element's name attribute value to result.
416 HashSet<AtomicString> existingNames;
417 ContainerNode& root = rootNode();
418 for (Element* element = traverseToFirstElement(root); element; element = traverseNextElement(*element, root)) {
419 const AtomicString& idAttribute = element->getIdAttribute();
420 if (!idAttribute.isEmpty()) {
421 HashSet<AtomicString>::AddResult addResult = existingNames.add(idAttribute);
422 if (addResult.isNewEntry)
423 names.append(idAttribute);
425 if (!element->isHTMLElement())
427 const AtomicString& nameAttribute = element->getNameAttribute();
428 if (!nameAttribute.isEmpty() && (type() != DocAll || nameShouldBeVisibleInDocumentAll(toHTMLElement(*element)))) {
429 HashSet<AtomicString>::AddResult addResult = existingNames.add(nameAttribute);
430 if (addResult.isNewEntry)
431 names.append(nameAttribute);
436 void HTMLCollection::namedPropertyEnumerator(Vector<String>& names, ExceptionState&)
438 supportedPropertyNames(names);
441 void HTMLCollection::updateIdNameCache() const
443 if (hasValidIdNameCache())
446 ContainerNode& root = rootNode();
447 for (Element* element = traverseToFirstElement(root); element; element = traverseNextElement(*element, root)) {
448 const AtomicString& idAttrVal = element->getIdAttribute();
449 if (!idAttrVal.isEmpty())
450 appendIdCache(idAttrVal, element);
451 if (!element->isHTMLElement())
453 const AtomicString& nameAttrVal = element->getNameAttribute();
454 if (!nameAttrVal.isEmpty() && idAttrVal != nameAttrVal && (type() != DocAll || nameShouldBeVisibleInDocumentAll(toHTMLElement(*element))))
455 appendNameCache(nameAttrVal, element);
458 setHasValidIdNameCache();
461 void HTMLCollection::namedItems(const AtomicString& name, Vector<RefPtr<Element> >& result) const
463 ASSERT(result.isEmpty());
469 Vector<Element*>* idResults = idCache(name);
470 Vector<Element*>* nameResults = nameCache(name);
472 for (unsigned i = 0; idResults && i < idResults->size(); ++i)
473 result.append(idResults->at(i));
475 for (unsigned i = 0; nameResults && i < nameResults->size(); ++i)
476 result.append(nameResults->at(i));
479 void HTMLCollection::append(NodeCacheMap& map, const AtomicString& key, Element* element)
481 OwnPtr<Vector<Element*> >& vector = map.add(key.impl(), nullptr).storedValue->value;
483 vector = adoptPtr(new Vector<Element*>);
484 vector->append(element);
487 } // namespace WebCore