• 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,Type type)38 HTMLCollection::HTMLCollection(PassRefPtr<Node> base, Type 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,Type type,CollectionInfo * info)47 HTMLCollection::HTMLCollection(PassRefPtr<Node> base, Type type, CollectionInfo* 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,Type type)56 PassRefPtr<HTMLCollection> HTMLCollection::create(PassRefPtr<Node> base, Type 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 
CollectionInfo()67 HTMLCollection::CollectionInfo::CollectionInfo()
68     : version(0)
69 {
70     reset();
71 }
72 
copyCacheMap(NodeCacheMap & dest,const NodeCacheMap & src)73 inline void HTMLCollection::CollectionInfo::copyCacheMap(NodeCacheMap& dest, const NodeCacheMap& src)
74 {
75     ASSERT(dest.isEmpty());
76     NodeCacheMap::const_iterator end = src.end();
77     for (NodeCacheMap::const_iterator it = src.begin(); it != end; ++it)
78         dest.add(it->first, new Vector<Element*>(*it->second));
79 }
80 
CollectionInfo(const CollectionInfo & other)81 HTMLCollection::CollectionInfo::CollectionInfo(const CollectionInfo& other)
82     : version(other.version)
83     , current(other.current)
84     , position(other.position)
85     , length(other.length)
86     , elementsArrayPosition(other.elementsArrayPosition)
87     , hasLength(other.hasLength)
88     , hasNameCache(other.hasNameCache)
89 {
90     copyCacheMap(idCache, other.idCache);
91     copyCacheMap(nameCache, other.nameCache);
92 }
93 
swap(CollectionInfo & other)94 void HTMLCollection::CollectionInfo::swap(CollectionInfo& other)
95 {
96     std::swap(version, other.version);
97     std::swap(current, other.current);
98     std::swap(position, other.position);
99     std::swap(length, other.length);
100     std::swap(elementsArrayPosition, other.elementsArrayPosition);
101 
102     idCache.swap(other.idCache);
103     nameCache.swap(other.nameCache);
104 
105     std::swap(hasLength, other.hasLength);
106     std::swap(hasNameCache, other.hasNameCache);
107 }
108 
~CollectionInfo()109 HTMLCollection::CollectionInfo::~CollectionInfo()
110 {
111     deleteAllValues(idCache);
112     deleteAllValues(nameCache);
113 }
114 
reset()115 void HTMLCollection::CollectionInfo::reset()
116 {
117     current = 0;
118     position = 0;
119     length = 0;
120     hasLength = false;
121     elementsArrayPosition = 0;
122     deleteAllValues(idCache);
123     idCache.clear();
124     deleteAllValues(nameCache);
125     nameCache.clear();
126     hasNameCache = false;
127 }
128 
resetCollectionInfo() const129 void HTMLCollection::resetCollectionInfo() const
130 {
131     unsigned docversion = static_cast<HTMLDocument*>(m_base->document())->domTreeVersion();
132 
133     if (!m_info) {
134         m_info = new CollectionInfo;
135         m_ownsInfo = true;
136         m_info->version = docversion;
137         return;
138     }
139 
140     if (m_info->version != docversion) {
141         m_info->reset();
142         m_info->version = docversion;
143     }
144 }
145 
nextNodeOrSibling(Node * base,Node * node,bool includeChildren)146 static Node* nextNodeOrSibling(Node* base, Node* node, bool includeChildren)
147 {
148     return includeChildren ? node->traverseNextNode(base) : node->traverseNextSibling(base);
149 }
150 
itemAfter(Element * previous) const151 Element* HTMLCollection::itemAfter(Element* previous) const
152 {
153     bool deep = true;
154 
155     switch (m_type) {
156         case DocAll:
157         case DocAnchors:
158         case DocApplets:
159         case DocEmbeds:
160         case DocForms:
161         case DocImages:
162         case DocLinks:
163         case DocObjects:
164         case DocScripts:
165         case DocumentNamedItems:
166         case MapAreas:
167         case Other:
168         case SelectOptions:
169         case WindowNamedItems:
170             break;
171         case NodeChildren:
172         case TRCells:
173         case TSectionRows:
174         case TableTBodies:
175             deep = false;
176             break;
177     }
178 
179     Node* current;
180     if (!previous)
181         current = m_base->firstChild();
182     else
183         current = nextNodeOrSibling(m_base.get(), previous, deep);
184 
185     for (; current; current = nextNodeOrSibling(m_base.get(), current, deep)) {
186         if (!current->isElementNode())
187             continue;
188         Element* e = static_cast<Element*>(current);
189         switch (m_type) {
190             case DocImages:
191                 if (e->hasLocalName(imgTag))
192                     return e;
193                 break;
194             case DocScripts:
195                 if (e->hasLocalName(scriptTag))
196                     return e;
197                 break;
198             case DocForms:
199                 if (e->hasLocalName(formTag))
200                     return e;
201                 break;
202             case TableTBodies:
203                 if (e->hasLocalName(tbodyTag))
204                     return e;
205                 break;
206             case TRCells:
207                 if (e->hasLocalName(tdTag) || e->hasLocalName(thTag))
208                     return e;
209                 break;
210             case TSectionRows:
211                 if (e->hasLocalName(trTag))
212                     return e;
213                 break;
214             case SelectOptions:
215                 if (e->hasLocalName(optionTag))
216                     return e;
217                 break;
218             case MapAreas:
219                 if (e->hasLocalName(areaTag))
220                     return e;
221                 break;
222             case DocApplets: // all <applet> elements and <object> elements that contain Java Applets
223                 if (e->hasLocalName(appletTag))
224                     return e;
225                 if (e->hasLocalName(objectTag) && static_cast<HTMLObjectElement*>(e)->containsJavaApplet())
226                     return e;
227                 break;
228             case DocEmbeds:
229                 if (e->hasLocalName(embedTag))
230                     return e;
231                 break;
232             case DocObjects:
233                 if (e->hasLocalName(objectTag))
234                     return e;
235                 break;
236             case DocLinks: // all <a> and <area> elements with a value for href
237                 if ((e->hasLocalName(aTag) || e->hasLocalName(areaTag)) && (!e->getAttribute(hrefAttr).isNull()))
238                     return e;
239                 break;
240             case DocAnchors: // all <a> elements with a value for name
241                 if (e->hasLocalName(aTag) && !e->getAttribute(nameAttr).isNull())
242                     return e;
243                 break;
244             case DocAll:
245             case NodeChildren:
246                 return e;
247             case DocumentNamedItems:
248             case Other:
249             case WindowNamedItems:
250                 ASSERT_NOT_REACHED();
251                 break;
252         }
253     }
254 
255     return 0;
256 }
257 
calcLength() const258 unsigned HTMLCollection::calcLength() const
259 {
260     unsigned len = 0;
261     for (Element* current = itemAfter(0); current; current = itemAfter(current))
262         ++len;
263     return len;
264 }
265 
266 // since the collections are to be "live", we have to do the
267 // calculation every time if anything has changed
length() const268 unsigned HTMLCollection::length() const
269 {
270     resetCollectionInfo();
271     if (!m_info->hasLength) {
272         m_info->length = calcLength();
273         m_info->hasLength = true;
274     }
275     return m_info->length;
276 }
277 
item(unsigned index) const278 Node* HTMLCollection::item(unsigned index) const
279 {
280      resetCollectionInfo();
281      if (m_info->current && m_info->position == index)
282          return m_info->current;
283      if (m_info->hasLength && m_info->length <= index)
284          return 0;
285      if (!m_info->current || m_info->position > index) {
286          m_info->current = itemAfter(0);
287          m_info->position = 0;
288          if (!m_info->current)
289              return 0;
290      }
291      Element* e = m_info->current;
292      for (unsigned pos = m_info->position; e && pos < index; pos++)
293          e = itemAfter(e);
294      m_info->current = e;
295      m_info->position = index;
296      return m_info->current;
297 }
298 
firstItem() const299 Node* HTMLCollection::firstItem() const
300 {
301      return item(0);
302 }
303 
nextItem() const304 Node* HTMLCollection::nextItem() const
305 {
306      resetCollectionInfo();
307 
308 #ifdef ANDROID_FIX
309      // resetCollectionInfo() can set info->current to be 0. If this is the
310      // case, we need to go back to the firstItem. Otherwise traverseNextItem
311      // will crash.
312      if (!m_info->current)
313          return firstItem();
314 #endif
315 
316      // Look for the 'second' item. The first one is currentItem, already given back.
317      Element* retval = itemAfter(m_info->current);
318      m_info->current = retval;
319      m_info->position++;
320      return retval;
321 }
322 
checkForNameMatch(Element * element,bool checkName,const AtomicString & name) const323 bool HTMLCollection::checkForNameMatch(Element* element, bool checkName, const AtomicString& name) const
324 {
325     if (!element->isHTMLElement())
326         return false;
327 
328     HTMLElement* e = static_cast<HTMLElement*>(element);
329     if (!checkName)
330         return e->getAttribute(idAttr) == name;
331 
332     // document.all returns only images, forms, applets, objects and embeds
333     // by name (though everything by id)
334     if (m_type == DocAll &&
335         !(e->hasLocalName(imgTag) || e->hasLocalName(formTag) ||
336           e->hasLocalName(appletTag) || e->hasLocalName(objectTag) ||
337           e->hasLocalName(embedTag) || e->hasLocalName(inputTag) ||
338           e->hasLocalName(selectTag)))
339         return false;
340 
341     return e->getAttribute(nameAttr) == name && e->getAttribute(idAttr) != name;
342 }
343 
namedItem(const AtomicString & name) const344 Node* HTMLCollection::namedItem(const AtomicString& name) const
345 {
346     // http://msdn.microsoft.com/workshop/author/dhtml/reference/methods/nameditem.asp
347     // This method first searches for an object with a matching id
348     // attribute. If a match is not found, the method then searches for an
349     // object with a matching name attribute, but only on those elements
350     // that are allowed a name attribute.
351     resetCollectionInfo();
352     m_idsDone = false;
353 
354     for (Element* e = itemAfter(0); e; e = itemAfter(e)) {
355         if (checkForNameMatch(e, m_idsDone, name)) {
356             m_info->current = e;
357             return e;
358         }
359     }
360 
361     m_idsDone = true;
362 
363     for (Element* e = itemAfter(0); e; e = itemAfter(e)) {
364         if (checkForNameMatch(e, m_idsDone, name)) {
365             m_info->current = e;
366             return e;
367         }
368     }
369 
370     m_info->current = 0;
371     return 0;
372 }
373 
updateNameCache() const374 void HTMLCollection::updateNameCache() const
375 {
376     if (m_info->hasNameCache)
377         return;
378 
379     for (Element* element = itemAfter(0); element; element = itemAfter(element)) {
380         if (!element->isHTMLElement())
381             continue;
382         HTMLElement* e = static_cast<HTMLElement*>(element);
383         const AtomicString& idAttrVal = e->getAttribute(idAttr);
384         const AtomicString& nameAttrVal = e->getAttribute(nameAttr);
385         if (!idAttrVal.isEmpty()) {
386             // add to id cache
387             Vector<Element*>* idVector = m_info->idCache.get(idAttrVal.impl());
388             if (!idVector) {
389                 idVector = new Vector<Element*>;
390                 m_info->idCache.add(idAttrVal.impl(), idVector);
391             }
392             idVector->append(e);
393         }
394         if (!nameAttrVal.isEmpty() && idAttrVal != nameAttrVal
395             && (m_type != DocAll ||
396                 (e->hasLocalName(imgTag) || e->hasLocalName(formTag) ||
397                  e->hasLocalName(appletTag) || e->hasLocalName(objectTag) ||
398                  e->hasLocalName(embedTag) || e->hasLocalName(inputTag) ||
399                  e->hasLocalName(selectTag)))) {
400             // add to name cache
401             Vector<Element*>* nameVector = m_info->nameCache.get(nameAttrVal.impl());
402             if (!nameVector) {
403                 nameVector = new Vector<Element*>;
404                 m_info->nameCache.add(nameAttrVal.impl(), nameVector);
405             }
406             nameVector->append(e);
407         }
408     }
409 
410     m_info->hasNameCache = true;
411 }
412 
namedItems(const AtomicString & name,Vector<RefPtr<Node>> & result) const413 void HTMLCollection::namedItems(const AtomicString& name, Vector<RefPtr<Node> >& result) const
414 {
415     ASSERT(result.isEmpty());
416 
417     if (name.isEmpty())
418         return;
419 
420     resetCollectionInfo();
421     updateNameCache();
422 
423     Vector<Element*>* idResults = m_info->idCache.get(name.impl());
424     Vector<Element*>* nameResults = m_info->nameCache.get(name.impl());
425 
426     for (unsigned i = 0; idResults && i < idResults->size(); ++i)
427         result.append(idResults->at(i));
428 
429     for (unsigned i = 0; nameResults && i < nameResults->size(); ++i)
430         result.append(nameResults->at(i));
431 }
432 
433 
nextNamedItem(const AtomicString & name) const434 Node* HTMLCollection::nextNamedItem(const AtomicString& name) const
435 {
436     resetCollectionInfo();
437 
438     for (Element* e = itemAfter(m_info->current); e; e = itemAfter(e)) {
439         if (checkForNameMatch(e, m_idsDone, name)) {
440             m_info->current = e;
441             return e;
442         }
443     }
444 
445     if (m_idsDone) {
446         m_info->current = 0;
447         return 0;
448     }
449     m_idsDone = true;
450 
451     for (Element* e = itemAfter(m_info->current); e; e = itemAfter(e)) {
452         if (checkForNameMatch(e, m_idsDone, name)) {
453             m_info->current = e;
454             return e;
455         }
456     }
457 
458     return 0;
459 }
460 
tags(const String & name)461 PassRefPtr<NodeList> HTMLCollection::tags(const String& name)
462 {
463     return m_base->getElementsByTagName(name);
464 }
465 
466 } // namespace WebCore
467