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