1/*
2 * Copyright (C) 1999 Lars Knoll (knoll@kde.org)
3 * (C) 1999 Antti Koivisto (koivisto@kde.org)
4 * Copyright (C) 2003-2017 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 "CachedHTMLCollection.h"
27#include "HTMLNames.h"
28#include "NodeRareData.h"
29#include <wtf/IsoMallocInlines.h>
30
31namespace WebCore {
32
33using namespace HTMLNames;
34
35WTF_MAKE_ISO_ALLOCATED_IMPL(HTMLCollection);
36
37inline auto HTMLCollection::rootTypeFromCollectionType(CollectionType type) -> RootType
38{
39 switch (type) {
40 case DocImages:
41 case DocApplets:
42 case DocEmbeds:
43 case DocForms:
44 case DocLinks:
45 case DocAnchors:
46 case DocScripts:
47 case DocAll:
48 case WindowNamedItems:
49 case DocumentNamedItems:
50 case DocumentAllNamedItems:
51 case FormControls:
52 return HTMLCollection::IsRootedAtDocument;
53 case AllDescendants:
54 case ByClass:
55 case ByTag:
56 case ByHTMLTag:
57 case FieldSetElements:
58 case NodeChildren:
59 case TableTBodies:
60 case TSectionRows:
61 case TableRows:
62 case TRCells:
63 case SelectOptions:
64 case SelectedOptions:
65 case DataListOptions:
66 case MapAreas:
67 return HTMLCollection::IsRootedAtNode;
68 }
69 ASSERT_NOT_REACHED();
70 return HTMLCollection::IsRootedAtNode;
71}
72
73static NodeListInvalidationType invalidationTypeExcludingIdAndNameAttributes(CollectionType type)
74{
75 switch (type) {
76 case ByTag:
77 case ByHTMLTag:
78 case AllDescendants:
79 case DocImages:
80 case DocEmbeds:
81 case DocForms:
82 case DocScripts:
83 case DocAll:
84 case NodeChildren:
85 case TableTBodies:
86 case TSectionRows:
87 case TableRows:
88 case TRCells:
89 case SelectOptions:
90 case MapAreas:
91 return DoNotInvalidateOnAttributeChanges;
92 case DocApplets:
93 case SelectedOptions:
94 case DataListOptions:
95 // FIXME: We can do better some day.
96 return InvalidateOnAnyAttrChange;
97 case ByClass:
98 return InvalidateOnClassAttrChange;
99 case DocAnchors:
100 return InvalidateOnNameAttrChange;
101 case DocLinks:
102 return InvalidateOnHRefAttrChange;
103 case WindowNamedItems:
104 case DocumentNamedItems:
105 case DocumentAllNamedItems:
106 return InvalidateOnIdNameAttrChange;
107 case FieldSetElements:
108 case FormControls:
109 return InvalidateForFormControls;
110 }
111 ASSERT_NOT_REACHED();
112 return DoNotInvalidateOnAttributeChanges;
113}
114
115HTMLCollection::HTMLCollection(ContainerNode& ownerNode, CollectionType type)
116 : m_collectionType(type)
117 , m_invalidationType(invalidationTypeExcludingIdAndNameAttributes(type))
118 , m_rootType(rootTypeFromCollectionType(type))
119 , m_ownerNode(ownerNode)
120{
121 ASSERT(m_rootType == static_cast<unsigned>(rootTypeFromCollectionType(type)));
122 ASSERT(m_invalidationType == static_cast<unsigned>(invalidationTypeExcludingIdAndNameAttributes(type)));
123 ASSERT(m_collectionType == static_cast<unsigned>(type));
124}
125
126HTMLCollection::~HTMLCollection()
127{
128 if (hasNamedElementCache())
129 document().collectionWillClearIdNameMap(*this);
130
131 // HTMLNameCollection & ClassCollection remove cache by themselves.
132 // FIXME: We need a cleaner way to handle this.
133 switch (type()) {
134 case ByClass:
135 case ByTag:
136 case ByHTMLTag:
137 case WindowNamedItems:
138 case DocumentNamedItems:
139 case DocumentAllNamedItems:
140 break;
141 default:
142 ownerNode().nodeLists()->removeCachedCollection(this);
143 }
144}
145
146void HTMLCollection::invalidateCacheForDocument(Document& document)
147{
148 if (hasNamedElementCache())
149 invalidateNamedElementCache(document);
150}
151
152void HTMLCollection::invalidateNamedElementCache(Document& document) const
153{
154 ASSERT(hasNamedElementCache());
155 document.collectionWillClearIdNameMap(*this);
156 {
157 auto locker = holdLock(m_namedElementCacheAssignmentLock);
158 m_namedElementCache = nullptr;
159 }
160}
161
162Element* HTMLCollection::namedItemSlow(const AtomicString& name) const
163{
164 // The pathological case. We need to walk the entire subtree.
165 updateNamedElementCache();
166 ASSERT(m_namedElementCache);
167
168 if (const Vector<Element*>* idResults = m_namedElementCache->findElementsWithId(name)) {
169 if (idResults->size())
170 return idResults->at(0);
171 }
172
173 if (const Vector<Element*>* nameResults = m_namedElementCache->findElementsWithName(name)) {
174 if (nameResults->size())
175 return nameResults->at(0);
176 }
177
178 return nullptr;
179}
180
181// Documented in https://dom.spec.whatwg.org/#interface-htmlcollection.
182const Vector<AtomicString>& HTMLCollection::supportedPropertyNames()
183{
184 updateNamedElementCache();
185 ASSERT(m_namedElementCache);
186
187 return m_namedElementCache->propertyNames();
188}
189
190bool HTMLCollection::isSupportedPropertyName(const String& name)
191{
192 updateNamedElementCache();
193 ASSERT(m_namedElementCache);
194
195 if (m_namedElementCache->findElementsWithId(name))
196 return true;
197 if (m_namedElementCache->findElementsWithName(name))
198 return true;
199
200 return false;
201}
202
203void HTMLCollection::updateNamedElementCache() const
204{
205 if (hasNamedElementCache())
206 return;
207
208 auto cache = std::make_unique<CollectionNamedElementCache>();
209
210 unsigned size = length();
211 for (unsigned i = 0; i < size; ++i) {
212 Element& element = *item(i);
213 const AtomicString& id = element.getIdAttribute();
214 if (!id.isEmpty())
215 cache->appendToIdCache(id, element);
216 if (!is<HTMLElement>(element))
217 continue;
218 const AtomicString& name = element.getNameAttribute();
219 if (!name.isEmpty() && id != name && (type() != DocAll || nameShouldBeVisibleInDocumentAll(downcast<HTMLElement>(element))))
220 cache->appendToNameCache(name, element);
221 }
222
223 setNamedItemCache(WTFMove(cache));
224}
225
226Vector<Ref<Element>> HTMLCollection::namedItems(const AtomicString& name) const
227{
228 // FIXME: This non-virtual function can't possibly be doing the correct thing for
229 // any derived class that overrides the virtual namedItem function.
230
231 Vector<Ref<Element>> elements;
232
233 if (name.isEmpty())
234 return elements;
235
236 updateNamedElementCache();
237 ASSERT(m_namedElementCache);
238
239 auto* elementsWithId = m_namedElementCache->findElementsWithId(name);
240 auto* elementsWithName = m_namedElementCache->findElementsWithName(name);
241
242 elements.reserveInitialCapacity((elementsWithId ? elementsWithId->size() : 0) + (elementsWithName ? elementsWithName->size() : 0));
243
244 if (elementsWithId) {
245 for (auto& element : *elementsWithId)
246 elements.uncheckedAppend(*element);
247 }
248 if (elementsWithName) {
249 for (auto& element : *elementsWithName)
250 elements.uncheckedAppend(*element);
251 }
252
253 return elements;
254}
255
256} // namespace WebCore
257