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