1 | /* |
2 | * Copyright (C) 2015, 2016 Apple Inc. All rights reserved. |
3 | * |
4 | * Redistribution and use in source and binary forms, with or without |
5 | * modification, are permitted provided that the following conditions |
6 | * are met: |
7 | * 1. Redistributions of source code must retain the above copyright |
8 | * notice, this list of conditions and the following disclaimer. |
9 | * 2. Redistributions in binary form must reproduce the above copyright |
10 | * notice, this list of conditions and the following disclaimer in the |
11 | * documentation and/or other materials provided with the distribution. |
12 | * |
13 | * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY |
14 | * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
15 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR |
16 | * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR |
17 | * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, |
18 | * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, |
19 | * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR |
20 | * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY |
21 | * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
22 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
23 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
24 | */ |
25 | |
26 | #include "config.h" |
27 | #include "CustomElementReactionQueue.h" |
28 | |
29 | #include "CustomElementRegistry.h" |
30 | #include "DOMWindow.h" |
31 | #include "Document.h" |
32 | #include "Element.h" |
33 | #include "HTMLNames.h" |
34 | #include "JSCustomElementInterface.h" |
35 | #include "JSDOMBinding.h" |
36 | #include "Microtasks.h" |
37 | #include <JavaScriptCore/CatchScope.h> |
38 | #include <JavaScriptCore/Heap.h> |
39 | #include <wtf/NeverDestroyed.h> |
40 | #include <wtf/Optional.h> |
41 | #include <wtf/Ref.h> |
42 | #include <wtf/SetForScope.h> |
43 | |
44 | namespace WebCore { |
45 | |
46 | class CustomElementReactionQueueItem { |
47 | public: |
48 | enum class Type { |
49 | ElementUpgrade, |
50 | Connected, |
51 | Disconnected, |
52 | Adopted, |
53 | AttributeChanged, |
54 | }; |
55 | |
56 | CustomElementReactionQueueItem(Type type) |
57 | : m_type(type) |
58 | { } |
59 | |
60 | CustomElementReactionQueueItem(Document& oldDocument, Document& newDocument) |
61 | : m_type(Type::Adopted) |
62 | , m_oldDocument(&oldDocument) |
63 | , m_newDocument(&newDocument) |
64 | { } |
65 | |
66 | CustomElementReactionQueueItem(const QualifiedName& attributeName, const AtomicString& oldValue, const AtomicString& newValue) |
67 | : m_type(Type::AttributeChanged) |
68 | , m_attributeName(attributeName) |
69 | , m_oldValue(oldValue) |
70 | , m_newValue(newValue) |
71 | { } |
72 | |
73 | Type type() const { return m_type; } |
74 | |
75 | void invoke(Element& element, JSCustomElementInterface& elementInterface) |
76 | { |
77 | switch (m_type) { |
78 | case Type::ElementUpgrade: |
79 | elementInterface.upgradeElement(element); |
80 | break; |
81 | case Type::Connected: |
82 | elementInterface.invokeConnectedCallback(element); |
83 | break; |
84 | case Type::Disconnected: |
85 | elementInterface.invokeDisconnectedCallback(element); |
86 | break; |
87 | case Type::Adopted: |
88 | elementInterface.invokeAdoptedCallback(element, *m_oldDocument, *m_newDocument); |
89 | break; |
90 | case Type::AttributeChanged: |
91 | ASSERT(m_attributeName); |
92 | elementInterface.invokeAttributeChangedCallback(element, m_attributeName.value(), m_oldValue, m_newValue); |
93 | break; |
94 | } |
95 | } |
96 | |
97 | private: |
98 | Type m_type; |
99 | RefPtr<Document> m_oldDocument; |
100 | RefPtr<Document> m_newDocument; |
101 | Optional<QualifiedName> m_attributeName; |
102 | AtomicString m_oldValue; |
103 | AtomicString m_newValue; |
104 | }; |
105 | |
106 | CustomElementReactionQueue::CustomElementReactionQueue(JSCustomElementInterface& elementInterface) |
107 | : m_interface(elementInterface) |
108 | { } |
109 | |
110 | CustomElementReactionQueue::~CustomElementReactionQueue() |
111 | { |
112 | ASSERT(m_items.isEmpty()); |
113 | } |
114 | |
115 | void CustomElementReactionQueue::clear() |
116 | { |
117 | m_items.clear(); |
118 | } |
119 | |
120 | void CustomElementReactionQueue::enqueueElementUpgrade(Element& element, bool alreadyScheduledToUpgrade) |
121 | { |
122 | ASSERT(CustomElementReactionDisallowedScope::isReactionAllowed()); |
123 | ASSERT(element.reactionQueue()); |
124 | auto& queue = *element.reactionQueue(); |
125 | if (alreadyScheduledToUpgrade) { |
126 | ASSERT(queue.m_items.size() == 1); |
127 | ASSERT(queue.m_items[0].type() == CustomElementReactionQueueItem::Type::ElementUpgrade); |
128 | } else { |
129 | queue.m_items.append({CustomElementReactionQueueItem::Type::ElementUpgrade}); |
130 | enqueueElementOnAppropriateElementQueue(element); |
131 | } |
132 | } |
133 | |
134 | void CustomElementReactionQueue::enqueueElementUpgradeIfDefined(Element& element) |
135 | { |
136 | ASSERT(CustomElementReactionDisallowedScope::isReactionAllowed()); |
137 | ASSERT(element.isCustomElementUpgradeCandidate()); |
138 | auto* window = element.document().domWindow(); |
139 | if (!window) |
140 | return; |
141 | |
142 | auto* registry = window->customElementRegistry(); |
143 | if (!registry) |
144 | return; |
145 | |
146 | auto* elementInterface = registry->findInterface(element); |
147 | if (!elementInterface) |
148 | return; |
149 | |
150 | element.enqueueToUpgrade(*elementInterface); |
151 | } |
152 | |
153 | void CustomElementReactionQueue::enqueueConnectedCallbackIfNeeded(Element& element) |
154 | { |
155 | ASSERT(CustomElementReactionDisallowedScope::isReactionAllowed()); |
156 | ASSERT(element.isDefinedCustomElement()); |
157 | ASSERT(element.document().refCount() > 0); |
158 | ASSERT(element.reactionQueue()); |
159 | auto& queue = *element.reactionQueue(); |
160 | if (!queue.m_interface->hasConnectedCallback()) |
161 | return; |
162 | queue.m_items.append({CustomElementReactionQueueItem::Type::Connected}); |
163 | enqueueElementOnAppropriateElementQueue(element); |
164 | } |
165 | |
166 | void CustomElementReactionQueue::enqueueDisconnectedCallbackIfNeeded(Element& element) |
167 | { |
168 | ASSERT(CustomElementReactionDisallowedScope::isReactionAllowed()); |
169 | ASSERT(element.isDefinedCustomElement()); |
170 | if (element.document().refCount() <= 0) |
171 | return; // Don't enqueue disconnectedCallback if the entire document is getting destructed. |
172 | ASSERT(element.reactionQueue()); |
173 | auto& queue = *element.reactionQueue(); |
174 | if (!queue.m_interface->hasDisconnectedCallback()) |
175 | return; |
176 | queue.m_items.append({CustomElementReactionQueueItem::Type::Disconnected}); |
177 | enqueueElementOnAppropriateElementQueue(element); |
178 | } |
179 | |
180 | void CustomElementReactionQueue::enqueueAdoptedCallbackIfNeeded(Element& element, Document& oldDocument, Document& newDocument) |
181 | { |
182 | ASSERT(CustomElementReactionDisallowedScope::isReactionAllowed()); |
183 | ASSERT(element.isDefinedCustomElement()); |
184 | ASSERT(element.document().refCount() > 0); |
185 | ASSERT(element.reactionQueue()); |
186 | auto& queue = *element.reactionQueue(); |
187 | if (!queue.m_interface->hasAdoptedCallback()) |
188 | return; |
189 | queue.m_items.append({oldDocument, newDocument}); |
190 | enqueueElementOnAppropriateElementQueue(element); |
191 | } |
192 | |
193 | void CustomElementReactionQueue::enqueueAttributeChangedCallbackIfNeeded(Element& element, const QualifiedName& attributeName, const AtomicString& oldValue, const AtomicString& newValue) |
194 | { |
195 | ASSERT(CustomElementReactionDisallowedScope::isReactionAllowed()); |
196 | ASSERT(element.isDefinedCustomElement()); |
197 | ASSERT(element.document().refCount() > 0); |
198 | ASSERT(element.reactionQueue()); |
199 | auto& queue = *element.reactionQueue(); |
200 | if (!queue.m_interface->observesAttribute(attributeName.localName())) |
201 | return; |
202 | queue.m_items.append({attributeName, oldValue, newValue}); |
203 | enqueueElementOnAppropriateElementQueue(element); |
204 | } |
205 | |
206 | void CustomElementReactionQueue::enqueuePostUpgradeReactions(Element& element) |
207 | { |
208 | ASSERT(CustomElementReactionDisallowedScope::isReactionAllowed()); |
209 | ASSERT(element.isCustomElementUpgradeCandidate()); |
210 | if (!element.hasAttributes() && !element.isConnected()) |
211 | return; |
212 | |
213 | ASSERT(element.reactionQueue()); |
214 | auto& queue = *element.reactionQueue(); |
215 | |
216 | if (element.hasAttributes()) { |
217 | for (auto& attribute : element.attributesIterator()) { |
218 | if (queue.m_interface->observesAttribute(attribute.localName())) |
219 | queue.m_items.append({attribute.name(), nullAtom(), attribute.value()}); |
220 | } |
221 | } |
222 | |
223 | if (element.isConnected() && queue.m_interface->hasConnectedCallback()) |
224 | queue.m_items.append({CustomElementReactionQueueItem::Type::Connected}); |
225 | } |
226 | |
227 | bool CustomElementReactionQueue::observesStyleAttribute() const |
228 | { |
229 | return m_interface->observesAttribute(HTMLNames::styleAttr->localName()); |
230 | } |
231 | |
232 | void CustomElementReactionQueue::invokeAll(Element& element) |
233 | { |
234 | while (!m_items.isEmpty()) { |
235 | Vector<CustomElementReactionQueueItem> items = WTFMove(m_items); |
236 | for (auto& item : items) |
237 | item.invoke(element, m_interface.get()); |
238 | } |
239 | } |
240 | |
241 | inline void CustomElementReactionQueue::ElementQueue::add(Element& element) |
242 | { |
243 | ASSERT(!m_invoking); |
244 | // FIXME: Avoid inserting the same element multiple times. |
245 | m_elements.append(element); |
246 | } |
247 | |
248 | inline void CustomElementReactionQueue::ElementQueue::invokeAll() |
249 | { |
250 | RELEASE_ASSERT(!m_invoking); |
251 | SetForScope<bool> invoking(m_invoking, true); |
252 | unsigned originalSize = m_elements.size(); |
253 | // It's possible for more elements to be enqueued if some IDL attributes were missing CEReactions. |
254 | // Invoke callbacks slightly later here instead of crashing / ignoring those cases. |
255 | for (unsigned i = 0; i < m_elements.size(); ++i) { |
256 | auto& element = m_elements[i].get(); |
257 | auto* queue = element.reactionQueue(); |
258 | ASSERT(queue); |
259 | queue->invokeAll(element); |
260 | } |
261 | ASSERT_UNUSED(originalSize, m_elements.size() == originalSize); |
262 | m_elements.clear(); |
263 | } |
264 | |
265 | inline void CustomElementReactionQueue::ElementQueue::processQueue(JSC::ExecState* state) |
266 | { |
267 | if (!state) { |
268 | invokeAll(); |
269 | return; |
270 | } |
271 | |
272 | auto& vm = state->vm(); |
273 | JSC::JSLockHolder lock(vm); |
274 | |
275 | JSC::Exception* previousException = nullptr; |
276 | { |
277 | auto catchScope = DECLARE_CATCH_SCOPE(vm); |
278 | previousException = catchScope.exception(); |
279 | if (previousException) |
280 | catchScope.clearException(); |
281 | } |
282 | |
283 | invokeAll(); |
284 | |
285 | if (previousException) { |
286 | auto throwScope = DECLARE_THROW_SCOPE(vm); |
287 | throwException(state, throwScope, previousException); |
288 | } |
289 | } |
290 | |
291 | // https://html.spec.whatwg.org/multipage/custom-elements.html#enqueue-an-element-on-the-appropriate-element-queue |
292 | void CustomElementReactionQueue::enqueueElementOnAppropriateElementQueue(Element& element) |
293 | { |
294 | ASSERT(element.reactionQueue()); |
295 | if (!CustomElementReactionStack::s_currentProcessingStack) { |
296 | auto& queue = ensureBackupQueue(); |
297 | queue.add(element); |
298 | return; |
299 | } |
300 | |
301 | auto*& queue = CustomElementReactionStack::s_currentProcessingStack->m_queue; |
302 | if (!queue) // We use a raw pointer to avoid genearing code to delete it in ~CustomElementReactionStack. |
303 | queue = new ElementQueue; |
304 | queue->add(element); |
305 | } |
306 | |
307 | #if !ASSERT_DISABLED |
308 | unsigned CustomElementReactionDisallowedScope::s_customElementReactionDisallowedCount = 0; |
309 | #endif |
310 | |
311 | CustomElementReactionStack* CustomElementReactionStack::s_currentProcessingStack = nullptr; |
312 | |
313 | void CustomElementReactionStack::processQueue(JSC::ExecState* state) |
314 | { |
315 | ASSERT(m_queue); |
316 | m_queue->processQueue(state); |
317 | delete m_queue; |
318 | m_queue = nullptr; |
319 | } |
320 | |
321 | class BackupElementQueueMicrotask final : public Microtask { |
322 | WTF_MAKE_FAST_ALLOCATED; |
323 | private: |
324 | Result run() final |
325 | { |
326 | CustomElementReactionQueue::processBackupQueue(); |
327 | return Result::Done; |
328 | } |
329 | }; |
330 | |
331 | static bool s_processingBackupElementQueue = false; |
332 | |
333 | CustomElementReactionQueue::ElementQueue& CustomElementReactionQueue::ensureBackupQueue() |
334 | { |
335 | if (!s_processingBackupElementQueue) { |
336 | s_processingBackupElementQueue = true; |
337 | MicrotaskQueue::mainThreadQueue().append(std::make_unique<BackupElementQueueMicrotask>()); |
338 | } |
339 | return backupElementQueue(); |
340 | } |
341 | |
342 | void CustomElementReactionQueue::processBackupQueue() |
343 | { |
344 | backupElementQueue().processQueue(nullptr); |
345 | s_processingBackupElementQueue = false; |
346 | } |
347 | |
348 | CustomElementReactionQueue::ElementQueue& CustomElementReactionQueue::backupElementQueue() |
349 | { |
350 | static NeverDestroyed<ElementQueue> queue; |
351 | return queue.get(); |
352 | } |
353 | |
354 | } |
355 | |