• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 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 "NodeList.h"
31 
32 #include <utility>
33 
34 namespace WebCore {
35 
36 using namespace HTMLNames;
37 
HTMLCollection(PassRefPtr<Node> base,CollectionType type)38 HTMLCollection::HTMLCollection(PassRefPtr<Node> base, CollectionType type)
39     : m_idsDone(false)
40     , m_base(base)
41     , m_type(type)
42     , m_info(m_base->isDocumentNode() ? static_cast<Document*>(m_base.get())->collectionInfo(type) : 0)
43     , m_ownsInfo(false)
44 {
45 }
46 
HTMLCollection(PassRefPtr<Node> base,CollectionType type,CollectionCache * info)47 HTMLCollection::HTMLCollection(PassRefPtr<Node> base, CollectionType type, CollectionCache* info)
48     : m_idsDone(false)
49     , m_base(base)
50     , m_type(type)
51     , m_info(info)
52     , m_ownsInfo(false)
53 {
54 }
55 
create(PassRefPtr<Node> base,CollectionType type)56 PassRefPtr<HTMLCollection> HTMLCollection::create(PassRefPtr<Node> base, CollectionType type)
57 {
58     return adoptRef(new HTMLCollection(base, type));
59 }
60 
~HTMLCollection()61 HTMLCollection::~HTMLCollection()
62 {
63     if (m_ownsInfo)
64         delete m_info;
65 }
66 
resetCollectionInfo() const67 void HTMLCollection::resetCollectionInfo() const
68 {
69     unsigned docversion = static_cast<HTMLDocument*>(m_base->document())->domTreeVersion();
70 
71     if (!m_info) {
72         m_info = new CollectionCache;
73         m_ownsInfo = true;
74         m_info->version = docversion;
75         return;
76     }
77 
78     if (m_info->version != docversion) {
79         m_info->reset();
80         m_info->version = docversion;
81     }
82 }
83 
nextNodeOrSibling(Node * base,Node * node,bool includeChildren)84 static Node* nextNodeOrSibling(Node* base, Node* node, bool includeChildren)
85 {
86     return includeChildren ? node->traverseNextNode(base) : node->traverseNextSibling(base);
87 }
88 
itemAfter(Element * previous) const89 Element* HTMLCollection::itemAfter(Element* previous) const
90 {
91     bool deep = true;
92 
93     switch (m_type) {
94         case DocAll:
95         case DocAnchors:
96         case DocApplets:
97         case DocEmbeds:
98         case DocForms:
99         case DocImages:
100         case DocLinks:
101         case DocObjects:
102         case DocScripts:
103         case DocumentNamedItems:
104         case MapAreas:
105         case OtherCollection:
106         case SelectOptions:
107         case WindowNamedItems:
108             break;
109         case NodeChildren:
110         case TRCells:
111         case TSectionRows:
112         case TableTBodies:
113             deep = false;
114             break;
115     }
116 
117     Node* current;
118     if (!previous)
119         current = m_base->firstChild();
120     else
121         current = nextNodeOrSibling(m_base.get(), previous, deep);
122 
123     for (; current; current = nextNodeOrSibling(m_base.get(), current, deep)) {
124         if (!current->isElementNode())
125             continue;
126         Element* e = static_cast<Element*>(current);
127         switch (m_type) {
128             case DocImages:
129                 if (e->hasLocalName(imgTag))
130                     return e;
131                 break;
132             case DocScripts:
133                 if (e->hasLocalName(scriptTag))
134                     return e;
135                 break;
136             case DocForms:
137                 if (e->hasLocalName(formTag))
138                     return e;
139                 break;
140             case TableTBodies:
141                 if (e->hasLocalName(tbodyTag))
142                     return e;
143                 break;
144             case TRCells:
145                 if (e->hasLocalName(tdTag) || e->hasLocalName(thTag))
146                     return e;
147                 break;
148             case TSectionRows:
149                 if (e->hasLocalName(trTag))
150                     return e;
151                 break;
152             case SelectOptions:
153                 if (e->hasLocalName(optionTag))
154                     return e;
155                 break;
156             case MapAreas:
157                 if (e->hasLocalName(areaTag))
158                     return e;
159                 break;
160             case DocApplets: // all <applet> elements and <object> elements that contain Java Applets
161                 if (e->hasLocalName(appletTag))
162                     return e;
163                 if (e->hasLocalName(objectTag) && static_cast<HTMLObjectElement*>(e)->containsJavaApplet())
164                     return e;
165                 break;
166             case DocEmbeds:
167                 if (e->hasLocalName(embedTag))
168                     return e;
169                 break;
170             case DocObjects:
171                 if (e->hasLocalName(objectTag))
172                     return e;
173                 break;
174             case DocLinks: // all <a> and <area> elements with a value for href
175                 if ((e->hasLocalName(aTag) || e->hasLocalName(areaTag)) && (!e->getAttribute(hrefAttr).isNull()))
176                     return e;
177                 break;
178             case DocAnchors: // all <a> elements with a value for name
179                 if (e->hasLocalName(aTag) && !e->getAttribute(nameAttr).isNull())
180                     return e;
181                 break;
182             case DocAll:
183             case NodeChildren:
184                 return e;
185             case DocumentNamedItems:
186             case OtherCollection:
187             case WindowNamedItems:
188                 ASSERT_NOT_REACHED();
189                 break;
190         }
191     }
192 
193     return 0;
194 }
195 
calcLength() const196 unsigned HTMLCollection::calcLength() const
197 {
198     unsigned len = 0;
199     for (Element* current = itemAfter(0); current; current = itemAfter(current))
200         ++len;
201     return len;
202 }
203 
204 // since the collections are to be "live", we have to do the
205 // calculation every time if anything has changed
length() const206 unsigned HTMLCollection::length() const
207 {
208     resetCollectionInfo();
209     if (!m_info->hasLength) {
210         m_info->length = calcLength();
211         m_info->hasLength = true;
212     }
213     return m_info->length;
214 }
215 
item(unsigned index) const216 Node* HTMLCollection::item(unsigned index) const
217 {
218      resetCollectionInfo();
219      if (m_info->current && m_info->position == index)
220          return m_info->current;
221      if (m_info->hasLength && m_info->length <= index)
222          return 0;
223      if (!m_info->current || m_info->position > index) {
224          m_info->current = itemAfter(0);
225          m_info->position = 0;
226          if (!m_info->current)
227              return 0;
228      }
229      Element* e = m_info->current;
230      for (unsigned pos = m_info->position; e && pos < index; pos++)
231          e = itemAfter(e);
232      m_info->current = e;
233      m_info->position = index;
234      return m_info->current;
235 }
236 
firstItem() const237 Node* HTMLCollection::firstItem() const
238 {
239      return item(0);
240 }
241 
nextItem() const242 Node* HTMLCollection::nextItem() const
243 {
244      resetCollectionInfo();
245 
246 #ifdef ANDROID_FIX
247      // resetCollectionInfo() can set info->current to be 0. If this is the
248      // case, we need to go back to the firstItem. Otherwise traverseNextItem
249      // will crash.
250      if (!m_info->current)
251          return firstItem();
252 #endif
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 
checkForNameMatch(Element * element,bool checkName,const AtomicString & name) const261 bool HTMLCollection::checkForNameMatch(Element* element, bool checkName, const AtomicString& name) const
262 {
263     if (!element->isHTMLElement())
264         return false;
265 
266     HTMLElement* e = static_cast<HTMLElement*>(element);
267     if (!checkName)
268         return e->getAttribute(idAttr) == name;
269 
270     // document.all returns only images, forms, applets, objects and embeds
271     // by name (though everything by id)
272     if (m_type == DocAll &&
273         !(e->hasLocalName(imgTag) || e->hasLocalName(formTag) ||
274           e->hasLocalName(appletTag) || e->hasLocalName(objectTag) ||
275           e->hasLocalName(embedTag) || e->hasLocalName(inputTag) ||
276           e->hasLocalName(selectTag)))
277         return false;
278 
279     return e->getAttribute(nameAttr) == name && e->getAttribute(idAttr) != name;
280 }
281 
namedItem(const AtomicString & name) const282 Node* HTMLCollection::namedItem(const AtomicString& name) const
283 {
284     // http://msdn.microsoft.com/workshop/author/dhtml/reference/methods/nameditem.asp
285     // This method first searches for an object with a matching id
286     // attribute. If a match is not found, the method then searches for an
287     // object with a matching name attribute, but only on those elements
288     // that are allowed a name attribute.
289     resetCollectionInfo();
290     m_idsDone = false;
291 
292     for (Element* e = itemAfter(0); e; e = itemAfter(e)) {
293         if (checkForNameMatch(e, m_idsDone, name)) {
294             m_info->current = e;
295             return e;
296         }
297     }
298 
299     m_idsDone = true;
300 
301     for (Element* e = itemAfter(0); e; e = itemAfter(e)) {
302         if (checkForNameMatch(e, m_idsDone, name)) {
303             m_info->current = e;
304             return e;
305         }
306     }
307 
308     m_info->current = 0;
309     return 0;
310 }
311 
updateNameCache() const312 void HTMLCollection::updateNameCache() const
313 {
314     if (m_info->hasNameCache)
315         return;
316 
317     for (Element* element = itemAfter(0); element; element = itemAfter(element)) {
318         if (!element->isHTMLElement())
319             continue;
320         HTMLElement* e = static_cast<HTMLElement*>(element);
321         const AtomicString& idAttrVal = e->getAttribute(idAttr);
322         const AtomicString& nameAttrVal = e->getAttribute(nameAttr);
323         if (!idAttrVal.isEmpty()) {
324             // add to id cache
325             Vector<Element*>* idVector = m_info->idCache.get(idAttrVal.impl());
326             if (!idVector) {
327                 idVector = new Vector<Element*>;
328                 m_info->idCache.add(idAttrVal.impl(), idVector);
329             }
330             idVector->append(e);
331         }
332         if (!nameAttrVal.isEmpty() && idAttrVal != nameAttrVal
333             && (m_type != DocAll ||
334                 (e->hasLocalName(imgTag) || e->hasLocalName(formTag) ||
335                  e->hasLocalName(appletTag) || e->hasLocalName(objectTag) ||
336                  e->hasLocalName(embedTag) || e->hasLocalName(inputTag) ||
337                  e->hasLocalName(selectTag)))) {
338             // add to name cache
339             Vector<Element*>* nameVector = m_info->nameCache.get(nameAttrVal.impl());
340             if (!nameVector) {
341                 nameVector = new Vector<Element*>;
342                 m_info->nameCache.add(nameAttrVal.impl(), nameVector);
343             }
344             nameVector->append(e);
345         }
346     }
347 
348     m_info->hasNameCache = true;
349 }
350 
namedItems(const AtomicString & name,Vector<RefPtr<Node>> & result) const351 void HTMLCollection::namedItems(const AtomicString& name, Vector<RefPtr<Node> >& result) const
352 {
353     ASSERT(result.isEmpty());
354 
355     if (name.isEmpty())
356         return;
357 
358     resetCollectionInfo();
359     updateNameCache();
360 
361     Vector<Element*>* idResults = m_info->idCache.get(name.impl());
362     Vector<Element*>* nameResults = m_info->nameCache.get(name.impl());
363 
364     for (unsigned i = 0; idResults && i < idResults->size(); ++i)
365         result.append(idResults->at(i));
366 
367     for (unsigned i = 0; nameResults && i < nameResults->size(); ++i)
368         result.append(nameResults->at(i));
369 }
370 
371 
nextNamedItem(const AtomicString & name) const372 Node* HTMLCollection::nextNamedItem(const AtomicString& name) const
373 {
374     resetCollectionInfo();
375 
376     for (Element* e = itemAfter(m_info->current); e; e = itemAfter(e)) {
377         if (checkForNameMatch(e, m_idsDone, name)) {
378             m_info->current = e;
379             return e;
380         }
381     }
382 
383     if (m_idsDone) {
384         m_info->current = 0;
385         return 0;
386     }
387     m_idsDone = true;
388 
389     for (Element* e = itemAfter(m_info->current); e; e = itemAfter(e)) {
390         if (checkForNameMatch(e, m_idsDone, name)) {
391             m_info->current = e;
392             return e;
393         }
394     }
395 
396     return 0;
397 }
398 
tags(const String & name)399 PassRefPtr<NodeList> HTMLCollection::tags(const String& name)
400 {
401     return m_base->getElementsByTagName(name);
402 }
403 
404 } // namespace WebCore
405