Bug 1421544 - Lazy push/pop CustomElementReactionsStack entry; draft
authorEdgar Chen <echen@mozilla.com>
Tue, 28 Nov 2017 16:58:50 +0800
changeset 710308 a186d83c0941f89addea1d949d9ad3b1a8247cef
parent 707931 f2b3bed28c503dd0b77330646d8cb3e1f0dbbf93
child 711031 d53b9469243c1d6c1eeb9e6e97c684ac616e0505
child 711592 a60883c49ed404100c7e33c6f6f66f6e8ec4679f
push id92814
push userechen@mozilla.com
push dateSat, 09 Dec 2017 14:22:20 +0000
bugs1421544
milestone59.0a1
Bug 1421544 - Lazy push/pop CustomElementReactionsStack entry; MozReview-Commit-ID: CeSNbp8uCp
dom/base/CustomElementRegistry.cpp
dom/base/CustomElementRegistry.h
--- 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