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