Bug 1421544 - Lazy push/pop CustomElementReactionsStack entry;
MozReview-Commit-ID: CeSNbp8uCp
--- a/dom/base/CustomElementRegistry.cpp
+++ b/dom/base/CustomElementRegistry.cpp
@@ -967,28 +967,34 @@ CustomElementRegistry::Upgrade(Element*
}
//-----------------------------------------------------
// CustomElementReactionsStack
void
CustomElementReactionsStack::CreateAndPushElementQueue()
{
+ MOZ_ASSERT(mRecursionDepth);
+ MOZ_ASSERT(!mIsElementQueuePushedForCurrentRecursionDepth);
+
// Push a new element queue onto the custom element reactions stack.
mReactionsStack.AppendElement(MakeUnique<ElementQueue>());
+ mIsElementQueuePushedForCurrentRecursionDepth = true;
}
void
CustomElementReactionsStack::PopAndInvokeElementQueue()
{
- // Pop the element queue from the custom element reactions stack,
- // and invoke custom element reactions in that queue.
+ MOZ_ASSERT(mRecursionDepth);
+ MOZ_ASSERT(mIsElementQueuePushedForCurrentRecursionDepth);
MOZ_ASSERT(!mReactionsStack.IsEmpty(),
"Reaction stack shouldn't be empty");
+ // Pop the element queue from the custom element reactions stack,
+ // and invoke custom element reactions in that queue.
const uint32_t lastIndex = mReactionsStack.Length() - 1;
ElementQueue* elementQueue = mReactionsStack.ElementAt(lastIndex).get();
// Check element queue size in order to reduce function call overhead.
if (!elementQueue->IsEmpty()) {
// It is still not clear what error reporting will look like in custom
// element, see https://github.com/w3c/webcomponents/issues/635.
// We usually report the error to entry global in gecko, so just follow the
// same behavior here.
@@ -1002,16 +1008,17 @@ CustomElementReactionsStack::PopAndInvok
}
// InvokeReactions() might create other custom element reactions, but those
// new reactions should be already consumed and removed at this point.
MOZ_ASSERT(lastIndex == mReactionsStack.Length() - 1,
"reactions created by InvokeReactions() should be consumed and removed");
mReactionsStack.RemoveElementAt(lastIndex);
+ mIsElementQueuePushedForCurrentRecursionDepth = false;
}
void
CustomElementReactionsStack::EnqueueUpgradeReaction(Element* aElement,
CustomElementDefinition* aDefinition)
{
Enqueue(aElement, new CustomElementUpgradeReaction(aDefinition));
}
@@ -1025,25 +1032,34 @@ CustomElementReactionsStack::EnqueueCall
void
CustomElementReactionsStack::Enqueue(Element* aElement,
CustomElementReaction* aReaction)
{
RefPtr<CustomElementData> elementData = aElement->GetCustomElementData();
MOZ_ASSERT(elementData, "CustomElementData should exist");
- // Add element to the current element queue.
- if (!mReactionsStack.IsEmpty()) {
+ if (mRecursionDepth) {
+ // If the element queue is not created for current recursion depth, create
+ // and push an element queue to reactions stack first.
+ if (!mIsElementQueuePushedForCurrentRecursionDepth) {
+ CreateAndPushElementQueue();
+ }
+
+ MOZ_ASSERT(!mReactionsStack.IsEmpty());
+ // Add element to the current element queue.
mReactionsStack.LastElement()->AppendElement(aElement);
elementData->mReactionQueue.AppendElement(aReaction);
return;
}
// If the custom element reactions stack is empty, then:
// Add element to the backup element queue.
+ MOZ_ASSERT(mReactionsStack.IsEmpty(),
+ "custom element reactions stack should be empty");
MOZ_ASSERT(!aReaction->IsUpgradeReaction(),
"Upgrade reaction should not be scheduled to backup queue");
mBackupQueue.AppendElement(aElement);
elementData->mReactionQueue.AppendElement(aReaction);
if (mIsBackupQueueProcessing) {
return;
}
--- a/dom/base/CustomElementRegistry.h
+++ b/dom/base/CustomElementRegistry.h
@@ -212,16 +212,18 @@ protected:
// https://html.spec.whatwg.org/multipage/scripting.html#custom-element-reactions-stack
class CustomElementReactionsStack
{
public:
NS_INLINE_DECL_REFCOUNTING(CustomElementReactionsStack)
CustomElementReactionsStack()
: mIsBackupQueueProcessing(false)
+ , mRecursionDepth(0)
+ , mIsElementQueuePushedForCurrentRecursionDepth(false)
{
}
// Hold a strong reference of Element so that it does not get cycle collected
// before the reactions in its reaction queue are invoked.
// The element reaction queues are stored in CustomElementData.
// We need to lookup ElementReactionQueueMap again to get relevant reaction queue.
// The choice of 1 for the auto size here is based on gut feeling.
@@ -236,44 +238,100 @@ public:
/**
* Enqueue a custom element callback reaction
* https://html.spec.whatwg.org/multipage/scripting.html#enqueue-a-custom-element-callback-reaction
*/
void EnqueueCallbackReaction(Element* aElement,
UniquePtr<CustomElementCallback> aCustomElementCallback);
- // [CEReactions] Before executing the algorithm's steps
- // Push a new element queue onto the custom element reactions stack.
- void CreateAndPushElementQueue();
+ /**
+ * [CEReactions] Before executing the algorithm's steps.
+ * Increase the current recursion depth, and the element queue is pushed
+ * lazily when we really enqueue reactions.
+ *
+ * @return true if the element queue is pushed for "previous" recursion depth.
+ */
+ bool EnterCEReactions()
+ {
+ bool temp = mIsElementQueuePushedForCurrentRecursionDepth;
+ mRecursionDepth++;
+ // The is-element-queue-pushed flag is initially false when entering a new
+ // recursion level. The original value will be cached in AutoCEReaction
+ // and restored after leaving this recursion level.
+ mIsElementQueuePushedForCurrentRecursionDepth = false;
+ return temp;
+ }
- // [CEReactions] After executing the algorithm's steps
- // Pop the element queue from the custom element reactions stack,
- // and invoke custom element reactions in that queue.
- void PopAndInvokeElementQueue();
+ /**
+ * [CEReactions] After executing the algorithm's steps.
+ * Pop and invoke the element queue if it is created and pushed for current
+ * recursion depth, then decrease the current recursion depth.
+ *
+ * @param aCx JSContext used for handling exception thrown by algorithm's
+ * steps, this could be a nullptr.
+ * aWasElementQueuePushed used for restoring status after leaving
+ * current recursion.
+ */
+ void LeaveCEReactions(JSContext* aCx, bool aWasElementQueuePushed)
+ {
+ MOZ_ASSERT(mRecursionDepth);
+
+ if (mIsElementQueuePushedForCurrentRecursionDepth) {
+ Maybe<JS::AutoSaveExceptionState> ases;
+ if (aCx) {
+ ases.emplace(aCx);
+ }
+ PopAndInvokeElementQueue();
+ }
+ mRecursionDepth--;
+ // Restore the is-element-queue-pushed flag cached in AutoCEReaction when
+ // leaving the recursion level.
+ mIsElementQueuePushedForCurrentRecursionDepth = aWasElementQueuePushed;
+
+ MOZ_ASSERT_IF(!mRecursionDepth, mReactionsStack.IsEmpty());
+ }
private:
~CustomElementReactionsStack() {};
+ /**
+ * Push a new element queue onto the custom element reactions stack.
+ */
+ void CreateAndPushElementQueue();
+
+ /**
+ * Pop the element queue from the custom element reactions stack, and invoke
+ * custom element reactions in that queue.
+ */
+ void PopAndInvokeElementQueue();
+
// The choice of 8 for the auto size here is based on gut feeling.
AutoTArray<UniquePtr<ElementQueue>, 8> mReactionsStack;
ElementQueue mBackupQueue;
// https://html.spec.whatwg.org/#enqueue-an-element-on-the-appropriate-element-queue
bool mIsBackupQueueProcessing;
void InvokeBackupQueue();
/**
* Invoke custom element reactions
* https://html.spec.whatwg.org/multipage/scripting.html#invoke-custom-element-reactions
*/
void InvokeReactions(ElementQueue* aElementQueue, nsIGlobalObject* aGlobal);
void Enqueue(Element* aElement, CustomElementReaction* aReaction);
+ // Current [CEReactions] recursion depth.
+ uint32_t mRecursionDepth;
+ // True if the element queue is pushed into reaction stack for current
+ // recursion depth. This will be cached in AutoCEReaction when entering a new
+ // CEReaction recursion and restored after leaving the recursion.
+ bool mIsElementQueuePushedForCurrentRecursionDepth;
+
private:
class BackupQueueMicroTask final : public mozilla::MicroTaskRunnable {
public:
explicit BackupQueueMicroTask(
CustomElementReactionsStack* aReactionStack)
: MicroTaskRunnable()
, mReactionStack(aReactionStack)
{
@@ -431,28 +489,31 @@ public:
class MOZ_RAII AutoCEReaction final {
public:
// JSContext is allowed to be a nullptr if we are guaranteeing that we're
// not doing something that might throw but not finish reporting a JS
// exception during the lifetime of the AutoCEReaction.
AutoCEReaction(CustomElementReactionsStack* aReactionsStack, JSContext* aCx)
: mReactionsStack(aReactionsStack)
- , mCx(aCx) {
- mReactionsStack->CreateAndPushElementQueue();
+ , mCx(aCx)
+ {
+ mIsElementQueuePushedForPreviousRecursionDepth =
+ mReactionsStack->EnterCEReactions();
}
- ~AutoCEReaction() {
- Maybe<JS::AutoSaveExceptionState> ases;
- if (mCx) {
- ases.emplace(mCx);
- }
- mReactionsStack->PopAndInvokeElementQueue();
+
+ ~AutoCEReaction()
+ {
+ mReactionsStack->LeaveCEReactions(
+ mCx, mIsElementQueuePushedForPreviousRecursionDepth);
}
+
private:
RefPtr<CustomElementReactionsStack> mReactionsStack;
JSContext* mCx;
+ bool mIsElementQueuePushedForPreviousRecursionDepth;
};
} // namespace dom
} // namespace mozilla
#endif // mozilla_dom_CustomElementRegistry_h