RadioNodeList support in HTMLFormElement::elements
[WebKit-https.git] / Source / WebCore / html / HTMLCollection.cpp
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 "HTMLCollection.h"
25
26 #include "HTMLDocument.h"
27 #include "HTMLElement.h"
28 #include "HTMLNames.h"
29 #include "HTMLObjectElement.h"
30 #include "HTMLOptionElement.h"
31 #include "NodeList.h"
32
33 #include <utility>
34
35 namespace WebCore {
36
37 using namespace HTMLNames;
38
39 HTMLCollection::HTMLCollection(Node* base, CollectionType type)
40     : m_includeChildren(shouldIncludeChildren(type))
41     , m_type(type)
42     , m_base(base)
43 {
44     ASSERT(m_base);
45     m_cache.clear();
46 }
47
48 bool HTMLCollection::shouldIncludeChildren(CollectionType type)
49 {
50     switch (type) {
51     case DocAll:
52     case DocAnchors:
53     case DocApplets:
54     case DocEmbeds:
55     case DocForms:
56     case DocImages:
57     case DocLinks:
58     case DocObjects:
59     case DocScripts:
60     case DocumentNamedItems:
61     case MapAreas:
62     case OtherCollection:
63     case SelectOptions:
64     case SelectedOptions:
65     case DataListOptions:
66     case WindowNamedItems:
67 #if ENABLE(MICRODATA)
68     case ItemProperties:
69 #endif
70     case FormControls:
71         return true;
72     case NodeChildren:
73     case TRCells:
74     case TSectionRows:
75     case TableTBodies:
76         return false;
77     }
78     ASSERT_NOT_REACHED();
79     return false;
80 }
81
82 PassOwnPtr<HTMLCollection> HTMLCollection::create(Node* base, CollectionType type)
83 {
84     return adoptPtr(new HTMLCollection(base, type));
85 }
86
87 HTMLCollection::~HTMLCollection()
88 {
89 }
90
91 void HTMLCollection::invalidateCacheIfNeeded() const
92 {
93     uint64_t docversion = static_cast<HTMLDocument*>(m_base->document())->domTreeVersion();
94
95     if (m_cache.version == docversion)
96         return;
97
98     m_cache.clear();
99     m_cache.version = docversion;
100 }
101
102 inline bool HTMLCollection::isAcceptableElement(Element* element) const
103 {
104     if (!element->isHTMLElement() && !(m_type == DocAll || m_type == NodeChildren))
105         return false;
106
107     switch (m_type) {
108     case DocImages:
109         return element->hasLocalName(imgTag);
110     case DocScripts:
111         return element->hasLocalName(scriptTag);
112     case DocForms:
113         return element->hasLocalName(formTag);
114     case TableTBodies:
115         return element->hasLocalName(tbodyTag);
116     case TRCells:
117         return element->hasLocalName(tdTag) || element->hasLocalName(thTag);
118     case TSectionRows:
119         return element->hasLocalName(trTag);
120     case SelectOptions:
121         return element->hasLocalName(optionTag);
122     case SelectedOptions:
123         if (element->hasLocalName(optionTag)) {
124             HTMLOptionElement* option = static_cast<HTMLOptionElement*>(element);
125             if (option->selected())
126                 return true;
127         }
128         return false;
129     case DataListOptions:
130         if (element->hasLocalName(optionTag)) {
131             HTMLOptionElement* option = static_cast<HTMLOptionElement*>(element);
132             if (!option->disabled() && !option->value().isEmpty())
133                 return true;
134         }
135         return false;
136     case MapAreas:
137         return element->hasLocalName(areaTag);
138     case DocApplets:
139         return element->hasLocalName(appletTag) || (element->hasLocalName(objectTag) && static_cast<HTMLObjectElement*>(element)->containsJavaApplet());
140     case DocEmbeds:
141         return element->hasLocalName(embedTag);
142     case DocObjects:
143         return element->hasLocalName(objectTag);
144     case DocLinks:
145         return (element->hasLocalName(aTag) || element->hasLocalName(areaTag)) && element->fastHasAttribute(hrefAttr);
146     case DocAnchors:
147         return element->hasLocalName(aTag) && element->fastHasAttribute(nameAttr);
148     case DocAll:
149     case NodeChildren:
150         return true;
151 #if ENABLE(MICRODATA)
152     case ItemProperties:
153         return element->fastHasAttribute(itempropAttr);
154 #endif
155     case FormControls:
156     case DocumentNamedItems:
157     case OtherCollection:
158     case WindowNamedItems:
159         ASSERT_NOT_REACHED();
160     }
161     return false;
162 }
163
164 static Node* nextNodeOrSibling(Node* base, Node* node, bool includeChildren)
165 {
166     return includeChildren ? node->traverseNextNode(base) : node->traverseNextSibling(base);
167 }
168
169 Element* HTMLCollection::itemAfter(Element* previous) const
170 {
171     Node* current;
172     if (!previous)
173         current = m_base->firstChild();
174     else
175         current = nextNodeOrSibling(m_base, previous, m_includeChildren);
176
177     for (; current; current = nextNodeOrSibling(m_base, current, m_includeChildren)) {
178         if (!current->isElementNode())
179             continue;
180         Element* element = static_cast<Element*>(current);
181         if (isAcceptableElement(element))
182             return element;
183     }
184
185     return 0;
186 }
187
188 unsigned HTMLCollection::calcLength() const
189 {
190     unsigned len = 0;
191     for (Element* current = itemAfter(0); current; current = itemAfter(current))
192         ++len;
193     return len;
194 }
195
196 // since the collections are to be "live", we have to do the
197 // calculation every time if anything has changed
198 unsigned HTMLCollection::length() const
199 {
200     invalidateCacheIfNeeded();
201     if (!m_cache.hasLength) {
202         m_cache.length = calcLength();
203         m_cache.hasLength = true;
204     }
205     return m_cache.length;
206 }
207
208 Node* HTMLCollection::item(unsigned index) const
209 {
210      invalidateCacheIfNeeded();
211      if (m_cache.current && m_cache.position == index)
212          return m_cache.current;
213      if (m_cache.hasLength && m_cache.length <= index)
214          return 0;
215      if (!m_cache.current || m_cache.position > index) {
216          m_cache.current = itemAfter(0);
217          m_cache.position = 0;
218          if (!m_cache.current)
219              return 0;
220      }
221      Element* e = m_cache.current;
222      for (unsigned pos = m_cache.position; e && pos < index; pos++)
223          e = itemAfter(e);
224      m_cache.current = e;
225      m_cache.position = index;
226      return m_cache.current;
227 }
228
229 Node* HTMLCollection::firstItem() const
230 {
231      return item(0);
232 }
233
234 Node* HTMLCollection::nextItem() const
235 {
236      invalidateCacheIfNeeded();
237
238      // Look for the 'second' item. The first one is currentItem, already given back.
239      Element* retval = itemAfter(m_cache.current);
240      m_cache.current = retval;
241      m_cache.position++;
242      return retval;
243 }
244
245 static inline bool nameShouldBeVisibleInDocumentAll(HTMLElement* element)
246 {
247     // The document.all collection returns only certain types of elements by name,
248     // although it returns any type of element by id.
249     return element->hasLocalName(appletTag)
250         || element->hasLocalName(embedTag)
251         || element->hasLocalName(formTag)
252         || element->hasLocalName(imgTag)
253         || element->hasLocalName(inputTag)
254         || element->hasLocalName(objectTag)
255         || element->hasLocalName(selectTag);
256 }
257
258 bool HTMLCollection::checkForNameMatch(Element* element, bool checkName, const AtomicString& name) const
259 {
260     if (!element->isHTMLElement())
261         return false;
262     
263     HTMLElement* e = toHTMLElement(element);
264     if (!checkName)
265         return e->getIdAttribute() == name;
266
267     if (m_type == DocAll && !nameShouldBeVisibleInDocumentAll(e))
268         return false;
269
270     return e->getNameAttribute() == name && e->getIdAttribute() != name;
271 }
272
273 Node* HTMLCollection::namedItem(const AtomicString& name) const
274 {
275     // http://msdn.microsoft.com/workshop/author/dhtml/reference/methods/nameditem.asp
276     // This method first searches for an object with a matching id
277     // attribute. If a match is not found, the method then searches for an
278     // object with a matching name attribute, but only on those elements
279     // that are allowed a name attribute.
280     invalidateCacheIfNeeded();
281
282     for (Element* e = itemAfter(0); e; e = itemAfter(e)) {
283         if (checkForNameMatch(e, /* checkName */ false, name)) {
284             m_cache.current = e;
285             return e;
286         }
287     }
288
289     for (Element* e = itemAfter(0); e; e = itemAfter(e)) {
290         if (checkForNameMatch(e, /* checkName */ true, name)) {
291             m_cache.current = e;
292             return e;
293         }
294     }
295
296     m_cache.current = 0;
297     return 0;
298 }
299
300 void HTMLCollection::updateNameCache() const
301 {
302     if (m_cache.hasNameCache)
303         return;
304
305     for (Element* element = itemAfter(0); element; element = itemAfter(element)) {
306         if (!element->isHTMLElement())
307             continue;
308         HTMLElement* e = toHTMLElement(element);
309         const AtomicString& idAttrVal = e->getIdAttribute();
310         const AtomicString& nameAttrVal = e->getNameAttribute();
311         if (!idAttrVal.isEmpty())
312             append(m_cache.idCache, idAttrVal, e);
313         if (!nameAttrVal.isEmpty() && idAttrVal != nameAttrVal && (m_type != DocAll || nameShouldBeVisibleInDocumentAll(e)))
314             append(m_cache.nameCache, nameAttrVal, e);
315     }
316
317     m_cache.hasNameCache = true;
318 }
319
320 bool HTMLCollection::hasNamedItem(const AtomicString& name) const
321 {
322     if (name.isEmpty())
323         return false;
324
325     invalidateCacheIfNeeded();
326     updateNameCache();
327
328     if (Vector<Element*>* idCache = m_cache.idCache.get(name.impl())) {
329         if (!idCache->isEmpty())
330             return true;
331     }
332
333     if (Vector<Element*>* nameCache = m_cache.nameCache.get(name.impl())) {
334         if (!nameCache->isEmpty())
335             return true;
336     }
337
338     return false;
339 }
340
341 void HTMLCollection::namedItems(const AtomicString& name, Vector<RefPtr<Node> >& result) const
342 {
343     ASSERT(result.isEmpty());
344     if (name.isEmpty())
345         return;
346
347     invalidateCacheIfNeeded();
348     updateNameCache();
349
350     Vector<Element*>* idResults = m_cache.idCache.get(name.impl());
351     Vector<Element*>* nameResults = m_cache.nameCache.get(name.impl());
352
353     for (unsigned i = 0; idResults && i < idResults->size(); ++i)
354         result.append(idResults->at(i));
355
356     for (unsigned i = 0; nameResults && i < nameResults->size(); ++i)
357         result.append(nameResults->at(i));
358 }
359
360 PassRefPtr<NodeList> HTMLCollection::tags(const String& name)
361 {
362     return m_base->getElementsByTagName(name);
363 }
364
365 void HTMLCollection::append(NodeCacheMap& map, const AtomicString& key, Element* element)
366 {
367     OwnPtr<Vector<Element*> >& vector = map.add(key.impl(), nullptr).iterator->second;
368     if (!vector)
369         vector = adoptPtr(new Vector<Element*>);
370     vector->append(element);
371 }
372
373 } // namespace WebCore