Home | History | Annotate | Download | only in html
      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 = &current;
    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 = &current;
    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 = &currentElement;
    379             while ((next = virtualItemAfter(next))) {
    380                 if (++currentOffset == offset)
    381                     return next;
    382             }
    383             return 0;
    384         }
    385         if (shouldOnlyIncludeDirectChildren()) {
    386             Element* next = &currentElement;
    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 = &currentElement;
    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