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 = ¤t;
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 = ¤t;
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 = ¤tElement;
379 while ((next = virtualItemAfter(next))) {
380 if (++currentOffset == offset)
381 return next;
382 }
383 return 0;
384 }
385 if (shouldOnlyIncludeDirectChildren()) {
386 Element* next = ¤tElement;
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 = ¤tElement;
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