diff --git a/dom/base/CustomElementRegistry.cpp b/dom/base/CustomElementRegistry.cpp index 21dbb168639c..4e7eef67a7e8 100644 --- a/dom/base/CustomElementRegistry.cpp +++ b/dom/base/CustomElementRegistry.cpp @@ -55,10 +55,6 @@ CustomElementCallback::Call() mArgs.name, mArgs.oldValue, mArgs.newValue, rv); break; } - - // If callbacks throw exceptions, it'll be handled and reported in - // Lifecycle*Callback::Call function. - rv.SuppressException(); } void @@ -92,12 +88,26 @@ CustomElementData::CustomElementData(nsIAtom* aType) CustomElementData::CustomElementData(nsIAtom* aType, State aState) : mType(aType) + , mCurrentCallback(-1) , mElementIsBeingCreated(false) , mCreatedCallbackInvoked(true) + , mAssociatedMicroTask(-1) , mState(aState) { } +void +CustomElementData::RunCallbackQueue() +{ + // Note: It's possible to re-enter this method. + while (static_cast(++mCurrentCallback) < mCallbackQueue.Length()) { + mCallbackQueue[mCurrentCallback]->Call(); + } + + mCallbackQueue.Clear(); + mCurrentCallback = -1; +} + //----------------------------------------------------- // CustomElementRegistry @@ -114,7 +124,7 @@ NS_IMPL_CYCLE_COLLECTION_UNLINK_END NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(CustomElementRegistry) for (auto iter = tmp->mCustomDefinitions.Iter(); !iter.Done(); iter.Next()) { - auto& callbacks = iter.UserData()->mCallbacks; + nsAutoPtr& callbacks = iter.UserData()->mCallbacks; if (callbacks->mAttributeChangedCallback.WasPassed()) { NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, @@ -178,6 +188,43 @@ CustomElementRegistry::IsCustomElementEnabled(JSContext* aCx, JSObject* aObject) nsContentUtils::IsWebComponentsEnabled(); } +/* static */ void +CustomElementRegistry::ProcessTopElementQueue() +{ + MOZ_ASSERT(nsContentUtils::IsSafeToRunScript()); + + nsTArray>& stack = *sProcessingStack; + uint32_t firstQueue = stack.LastIndexOf((CustomElementData*) nullptr); + + for (uint32_t i = firstQueue + 1; i < stack.Length(); ++i) { + // Callback queue may have already been processed in an earlier + // element queue or in an element queue that was popped + // off more recently. + if (stack[i]->mAssociatedMicroTask != -1) { + stack[i]->RunCallbackQueue(); + stack[i]->mAssociatedMicroTask = -1; + } + } + + // If this was actually the base element queue, don't bother trying to pop + // the first "queue" marker (sentinel). + if (firstQueue != 0) { + stack.SetLength(firstQueue); + } else { + // Don't pop sentinel for base element queue. + stack.SetLength(1); + } +} + +/* static */ void +CustomElementRegistry::XPCOMShutdown() +{ + sProcessingStack.reset(); +} + +/* static */ Maybe>> +CustomElementRegistry::sProcessingStack; + CustomElementRegistry::CustomElementRegistry(nsPIDOMWindowInner* aWindow) : mWindow(aWindow) , mIsCustomDefinitionRunning(false) @@ -187,6 +234,12 @@ CustomElementRegistry::CustomElementRegistry(nsPIDOMWindowInner* aWindow) MOZ_ALWAYS_TRUE(mConstructors.init()); mozilla::HoldJSObjects(this); + + if (!sProcessingStack) { + sProcessingStack.emplace(); + // Add the base queue sentinel to the processing stack. + sProcessingStack->AppendElement((CustomElementData*) nullptr); + } } CustomElementRegistry::~CustomElementRegistry() @@ -290,15 +343,14 @@ CustomElementRegistry::SetupCustomElement(Element* aElement, // Enqueuing the created callback will set the CustomElementData on the // element, causing prototype swizzling to occur in Element::WrapObject. - // We make it synchronously for createElement/createElementNS in order to - // pass tests. It'll be removed when we deprecate custom elements v0. - SyncInvokeReactions(nsIDocument::eCreated, aElement, definition); + EnqueueLifecycleCallback(nsIDocument::eCreated, aElement, nullptr, definition); } -UniquePtr -CustomElementRegistry::CreateCustomElementCallback( - nsIDocument::ElementCallbackType aType, Element* aCustomElement, - LifecycleCallbackArgs* aArgs, CustomElementDefinition* aDefinition) +void +CustomElementRegistry::EnqueueLifecycleCallback(nsIDocument::ElementCallbackType aType, + Element* aCustomElement, + LifecycleCallbackArgs* aArgs, + CustomElementDefinition* aDefinition) { RefPtr elementData = aCustomElement->GetCustomElementData(); MOZ_ASSERT(elementData, "CustomElementData should exist"); @@ -317,7 +369,7 @@ CustomElementRegistry::CreateCustomElementCallback( if (!definition || definition->mLocalName != info->NameAtom()) { // Trying to enqueue a callback for an element that is not // a custom element. We are done, nothing to do. - return nullptr; + return; } } @@ -351,7 +403,7 @@ CustomElementRegistry::CreateCustomElementCallback( // If there is no such callback, stop. if (!func) { - return nullptr; + return; } if (aType == nsIDocument::eCreated) { @@ -359,62 +411,55 @@ CustomElementRegistry::CreateCustomElementCallback( } else if (!elementData->mCreatedCallbackInvoked) { // Callbacks other than created callback must not be enqueued // until after the created callback has been invoked. - return nullptr; + return; } // Add CALLBACK to ELEMENT's callback queue. - auto callback = - MakeUnique(aCustomElement, aType, func, elementData); - + CustomElementCallback* callback = new CustomElementCallback(aCustomElement, + aType, + func, + elementData); + // Ownership of callback is taken by mCallbackQueue. + elementData->mCallbackQueue.AppendElement(callback); if (aArgs) { callback->SetArgs(*aArgs); } - return Move(callback); -} + if (!elementData->mElementIsBeingCreated) { + CustomElementData* lastData = + sProcessingStack->SafeLastElement(nullptr); -void -CustomElementRegistry::SyncInvokeReactions(nsIDocument::ElementCallbackType aType, - Element* aCustomElement, - CustomElementDefinition* aDefinition) -{ - auto callback = CreateCustomElementCallback(aType, aCustomElement, nullptr, - aDefinition); - if (!callback) { - return; + // A new element queue needs to be pushed if the queue at the + // top of the stack is associated with another microtask level. + bool shouldPushElementQueue = + (!lastData || lastData->mAssociatedMicroTask < + static_cast(nsContentUtils::MicroTaskLevel())); + + // Push a new element queue onto the processing stack when appropriate + // (when we enter a new microtask). + if (shouldPushElementQueue) { + // Push a sentinel value on the processing stack to mark the + // boundary between the element queues. + sProcessingStack->AppendElement((CustomElementData*) nullptr); + } + + sProcessingStack->AppendElement(elementData); + elementData->mAssociatedMicroTask = + static_cast(nsContentUtils::MicroTaskLevel()); + + // Add a script runner to pop and process the element queue at + // the top of the processing stack. + if (shouldPushElementQueue) { + // Lifecycle callbacks enqueued by user agent implementation + // should be invoked prior to returning control back to script. + // Create a script runner to process the top of the processing + // stack as soon as it is safe to run script. + nsCOMPtr runnable = NS_NewRunnableFunction( + "dom::CustomElementRegistry::EnqueueLifecycleCallback", + &CustomElementRegistry::ProcessTopElementQueue); + nsContentUtils::AddScriptRunner(runnable); + } } - - UniquePtr reaction(Move( - MakeUnique(this, aDefinition, - Move(callback)))); - - RefPtr runnable = - new SyncInvokeReactionRunnable(Move(reaction), aCustomElement); - - nsContentUtils::AddScriptRunner(runnable); -} - -void -CustomElementRegistry::EnqueueLifecycleCallback(nsIDocument::ElementCallbackType aType, - Element* aCustomElement, - LifecycleCallbackArgs* aArgs, - CustomElementDefinition* aDefinition) -{ - auto callback = - CreateCustomElementCallback(aType, aCustomElement, aArgs, aDefinition); - if (!callback) { - return; - } - - DocGroup* docGroup = mWindow->GetDocGroup(); - if (!docGroup) { - return; - } - - CustomElementReactionsStack* reactionsStack = - docGroup->CustomElementReactionsStack(); - reactionsStack->EnqueueCallbackReaction(this, aCustomElement, aDefinition, - Move(callback)); } void @@ -898,16 +943,6 @@ CustomElementReactionsStack::EnqueueUpgradeReaction(CustomElementRegistry* aRegi Enqueue(aElement, new CustomElementUpgradeReaction(aRegistry, aDefinition)); } -void -CustomElementReactionsStack::EnqueueCallbackReaction(CustomElementRegistry* aRegistry, - Element* aElement, - CustomElementDefinition* aDefinition, - UniquePtr aCustomElementCallback) -{ - Enqueue(aElement, new CustomElementCallbackReaction(aRegistry, aDefinition, - Move(aCustomElementCallback))); -} - void CustomElementReactionsStack::Enqueue(Element* aElement, CustomElementReaction* aReaction) @@ -949,7 +984,6 @@ CustomElementReactionsStack::InvokeBackupQueue() void CustomElementReactionsStack::InvokeReactions(ElementQueue& aElementQueue) { - // Note: It's possible to re-enter this method. for (uint32_t i = 0; i < aElementQueue.Length(); ++i) { nsCOMPtr element = do_QueryReferent(aElementQueue[i]); @@ -960,14 +994,10 @@ CustomElementReactionsStack::InvokeReactions(ElementQueue& aElementQueue) RefPtr elementData = element->GetCustomElementData(); MOZ_ASSERT(elementData, "CustomElementData should exist"); - auto& reactions = elementData->mReactionQueue; + nsTArray>& reactions = + elementData->mReactionQueue; for (uint32_t j = 0; j < reactions.Length(); ++j) { - // Transfer the ownership of the entry due to reentrant invocation of - // this funciton. The entry will be removed when bug 1379573 is landed. - auto reaction(Move(reactions.ElementAt(j))); - if (reaction) { - reaction->Invoke(element); - } + reactions.ElementAt(j)->Invoke(element); } reactions.Clear(); } @@ -1002,14 +1032,5 @@ CustomElementUpgradeReaction::Invoke(Element* aElement) mRegistry->Upgrade(aElement, mDefinition); } -//----------------------------------------------------- -// CustomElementCallbackReaction - -/* virtual */ void -CustomElementCallbackReaction::Invoke(Element* aElement) -{ - mCustomElementCallback->Call(); -} - } // namespace dom } // namespace mozilla diff --git a/dom/base/CustomElementRegistry.h b/dom/base/CustomElementRegistry.h index f39fa2dc7e19..6faf5c6463b8 100644 --- a/dom/base/CustomElementRegistry.h +++ b/dom/base/CustomElementRegistry.h @@ -85,14 +85,23 @@ struct CustomElementData explicit CustomElementData(nsIAtom* aType); CustomElementData(nsIAtom* aType, State aState); + // Objects in this array are transient and empty after each microtask + // checkpoint. + nsTArray> mCallbackQueue; // Custom element type, for