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 | |
31 | namespace WebCore { |
32 | |
33 | using namespace HTMLNames; |
34 | |
35 | WTF_MAKE_ISO_ALLOCATED_IMPL(HTMLCollection); |
36 | |
37 | inline 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 | |
73 | static 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 | |
115 | HTMLCollection::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 | |
126 | HTMLCollection::~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 | |
146 | void HTMLCollection::invalidateCacheForDocument(Document& document) |
147 | { |
148 | if (hasNamedElementCache()) |
149 | invalidateNamedElementCache(document); |
150 | } |
151 | |
152 | void 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 | |
162 | Element* 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. |
182 | const Vector<AtomicString>& HTMLCollection::supportedPropertyNames() |
183 | { |
184 | updateNamedElementCache(); |
185 | ASSERT(m_namedElementCache); |
186 | |
187 | return m_namedElementCache->propertyNames(); |
188 | } |
189 | |
190 | bool 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 | |
203 | void 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 | |
226 | Vector<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 | |