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