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