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