/* * Copyright (C) 2015, 2016 Apple Inc. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include "config.h" #include "CustomElementReactionQueue.h" #include "CustomElementRegistry.h" #include "DOMWindow.h" #include "Document.h" #include "Element.h" #include "HTMLNames.h" #include "JSCustomElementInterface.h" #include "JSDOMBinding.h" #include "Microtasks.h" #include #include #include #include namespace WebCore { class CustomElementReactionQueueItem { public: enum class Type { ElementUpgrade, Connected, Disconnected, Adopted, AttributeChanged, }; CustomElementReactionQueueItem(Type type) : m_type(type) { } CustomElementReactionQueueItem(Document& oldDocument, Document& newDocument) : m_type(Type::Adopted) , m_oldDocument(&oldDocument) , m_newDocument(&newDocument) { } CustomElementReactionQueueItem(const QualifiedName& attributeName, const AtomicString& oldValue, const AtomicString& newValue) : m_type(Type::AttributeChanged) , m_attributeName(attributeName) , m_oldValue(oldValue) , m_newValue(newValue) { } void invoke(Element& element, JSCustomElementInterface& elementInterface) { switch (m_type) { case Type::ElementUpgrade: elementInterface.upgradeElement(element); break; case Type::Connected: elementInterface.invokeConnectedCallback(element); break; case Type::Disconnected: elementInterface.invokeDisconnectedCallback(element); break; case Type::Adopted: elementInterface.invokeAdoptedCallback(element, *m_oldDocument, *m_newDocument); break; case Type::AttributeChanged: ASSERT(m_attributeName); elementInterface.invokeAttributeChangedCallback(element, m_attributeName.value(), m_oldValue, m_newValue); break; } } private: Type m_type; RefPtr m_oldDocument; RefPtr m_newDocument; std::optional m_attributeName; AtomicString m_oldValue; AtomicString m_newValue; }; CustomElementReactionQueue::CustomElementReactionQueue(JSCustomElementInterface& elementInterface) : m_interface(elementInterface) { } CustomElementReactionQueue::~CustomElementReactionQueue() { ASSERT(m_items.isEmpty()); } void CustomElementReactionQueue::clear() { m_items.clear(); } void CustomElementReactionQueue::enqueueElementUpgrade(Element& element) { auto& queue = CustomElementReactionStack::ensureCurrentQueue(element); queue.m_items.append({CustomElementReactionQueueItem::Type::ElementUpgrade}); } void CustomElementReactionQueue::enqueueElementUpgradeIfDefined(Element& element) { ASSERT(element.isConnected()); ASSERT(element.isCustomElementUpgradeCandidate()); auto* window = element.document().domWindow(); if (!window) return; auto* registry = window->customElementRegistry(); if (!registry) return; auto* elementInterface = registry->findInterface(element); if (!elementInterface) return; element.enqueueToUpgrade(*elementInterface); } void CustomElementReactionQueue::enqueueConnectedCallbackIfNeeded(Element& element) { ASSERT(element.isDefinedCustomElement()); ASSERT(element.document().refCount() > 0); auto& queue = CustomElementReactionStack::ensureCurrentQueue(element); if (queue.m_interface->hasConnectedCallback()) queue.m_items.append({CustomElementReactionQueueItem::Type::Connected}); } void CustomElementReactionQueue::enqueueDisconnectedCallbackIfNeeded(Element& element) { ASSERT(element.isDefinedCustomElement()); if (element.document().refCount() <= 0) return; // Don't enqueue disconnectedCallback if the entire document is getting destructed. auto& queue = CustomElementReactionStack::ensureCurrentQueue(element); if (queue.m_interface->hasDisconnectedCallback()) queue.m_items.append({CustomElementReactionQueueItem::Type::Disconnected}); } void CustomElementReactionQueue::enqueueAdoptedCallbackIfNeeded(Element& element, Document& oldDocument, Document& newDocument) { ASSERT(element.isDefinedCustomElement()); ASSERT(element.document().refCount() > 0); auto& queue = CustomElementReactionStack::ensureCurrentQueue(element); if (queue.m_interface->hasAdoptedCallback()) queue.m_items.append({oldDocument, newDocument}); } void CustomElementReactionQueue::enqueueAttributeChangedCallbackIfNeeded(Element& element, const QualifiedName& attributeName, const AtomicString& oldValue, const AtomicString& newValue) { ASSERT(element.isDefinedCustomElement()); ASSERT(element.document().refCount() > 0); auto& queue = CustomElementReactionStack::ensureCurrentQueue(element); if (queue.m_interface->observesAttribute(attributeName.localName())) queue.m_items.append({attributeName, oldValue, newValue}); } void CustomElementReactionQueue::enqueuePostUpgradeReactions(Element& element) { ASSERT(element.isCustomElementUpgradeCandidate()); if (!element.hasAttributes() && !element.isConnected()) return; auto* queue = element.reactionQueue(); ASSERT(queue); if (element.hasAttributes()) { for (auto& attribute : element.attributesIterator()) { if (queue->m_interface->observesAttribute(attribute.localName())) queue->m_items.append({attribute.name(), nullAtom, attribute.value()}); } } if (element.isConnected() && queue->m_interface->hasConnectedCallback()) queue->m_items.append({CustomElementReactionQueueItem::Type::Connected}); } bool CustomElementReactionQueue::observesStyleAttribute() const { return m_interface->observesAttribute(HTMLNames::styleAttr.localName()); } void CustomElementReactionQueue::invokeAll(Element& element) { while (!m_items.isEmpty()) { Vector items = WTFMove(m_items); for (auto& item : items) item.invoke(element, m_interface.get()); } } inline void CustomElementReactionStack::ElementQueue::add(Element& element) { RELEASE_ASSERT(!m_invoking); // FIXME: Avoid inserting the same element multiple times. m_elements.append(element); } inline void CustomElementReactionStack::ElementQueue::invokeAll() { RELEASE_ASSERT(!m_invoking); SetForScope invoking(m_invoking, true); Vector> elements; elements.swap(m_elements); RELEASE_ASSERT(m_elements.isEmpty()); for (auto& element : elements) { auto* queue = element->reactionQueue(); ASSERT(queue); queue->invokeAll(element.get()); } RELEASE_ASSERT(m_elements.isEmpty()); } CustomElementReactionQueue& CustomElementReactionStack::ensureCurrentQueue(Element& element) { ASSERT(element.reactionQueue()); if (!s_currentProcessingStack) { auto& queue = CustomElementReactionStack::ensureBackupQueue(); queue.add(element); return *element.reactionQueue(); } auto*& queue = s_currentProcessingStack->m_queue; if (!queue) // We use a raw pointer to avoid genearing code to delete it in ~CustomElementReactionStack. queue = new ElementQueue; queue->add(element); return *element.reactionQueue(); } CustomElementReactionStack* CustomElementReactionStack::s_currentProcessingStack = nullptr; void CustomElementReactionStack::processQueue() { ASSERT(m_queue); m_queue->invokeAll(); delete m_queue; m_queue = nullptr; } class BackupElementQueueMicrotask final : public Microtask { WTF_MAKE_FAST_ALLOCATED; private: Result run() final { CustomElementReactionStack::processBackupQueue(); return Result::Done; } }; static bool s_processingBackupElementQueue = false; CustomElementReactionStack::ElementQueue& CustomElementReactionStack::ensureBackupQueue() { if (!s_processingBackupElementQueue) { s_processingBackupElementQueue = true; MicrotaskQueue::mainThreadQueue().append(std::make_unique()); } return backupElementQueue(); } void CustomElementReactionStack::processBackupQueue() { backupElementQueue().invokeAll(); s_processingBackupElementQueue = false; } CustomElementReactionStack::ElementQueue& CustomElementReactionStack::backupElementQueue() { static NeverDestroyed queue; return queue.get(); } }