Improve use of NeverDestroyed
[WebKit-https.git] / Source / WebCore / dom / CustomElementReactionQueue.cpp
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 <heap/Heap.h>
38 #include <wtf/NeverDestroyed.h>
39 #include <wtf/Optional.h>
40 #include <wtf/Ref.h>
41 #include <wtf/SetForScope.h>
42
43 namespace WebCore {
44
45 class CustomElementReactionQueueItem {
46 public:
47     enum class Type {
48         ElementUpgrade,
49         Connected,
50         Disconnected,
51         Adopted,
52         AttributeChanged,
53     };
54
55     CustomElementReactionQueueItem(Type type)
56         : m_type(type)
57     { }
58
59     CustomElementReactionQueueItem(Document& oldDocument, Document& newDocument)
60         : m_type(Type::Adopted)
61         , m_oldDocument(&oldDocument)
62         , m_newDocument(&newDocument)
63     { }
64
65     CustomElementReactionQueueItem(const QualifiedName& attributeName, const AtomicString& oldValue, const AtomicString& newValue)
66         : m_type(Type::AttributeChanged)
67         , m_attributeName(attributeName)
68         , m_oldValue(oldValue)
69         , m_newValue(newValue)
70     { }
71
72     void invoke(Element& element, JSCustomElementInterface& elementInterface)
73     {
74         switch (m_type) {
75         case Type::ElementUpgrade:
76             elementInterface.upgradeElement(element);
77             break;
78         case Type::Connected:
79             elementInterface.invokeConnectedCallback(element);
80             break;
81         case Type::Disconnected:
82             elementInterface.invokeDisconnectedCallback(element);
83             break;
84         case Type::Adopted:
85             elementInterface.invokeAdoptedCallback(element, *m_oldDocument, *m_newDocument);
86             break;
87         case Type::AttributeChanged:
88             ASSERT(m_attributeName);
89             elementInterface.invokeAttributeChangedCallback(element, m_attributeName.value(), m_oldValue, m_newValue);
90             break;
91         }
92     }
93
94 private:
95     Type m_type;
96     RefPtr<Document> m_oldDocument;
97     RefPtr<Document> m_newDocument;
98     std::optional<QualifiedName> m_attributeName;
99     AtomicString m_oldValue;
100     AtomicString m_newValue;
101 };
102
103 CustomElementReactionQueue::CustomElementReactionQueue(JSCustomElementInterface& elementInterface)
104     : m_interface(elementInterface)
105 { }
106
107 CustomElementReactionQueue::~CustomElementReactionQueue()
108 {
109     ASSERT(m_items.isEmpty());
110 }
111
112 void CustomElementReactionQueue::clear()
113 {
114     m_items.clear();
115 }
116
117 void CustomElementReactionQueue::enqueueElementUpgrade(Element& element)
118 {
119     auto& queue = CustomElementReactionStack::ensureCurrentQueue(element);
120     queue.m_items.append({CustomElementReactionQueueItem::Type::ElementUpgrade});
121 }
122
123 void CustomElementReactionQueue::enqueueElementUpgradeIfDefined(Element& element)
124 {
125     ASSERT(element.isConnected());
126     ASSERT(element.isCustomElementUpgradeCandidate());
127     auto* window = element.document().domWindow();
128     if (!window)
129         return;
130
131     auto* registry = window->customElementRegistry();
132     if (!registry)
133         return;
134
135     auto* elementInterface = registry->findInterface(element);
136     if (!elementInterface)
137         return;
138
139     element.enqueueToUpgrade(*elementInterface);
140 }
141
142 void CustomElementReactionQueue::enqueueConnectedCallbackIfNeeded(Element& element)
143 {
144     ASSERT(element.isDefinedCustomElement());
145     ASSERT(element.document().refCount() > 0);
146     auto& queue = CustomElementReactionStack::ensureCurrentQueue(element);
147     if (queue.m_interface->hasConnectedCallback())
148         queue.m_items.append({CustomElementReactionQueueItem::Type::Connected});
149 }
150
151 void CustomElementReactionQueue::enqueueDisconnectedCallbackIfNeeded(Element& element)
152 {
153     ASSERT(element.isDefinedCustomElement());
154     if (element.document().refCount() <= 0)
155         return; // Don't enqueue disconnectedCallback if the entire document is getting destructed.
156     auto& queue = CustomElementReactionStack::ensureCurrentQueue(element);
157     if (queue.m_interface->hasDisconnectedCallback())
158         queue.m_items.append({CustomElementReactionQueueItem::Type::Disconnected});
159 }
160
161 void CustomElementReactionQueue::enqueueAdoptedCallbackIfNeeded(Element& element, Document& oldDocument, Document& newDocument)
162 {
163     ASSERT(element.isDefinedCustomElement());
164     ASSERT(element.document().refCount() > 0);
165     auto& queue = CustomElementReactionStack::ensureCurrentQueue(element);
166     if (queue.m_interface->hasAdoptedCallback())
167         queue.m_items.append({oldDocument, newDocument});
168 }
169
170 void CustomElementReactionQueue::enqueueAttributeChangedCallbackIfNeeded(Element& element, const QualifiedName& attributeName, const AtomicString& oldValue, const AtomicString& newValue)
171 {
172     ASSERT(element.isDefinedCustomElement());
173     ASSERT(element.document().refCount() > 0);
174     auto& queue = CustomElementReactionStack::ensureCurrentQueue(element);
175     if (queue.m_interface->observesAttribute(attributeName.localName()))
176         queue.m_items.append({attributeName, oldValue, newValue});
177 }
178
179 void CustomElementReactionQueue::enqueuePostUpgradeReactions(Element& element)
180 {
181     ASSERT(element.isCustomElementUpgradeCandidate());
182     if (!element.hasAttributes() && !element.isConnected())
183         return;
184
185     auto* queue = element.reactionQueue();
186     ASSERT(queue);
187
188     if (element.hasAttributes()) {
189         for (auto& attribute : element.attributesIterator()) {
190             if (queue->m_interface->observesAttribute(attribute.localName()))
191                 queue->m_items.append({attribute.name(), nullAtom(), attribute.value()});
192         }
193     }
194
195     if (element.isConnected() && queue->m_interface->hasConnectedCallback())
196         queue->m_items.append({CustomElementReactionQueueItem::Type::Connected});
197 }
198
199 bool CustomElementReactionQueue::observesStyleAttribute() const
200 {
201     return m_interface->observesAttribute(HTMLNames::styleAttr.localName());
202 }
203
204 void CustomElementReactionQueue::invokeAll(Element& element)
205 {
206     while (!m_items.isEmpty()) {
207         Vector<CustomElementReactionQueueItem> items = WTFMove(m_items);
208         for (auto& item : items)
209             item.invoke(element, m_interface.get());
210     }
211 }
212
213 inline void CustomElementReactionStack::ElementQueue::add(Element& element)
214 {
215     RELEASE_ASSERT(!m_invoking);
216     // FIXME: Avoid inserting the same element multiple times.
217     m_elements.append(element);
218 }
219
220 inline void CustomElementReactionStack::ElementQueue::invokeAll()
221 {
222     RELEASE_ASSERT(!m_invoking);
223     SetForScope<bool> invoking(m_invoking, true);
224     Vector<Ref<Element>> elements;
225     elements.swap(m_elements);
226     RELEASE_ASSERT(m_elements.isEmpty());
227     for (auto& element : elements) {
228         auto* queue = element->reactionQueue();
229         ASSERT(queue);
230         queue->invokeAll(element.get());
231     }
232     RELEASE_ASSERT(m_elements.isEmpty());
233 }
234
235 CustomElementReactionQueue& CustomElementReactionStack::ensureCurrentQueue(Element& element)
236 {
237     ASSERT(element.reactionQueue());
238     if (!s_currentProcessingStack) {
239         auto& queue = CustomElementReactionStack::ensureBackupQueue();
240         queue.add(element);
241         return *element.reactionQueue();
242     }
243
244     auto*& queue = s_currentProcessingStack->m_queue;
245     if (!queue) // We use a raw pointer to avoid genearing code to delete it in ~CustomElementReactionStack.
246         queue = new ElementQueue;
247     queue->add(element);
248     return *element.reactionQueue();
249 }
250
251 CustomElementReactionStack* CustomElementReactionStack::s_currentProcessingStack = nullptr;
252
253 void CustomElementReactionStack::processQueue()
254 {
255     ASSERT(m_queue);
256     m_queue->invokeAll();
257     delete m_queue;
258     m_queue = nullptr;
259 }
260
261 class BackupElementQueueMicrotask final : public Microtask {
262     WTF_MAKE_FAST_ALLOCATED;
263 private:
264     Result run() final
265     {
266         CustomElementReactionStack::processBackupQueue();
267         return Result::Done;
268     }
269 };
270
271 static bool s_processingBackupElementQueue = false;
272
273 CustomElementReactionStack::ElementQueue& CustomElementReactionStack::ensureBackupQueue()
274 {
275     if (!s_processingBackupElementQueue) {
276         s_processingBackupElementQueue = true;
277         MicrotaskQueue::mainThreadQueue().append(std::make_unique<BackupElementQueueMicrotask>());
278     }
279     return backupElementQueue();
280 }
281
282 void CustomElementReactionStack::processBackupQueue()
283 {
284     backupElementQueue().invokeAll();
285     s_processingBackupElementQueue = false;
286 }
287
288 CustomElementReactionStack::ElementQueue& CustomElementReactionStack::backupElementQueue()
289 {
290     static NeverDestroyed<ElementQueue> queue;
291     return queue.get();
292 }
293
294 }