| 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 | |