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