1 /* 2 * Copyright (C) 1999 Lars Knoll (knoll (at) kde.org) 3 * (C) 1999 Antti Koivisto (koivisto (at) kde.org) 4 * Copyright (C) 2003, 2004, 2005, 2006, 2007, 2008, 2011, 2012 Apple Inc. All rights reserved. 5 * Copyright (C) 2014 Samsung Electronics. All rights reserved. 6 * 7 * This library is free software; you can redistribute it and/or 8 * modify it under the terms of the GNU Library General Public 9 * License as published by the Free Software Foundation; either 10 * version 2 of the License, or (at your option) any later version. 11 * 12 * This library is distributed in the hope that it will be useful, 13 * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 15 * Library General Public License for more details. 16 * 17 * You should have received a copy of the GNU Library General Public License 18 * along with this library; see the file COPYING.LIB. If not, write to 19 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, 20 * Boston, MA 02110-1301, USA. 21 * 22 */ 23 24 #include "config.h" 25 #include "core/html/HTMLCollection.h" 26 27 #include "core/HTMLNames.h" 28 #include "core/dom/ClassCollection.h" 29 #include "core/dom/ElementTraversal.h" 30 #include "core/dom/NodeRareData.h" 31 #include "core/html/DocumentNameCollection.h" 32 #include "core/html/HTMLElement.h" 33 #include "core/html/HTMLObjectElement.h" 34 #include "core/html/HTMLOptionElement.h" 35 #include "core/html/WindowNameCollection.h" 36 #include "wtf/HashSet.h" 37 38 namespace WebCore { 39 40 using namespace HTMLNames; 41 42 static bool shouldTypeOnlyIncludeDirectChildren(CollectionType type) 43 { 44 switch (type) { 45 case ClassCollectionType: 46 case TagCollectionType: 47 case HTMLTagCollectionType: 48 case DocAll: 49 case DocAnchors: 50 case DocApplets: 51 case DocEmbeds: 52 case DocForms: 53 case DocImages: 54 case DocLinks: 55 case DocScripts: 56 case DocumentNamedItems: 57 case MapAreas: 58 case TableRows: 59 case SelectOptions: 60 case SelectedOptions: 61 case DataListOptions: 62 case WindowNamedItems: 63 case FormControls: 64 return false; 65 case NodeChildren: 66 case TRCells: 67 case TSectionRows: 68 case TableTBodies: 69 return true; 70 case NameNodeListType: 71 case RadioNodeListType: 72 case RadioImgNodeListType: 73 case LabelsNodeListType: 74 break; 75 } 76 ASSERT_NOT_REACHED(); 77 return false; 78 } 79 80 static NodeListRootType rootTypeFromCollectionType(CollectionType type) 81 { 82 switch (type) { 83 case DocImages: 84 case DocApplets: 85 case DocEmbeds: 86 case DocForms: 87 case DocLinks: 88 case DocAnchors: 89 case DocScripts: 90 case DocAll: 91 case WindowNamedItems: 92 case DocumentNamedItems: 93 case FormControls: 94 return NodeListIsRootedAtDocument; 95 case ClassCollectionType: 96 case TagCollectionType: 97 case HTMLTagCollectionType: 98 case NodeChildren: 99 case TableTBodies: 100 case TSectionRows: 101 case TableRows: 102 case TRCells: 103 case SelectOptions: 104 case SelectedOptions: 105 case DataListOptions: 106 case MapAreas: 107 return NodeListIsRootedAtNode; 108 case NameNodeListType: 109 case RadioNodeListType: 110 case RadioImgNodeListType: 111 case LabelsNodeListType: 112 break; 113 } 114 ASSERT_NOT_REACHED(); 115 return NodeListIsRootedAtNode; 116 } 117 118 static NodeListInvalidationType invalidationTypeExcludingIdAndNameAttributes(CollectionType type) 119 { 120 switch (type) { 121 case TagCollectionType: 122 case HTMLTagCollectionType: 123 case DocImages: 124 case DocEmbeds: 125 case DocForms: 126 case DocScripts: 127 case DocAll: 128 case NodeChildren: 129 case TableTBodies: 130 case TSectionRows: 131 case TableRows: 132 case TRCells: 133 case SelectOptions: 134 case MapAreas: 135 return DoNotInvalidateOnAttributeChanges; 136 case DocApplets: 137 case SelectedOptions: 138 case DataListOptions: 139 // FIXME: We can do better some day. 140 return InvalidateOnAnyAttrChange; 141 case DocAnchors: 142 return InvalidateOnNameAttrChange; 143 case DocLinks: 144 return InvalidateOnHRefAttrChange; 145 case WindowNamedItems: 146 return InvalidateOnIdNameAttrChange; 147 case DocumentNamedItems: 148 return InvalidateOnIdNameAttrChange; 149 case FormControls: 150 return InvalidateForFormControls; 151 case ClassCollectionType: 152 return InvalidateOnClassAttrChange; 153 case NameNodeListType: 154 case RadioNodeListType: 155 case RadioImgNodeListType: 156 case LabelsNodeListType: 157 break; 158 } 159 ASSERT_NOT_REACHED(); 160 return DoNotInvalidateOnAttributeChanges; 161 } 162 163 HTMLCollection::HTMLCollection(ContainerNode& ownerNode, CollectionType type, ItemAfterOverrideType itemAfterOverrideType) 164 : LiveNodeListBase(ownerNode, rootTypeFromCollectionType(type), invalidationTypeExcludingIdAndNameAttributes(type), type) 165 , m_overridesItemAfter(itemAfterOverrideType == OverridesItemAfter) 166 , m_shouldOnlyIncludeDirectChildren(shouldTypeOnlyIncludeDirectChildren(type)) 167 { 168 ScriptWrappable::init(this); 169 } 170 171 PassRefPtrWillBeRawPtr<HTMLCollection> HTMLCollection::create(ContainerNode& base, CollectionType type) 172 { 173 return adoptRefWillBeNoop(new HTMLCollection(base, type, DoesNotOverrideItemAfter)); 174 } 175 176 HTMLCollection::~HTMLCollection() 177 { 178 #if !ENABLE(OILPAN) 179 if (hasValidIdNameCache()) 180 unregisterIdNameCacheFromDocument(document()); 181 // Named HTMLCollection types remove cache by themselves. 182 if (isUnnamedHTMLCollectionType(type())) 183 ownerNode().nodeLists()->removeCache(this, type()); 184 #endif 185 } 186 187 void HTMLCollection::invalidateCache(Document* oldDocument) const 188 { 189 m_collectionIndexCache.invalidate(); 190 invalidateIdNameCacheMaps(oldDocument); 191 } 192 193 template <class NodeListType> 194 inline bool isMatchingElement(const NodeListType&, const Element&); 195 196 template <> inline bool isMatchingElement(const HTMLCollection& htmlCollection, const Element& element) 197 { 198 CollectionType type = htmlCollection.type(); 199 200 // These collections apply to any kind of Elements, not just HTMLElements. 201 switch (type) { 202 case DocAll: 203 case NodeChildren: 204 return true; 205 case ClassCollectionType: 206 return toClassCollection(htmlCollection).elementMatches(element); 207 case TagCollectionType: 208 return toTagCollection(htmlCollection).elementMatches(element); 209 case HTMLTagCollectionType: 210 return toHTMLTagCollection(htmlCollection).elementMatches(element); 211 case DocumentNamedItems: 212 return toDocumentNameCollection(htmlCollection).elementMatches(element); 213 case WindowNamedItems: 214 return toWindowNameCollection(htmlCollection).elementMatches(element); 215 default: 216 break; 217 } 218 219 // The following only applies to HTMLElements. 220 if (!element.isHTMLElement()) 221 return false; 222 223 switch (type) { 224 case DocImages: 225 return element.hasLocalName(imgTag); 226 case DocScripts: 227 return element.hasLocalName(scriptTag); 228 case DocForms: 229 return element.hasLocalName(formTag); 230 case TableTBodies: 231 return element.hasLocalName(tbodyTag); 232 case TRCells: 233 return element.hasLocalName(tdTag) || element.hasLocalName(thTag); 234 case TSectionRows: 235 return element.hasLocalName(trTag); 236 case SelectOptions: 237 return element.hasLocalName(optionTag); 238 case SelectedOptions: 239 return element.hasLocalName(optionTag) && toHTMLOptionElement(element).selected(); 240 case DataListOptions: 241 if (element.hasLocalName(optionTag)) { 242 const HTMLOptionElement& option = toHTMLOptionElement(element); 243 if (!option.isDisabledFormControl() && !option.value().isEmpty()) 244 return true; 245 } 246 return false; 247 case MapAreas: 248 return element.hasLocalName(areaTag); 249 case DocApplets: 250 return element.hasLocalName(appletTag) || (element.hasLocalName(objectTag) && toHTMLObjectElement(element).containsJavaApplet()); 251 case DocEmbeds: 252 return element.hasLocalName(embedTag); 253 case DocLinks: 254 return (element.hasLocalName(aTag) || element.hasLocalName(areaTag)) && element.fastHasAttribute(hrefAttr); 255 case DocAnchors: 256 return element.hasLocalName(aTag) && element.fastHasAttribute(nameAttr); 257 case ClassCollectionType: 258 case TagCollectionType: 259 case HTMLTagCollectionType: 260 case DocAll: 261 case NodeChildren: 262 case FormControls: 263 case DocumentNamedItems: 264 case TableRows: 265 case WindowNamedItems: 266 case NameNodeListType: 267 case RadioNodeListType: 268 case RadioImgNodeListType: 269 case LabelsNodeListType: 270 ASSERT_NOT_REACHED(); 271 } 272 return false; 273 } 274 275 template <> inline bool isMatchingElement(const ClassCollection& collection, const Element& element) 276 { 277 return collection.elementMatches(element); 278 } 279 280 template <> inline bool isMatchingElement(const HTMLTagCollection& collection, const Element& element) 281 { 282 return collection.elementMatches(element); 283 } 284 285 Element* HTMLCollection::virtualItemAfter(Element*) const 286 { 287 ASSERT_NOT_REACHED(); 288 return 0; 289 } 290 291 static inline bool nameShouldBeVisibleInDocumentAll(const HTMLElement& element) 292 { 293 // http://www.whatwg.org/specs/web-apps/current-work/multipage/common-dom-interfaces.html#dom-htmlallcollection-nameditem: 294 // The document.all collection returns only certain types of elements by name, 295 // although it returns any type of element by id. 296 return element.hasLocalName(aTag) 297 || element.hasLocalName(appletTag) 298 || element.hasLocalName(areaTag) 299 || element.hasLocalName(embedTag) 300 || element.hasLocalName(formTag) 301 || element.hasLocalName(frameTag) 302 || element.hasLocalName(framesetTag) 303 || element.hasLocalName(iframeTag) 304 || element.hasLocalName(imgTag) 305 || element.hasLocalName(inputTag) 306 || element.hasLocalName(objectTag) 307 || element.hasLocalName(selectTag); 308 } 309 310 inline Element* firstMatchingChildElement(const HTMLCollection& nodeList) 311 { 312 Element* element = ElementTraversal::firstChild(nodeList.rootNode()); 313 while (element && !isMatchingElement(nodeList, *element)) 314 element = ElementTraversal::nextSibling(*element); 315 return element; 316 } 317 318 inline Element* lastMatchingChildElement(const HTMLCollection& nodeList) 319 { 320 Element* element = ElementTraversal::lastChild(nodeList.rootNode()); 321 while (element && !isMatchingElement(nodeList, *element)) 322 element = ElementTraversal::previousSibling(*element); 323 return element; 324 } 325 326 inline Element* nextMatchingChildElement(const HTMLCollection& nodeList, Element& current) 327 { 328 Element* next = ¤t; 329 do { 330 next = ElementTraversal::nextSibling(*next); 331 } while (next && !isMatchingElement(nodeList, *next)); 332 return next; 333 } 334 335 inline Element* previousMatchingChildElement(const HTMLCollection& nodeList, Element& current) 336 { 337 Element* previous = ¤t; 338 do { 339 previous = ElementTraversal::previousSibling(*previous); 340 } while (previous && !isMatchingElement(nodeList, *previous)); 341 return previous; 342 } 343 344 Element* HTMLCollection::traverseToFirstElement() const 345 { 346 switch (type()) { 347 case HTMLTagCollectionType: 348 return firstMatchingElement(toHTMLTagCollection(*this)); 349 case ClassCollectionType: 350 return firstMatchingElement(toClassCollection(*this)); 351 default: 352 if (overridesItemAfter()) 353 return virtualItemAfter(0); 354 if (shouldOnlyIncludeDirectChildren()) 355 return firstMatchingChildElement(*this); 356 return firstMatchingElement(*this); 357 } 358 } 359 360 Element* HTMLCollection::traverseToLastElement() const 361 { 362 ASSERT(canTraverseBackward()); 363 if (shouldOnlyIncludeDirectChildren()) 364 return lastMatchingChildElement(*this); 365 return lastMatchingElement(*this); 366 } 367 368 Element* HTMLCollection::traverseForwardToOffset(unsigned offset, Element& currentElement, unsigned& currentOffset) const 369 { 370 ASSERT(currentOffset < offset); 371 switch (type()) { 372 case HTMLTagCollectionType: 373 return traverseMatchingElementsForwardToOffset(toHTMLTagCollection(*this), offset, currentElement, currentOffset); 374 case ClassCollectionType: 375 return traverseMatchingElementsForwardToOffset(toClassCollection(*this), offset, currentElement, currentOffset); 376 default: 377 if (overridesItemAfter()) { 378 Element* next = ¤tElement; 379 while ((next = virtualItemAfter(next))) { 380 if (++currentOffset == offset) 381 return next; 382 } 383 return 0; 384 } 385 if (shouldOnlyIncludeDirectChildren()) { 386 Element* next = ¤tElement; 387 while ((next = nextMatchingChildElement(*this, *next))) { 388 if (++currentOffset == offset) 389 return next; 390 } 391 return 0; 392 } 393 return traverseMatchingElementsForwardToOffset(*this, offset, currentElement, currentOffset); 394 } 395 } 396 397 Element* HTMLCollection::traverseBackwardToOffset(unsigned offset, Element& currentElement, unsigned& currentOffset) const 398 { 399 ASSERT(currentOffset > offset); 400 ASSERT(canTraverseBackward()); 401 if (shouldOnlyIncludeDirectChildren()) { 402 Element* previous = ¤tElement; 403 while ((previous = previousMatchingChildElement(*this, *previous))) { 404 if (--currentOffset == offset) 405 return previous; 406 } 407 return 0; 408 } 409 return traverseMatchingElementsBackwardToOffset(*this, offset, currentElement, currentOffset); 410 } 411 412 Element* HTMLCollection::namedItem(const AtomicString& name) const 413 { 414 // http://msdn.microsoft.com/workshop/author/dhtml/reference/methods/nameditem.asp 415 // This method first searches for an object with a matching id 416 // attribute. If a match is not found, the method then searches for an 417 // object with a matching name attribute, but only on those elements 418 // that are allowed a name attribute. 419 updateIdNameCache(); 420 421 const NamedItemCache& cache = namedItemCache(); 422 WillBeHeapVector<RawPtrWillBeMember<Element> >* idResults = cache.getElementsById(name); 423 if (idResults && !idResults->isEmpty()) 424 return idResults->first(); 425 426 WillBeHeapVector<RawPtrWillBeMember<Element> >* nameResults = cache.getElementsByName(name); 427 if (nameResults && !nameResults->isEmpty()) 428 return nameResults->first(); 429 430 return 0; 431 } 432 433 bool HTMLCollection::namedPropertyQuery(const AtomicString& name, ExceptionState&) 434 { 435 return namedItem(name); 436 } 437 438 void HTMLCollection::supportedPropertyNames(Vector<String>& names) 439 { 440 // As per the specification (http://dom.spec.whatwg.org/#htmlcollection): 441 // The supported property names are the values from the list returned by these steps: 442 // 1. Let result be an empty list. 443 // 2. For each element represented by the collection, in tree order, run these substeps: 444 // 1. If element has an ID which is neither the empty string nor is in result, append element's ID to result. 445 // 2. If element is in the HTML namespace and has a name attribute whose value is neither the empty string 446 // nor is in result, append element's name attribute value to result. 447 // 3. Return result. 448 HashSet<AtomicString> existingNames; 449 unsigned length = this->length(); 450 for (unsigned i = 0; i < length; ++i) { 451 Element* element = item(i); 452 const AtomicString& idAttribute = element->getIdAttribute(); 453 if (!idAttribute.isEmpty()) { 454 HashSet<AtomicString>::AddResult addResult = existingNames.add(idAttribute); 455 if (addResult.isNewEntry) 456 names.append(idAttribute); 457 } 458 if (!element->isHTMLElement()) 459 continue; 460 const AtomicString& nameAttribute = element->getNameAttribute(); 461 if (!nameAttribute.isEmpty() && (type() != DocAll || nameShouldBeVisibleInDocumentAll(toHTMLElement(*element)))) { 462 HashSet<AtomicString>::AddResult addResult = existingNames.add(nameAttribute); 463 if (addResult.isNewEntry) 464 names.append(nameAttribute); 465 } 466 } 467 } 468 469 void HTMLCollection::namedPropertyEnumerator(Vector<String>& names, ExceptionState&) 470 { 471 supportedPropertyNames(names); 472 } 473 474 void HTMLCollection::updateIdNameCache() const 475 { 476 if (hasValidIdNameCache()) 477 return; 478 479 OwnPtrWillBeRawPtr<NamedItemCache> cache = NamedItemCache::create(); 480 unsigned length = this->length(); 481 for (unsigned i = 0; i < length; ++i) { 482 Element* element = item(i); 483 const AtomicString& idAttrVal = element->getIdAttribute(); 484 if (!idAttrVal.isEmpty()) 485 cache->addElementWithId(idAttrVal, element); 486 if (!element->isHTMLElement()) 487 continue; 488 const AtomicString& nameAttrVal = element->getNameAttribute(); 489 if (!nameAttrVal.isEmpty() && idAttrVal != nameAttrVal && (type() != DocAll || nameShouldBeVisibleInDocumentAll(toHTMLElement(*element)))) 490 cache->addElementWithName(nameAttrVal, element); 491 } 492 // Set the named item cache last as traversing the tree may cause cache invalidation. 493 setNamedItemCache(cache.release()); 494 } 495 496 void HTMLCollection::namedItems(const AtomicString& name, WillBeHeapVector<RefPtrWillBeMember<Element> >& result) const 497 { 498 ASSERT(result.isEmpty()); 499 if (name.isEmpty()) 500 return; 501 502 updateIdNameCache(); 503 504 const NamedItemCache& cache = namedItemCache(); 505 if (WillBeHeapVector<RawPtrWillBeMember<Element> >* idResults = cache.getElementsById(name)) { 506 for (unsigned i = 0; i < idResults->size(); ++i) 507 result.append(idResults->at(i)); 508 } 509 if (WillBeHeapVector<RawPtrWillBeMember<Element> >* nameResults = cache.getElementsByName(name)) { 510 for (unsigned i = 0; i < nameResults->size(); ++i) 511 result.append(nameResults->at(i)); 512 } 513 } 514 515 HTMLCollection::NamedItemCache::NamedItemCache() 516 { 517 } 518 519 void HTMLCollection::trace(Visitor* visitor) 520 { 521 visitor->trace(m_namedItemCache); 522 visitor->trace(m_collectionIndexCache); 523 LiveNodeListBase::trace(visitor); 524 } 525 526 } // namespace WebCore 527