Bug 1462703 - Upgrade the created element after callback runs draft
authorTimothy Guan-tin Chien <timdream@gmail.com>
Wed, 23 May 2018 13:04:18 +0800
changeset 800172 0c6d2b62c0237bfeb5e9d98c73b97dd611ff29a8
parent 799797 9140f8fa9e9727ce7b65c68d235bcb1a61c6382e
child 800173 b270d34d1765e553b327a74115fdca4f34695b08
push id111288
push usertimdream@gmail.com
push dateSat, 26 May 2018 01:57:00 +0000
bugs1462703
milestone62.0a1
Bug 1462703 - Upgrade the created element after callback runs nsContentUtils::NS_NewXULOrHTMLElement will call into CustomElementRegisty::RegisterCallbackUpgradeElement, which keeps the newly created element, allowing RunCustomElementCreationCallback to upgrade them after the callback runs. It is unclear if this changes the order of constructor executions, but even so it should not affact our use case. MozReview-Commit-ID: LWTn7B35aBv
dom/base/CustomElementRegistry.cpp
dom/base/CustomElementRegistry.h
dom/base/nsContentUtils.cpp
dom/base/nsContentUtils.h
dom/tests/mochitest/webcomponents/test_custom_element_set_element_creation_callback.html
--- a/dom/base/CustomElementRegistry.cpp
+++ b/dom/base/CustomElementRegistry.cpp
@@ -307,35 +307,52 @@ NS_IMETHODIMP
 CustomElementRegistry::RunCustomElementCreationCallback::Run()
 {
   ErrorResult er;
   nsDependentAtomString value(mAtom);
   mCallback->Call(value, er);
   MOZ_ASSERT(NS_SUCCEEDED(er.StealNSResult()),
     "chrome JavaScript error in the callback.");
 
-  MOZ_ASSERT(mRegistry->mCustomDefinitions.GetWeak(mAtom),
-    "Callback should define the definition of type.");
+  CustomElementDefinition* definition =
+    mRegistry->mCustomDefinitions.GetWeak(mAtom);
+  MOZ_ASSERT(definition, "Callback should define the definition of type.");
   MOZ_ASSERT(!mRegistry->mElementCreationCallbacks.GetWeak(mAtom),
     "Callback should be removed.");
 
+  nsAutoPtr<nsTHashtable<nsRefPtrHashKey<nsIWeakReference>>> elements;
+  mRegistry->mElementCreationCallbacksUpgradeCandidatesMap.Remove(mAtom, &elements);
+  MOZ_ASSERT(elements, "There should be a list");
+
+  for (auto iter = elements->Iter(); !iter.Done(); iter.Next()) {
+    nsCOMPtr<Element> elem = do_QueryReferent(iter.Get()->GetKey());
+    if (!elem) {
+      continue;
+    }
+
+    CustomElementRegistry::Upgrade(elem, definition, er);
+    MOZ_ASSERT(NS_SUCCEEDED(er.StealNSResult()),
+      "chrome JavaScript error in custom element construction.");
+  }
+
   return NS_OK;
 }
 
 CustomElementDefinition*
 CustomElementRegistry::LookupCustomElementDefinition(nsAtom* aNameAtom,
                                                      nsAtom* aTypeAtom)
 {
   CustomElementDefinition* data = mCustomDefinitions.GetWeak(aTypeAtom);
 
   if (!data) {
     RefPtr<CustomElementCreationCallback> callback;
     mElementCreationCallbacks.Get(aTypeAtom, getter_AddRefs(callback));
     if (callback) {
       mElementCreationCallbacks.Remove(aTypeAtom);
+      mElementCreationCallbacksUpgradeCandidatesMap.LookupOrAdd(aTypeAtom);
       RefPtr<Runnable> runnable =
         new RunCustomElementCreationCallback(this, aTypeAtom, callback);
       nsContentUtils::AddScriptRunner(runnable);
       data = mCustomDefinitions.GetWeak(aTypeAtom);
     }
   }
 
   if (data && data->mLocalName == aNameAtom) {
--- a/dom/base/CustomElementRegistry.h
+++ b/dom/base/CustomElementRegistry.h
@@ -371,27 +371,23 @@ private:
   class RunCustomElementCreationCallback : public mozilla::Runnable
   {
   public:
     NS_DECL_NSIRUNNABLE
     explicit RunCustomElementCreationCallback(CustomElementRegistry* aRegistry,
                                               nsAtom* aAtom,
                                               CustomElementCreationCallback* aCallback)
       : mozilla::Runnable("CustomElementRegistry::RunCustomElementCreationCallback")
-#ifdef DEBUG
       , mRegistry(aRegistry)
-#endif
       , mAtom(aAtom)
       , mCallback(aCallback)
     {
     }
     private:
-#ifdef DEBUG
       RefPtr<CustomElementRegistry> mRegistry;
-#endif
       RefPtr<nsAtom> mAtom;
       RefPtr<CustomElementCreationCallback> mCallback;
   };
 
 public:
   /**
    * Looking up a custom element definition.
    * https://html.spec.whatwg.org/#look-up-a-custom-element-definition
@@ -425,16 +421,48 @@ public:
                                  nsAtom* aTypeName = nullptr);
 
   /**
    * Unregister an unresolved custom element that is a candidate for
    * upgrade when a custom element is removed from tree.
    */
   void UnregisterUnresolvedElement(Element* aElement,
                                    nsAtom* aTypeName = nullptr);
+
+  /**
+   * Register an element to be upgraded when the custom element creation
+   * callback is executed.
+   *
+   * To be used when LookupCustomElementDefinition() didn't return a definition,
+   * but with the callback scheduled to be run.
+   */
+  inline void RegisterCallbackUpgradeElement(Element* aElement,
+                                             nsAtom* aTypeName = nullptr)
+  {
+    if (mElementCreationCallbacksUpgradeCandidatesMap.IsEmpty()) {
+      return;
+    }
+
+    RefPtr<nsAtom> typeName = aTypeName;
+    if (!typeName) {
+      typeName = aElement->NodeInfo()->NameAtom();
+    }
+
+    nsTHashtable<nsRefPtrHashKey<nsIWeakReference>>* elements =
+      mElementCreationCallbacksUpgradeCandidatesMap.Get(typeName);
+
+    // If there isn't a table, there won't be a definition added by the callback.
+    if (!elements) {
+      return;
+    }
+
+    nsWeakPtr elem = do_GetWeakReference(aElement);
+    elements->PutEntry(elem);
+  }
+
 private:
   ~CustomElementRegistry();
 
   static UniquePtr<CustomElementCallback> CreateCustomElementCallback(
     nsIDocument::ElementCallbackType aType, Element* aCustomElement,
     LifecycleCallbackArgs* aArgs,
     LifecycleAdoptedCallbackArgs* aAdoptedCallbackArgs,
     CustomElementDefinition* aDefinition);
@@ -474,16 +502,20 @@ private:
     WhenDefinedPromiseMap;
   WhenDefinedPromiseMap mWhenDefinedPromiseMap;
 
   // The "upgrade candidates map" from the web components spec. Maps from a
   // namespace id and local name to a list of elements to upgrade if that
   // element is registered as a custom element.
   CandidateMap mCandidatesMap;
 
+  // If an element creation callback is found, the nsTHashtable for the
+  // type is created here, and elements will later be upgraded.
+  CandidateMap mElementCreationCallbacksUpgradeCandidatesMap;
+
   nsCOMPtr<nsPIDOMWindowInner> mWindow;
 
   // It is used to prevent reentrant invocations of element definition.
   bool mIsCustomDefinitionRunning;
 
 private:
   class MOZ_RAII AutoSetRunningFlag final {
     public:
--- a/dom/base/nsContentUtils.cpp
+++ b/dom/base/nsContentUtils.cpp
@@ -10034,86 +10034,95 @@ nsContentUtils::NewXULOrHTMLElement(Elem
   }
 
   if (!*aResult) {
     return NS_ERROR_OUT_OF_MEMORY;
   }
 
   if (customElementEnabled && isCustomElement) {
     (*aResult)->SetCustomElementData(new CustomElementData(typeAtom));
+    nsContentUtils::RegisterCallbackUpgradeElement(*aResult, typeAtom);
   }
 
   return NS_OK;
 }
 
+CustomElementRegistry*
+GetCustomElementRegistry(nsIDocument* aDoc)
+{
+  MOZ_ASSERT(aDoc);
+
+  if (!aDoc->GetDocShell()) {
+    return nullptr;
+  }
+
+  nsPIDOMWindowInner* window = aDoc->GetInnerWindow();
+  if (!window) {
+    return nullptr;
+  }
+
+  return window->CustomElements();
+}
+
 /* static */ CustomElementDefinition*
 nsContentUtils::LookupCustomElementDefinition(nsIDocument* aDoc,
                                               nsAtom* aNameAtom,
                                               uint32_t aNameSpaceID,
                                               nsAtom* aTypeAtom)
 {
-  MOZ_ASSERT(aDoc);
-
-  if ((aNameSpaceID != kNameSpaceID_XUL &&
-       aNameSpaceID != kNameSpaceID_XHTML) ||
-      !aDoc->GetDocShell()) {
+  if (aNameSpaceID != kNameSpaceID_XUL &&
+      aNameSpaceID != kNameSpaceID_XHTML) {
     return nullptr;
   }
 
-  nsPIDOMWindowInner* window = aDoc->GetInnerWindow();
-  if (!window) {
-    return nullptr;
-  }
-
-  RefPtr<CustomElementRegistry> registry(window->CustomElements());
+  RefPtr<CustomElementRegistry> registry(GetCustomElementRegistry(aDoc));
   if (!registry) {
     return nullptr;
   }
 
   return registry->LookupCustomElementDefinition(aNameAtom, aTypeAtom);
 }
 
 /* static */ void
+nsContentUtils::RegisterCallbackUpgradeElement(Element* aElement,
+                                               nsAtom* aTypeName)
+{
+  MOZ_ASSERT(aElement);
+
+  nsIDocument* doc = aElement->OwnerDoc();
+  CustomElementRegistry* registry = GetCustomElementRegistry(doc);
+  if (registry) {
+    registry->RegisterCallbackUpgradeElement(aElement, aTypeName);
+  }
+}
+
+/* static */ void
 nsContentUtils::RegisterUnresolvedElement(Element* aElement, nsAtom* aTypeName)
 {
   MOZ_ASSERT(aElement);
 
   nsIDocument* doc = aElement->OwnerDoc();
-  nsPIDOMWindowInner* window(doc->GetInnerWindow());
-  if (!window) {
-    return;
-  }
-
-  RefPtr<CustomElementRegistry> registry(window->CustomElements());
-  if (!registry) {
-    return;
-  }
-
-  registry->RegisterUnresolvedElement(aElement, aTypeName);
+  CustomElementRegistry* registry = GetCustomElementRegistry(doc);
+  if (registry) {
+    registry->RegisterUnresolvedElement(aElement, aTypeName);
+  }
 }
 
 /* static */ void
 nsContentUtils::UnregisterUnresolvedElement(Element* aElement)
 {
   MOZ_ASSERT(aElement);
 
   nsAtom* typeAtom =
     aElement->GetCustomElementData()->GetCustomElementType();
   nsIDocument* doc = aElement->OwnerDoc();
-  nsPIDOMWindowInner* window(doc->GetInnerWindow());
-  if (!window) {
-    return;
-  }
-
-  CustomElementRegistry* registry = window->CustomElements();
-  if (!registry) {
-    return;
-  }
-
-  registry->UnregisterUnresolvedElement(aElement, typeAtom);
+  CustomElementRegistry* registry = GetCustomElementRegistry(doc);
+  if (registry) {
+    registry->UnregisterUnresolvedElement(aElement, typeAtom);
+  }
 }
 
 /* static */ void
 nsContentUtils::EnqueueUpgradeReaction(Element* aElement,
                                        CustomElementDefinition* aDefinition)
 {
   MOZ_ASSERT(aElement);
 
--- a/dom/base/nsContentUtils.h
+++ b/dom/base/nsContentUtils.h
@@ -3036,16 +3036,19 @@ public:
    * https://html.spec.whatwg.org/#look-up-a-custom-element-definition
    */
   static mozilla::dom::CustomElementDefinition*
     LookupCustomElementDefinition(nsIDocument* aDoc,
                                   nsAtom* aNameAtom,
                                   uint32_t aNameSpaceID,
                                   nsAtom* aTypeAtom);
 
+  static void RegisterCallbackUpgradeElement(Element* aElement,
+                                             nsAtom* aTypeName);
+
   static void RegisterUnresolvedElement(Element* aElement, nsAtom* aTypeName);
   static void UnregisterUnresolvedElement(Element* aElement);
 
   static void EnqueueUpgradeReaction(Element* aElement,
                                      mozilla::dom::CustomElementDefinition* aDefinition);
 
   static void EnqueueLifecycleCallback(nsIDocument::ElementCallbackType aType,
                                        Element* aCustomElement,
--- a/dom/tests/mochitest/webcomponents/test_custom_element_set_element_creation_callback.html
+++ b/dom/tests/mochitest/webcomponents/test_custom_element_set_element_creation_callback.html
@@ -112,18 +112,16 @@ function simpleInnerHTMLTest() {
     is(type, "x-html-obj-elem5", "Type is passed to the callback.");
     customElements.define("x-html-obj-elem5", XObjElement4);
   });
   ok(!callbackCalled, "Callback should not be called.");
   let p = document.createElement("p");
   p.innerHTML = "<x-html-obj-elem5></x-html-obj-elem5>";
   let el = p.firstChild;
   ok(callbackCalled, "Callback should be called.");
-  isnot(Object.getPrototypeOf(el), XObjElement4.prototype, "Created element is not upgraded.");
-  document.body.appendChild(p);
   is(Object.getPrototypeOf(el), XObjElement4.prototype, "Created element should have the prototype of the custom type.");
 }
 
 function twoElementInnerHTMLTest() {
   let callbackCalled = false;
   class XObjElement5 extends HTMLElement {};
   registry.setElementCreationCallback("x-html-obj-elem6", (type) => {
     if (callbackCalled) {
@@ -135,19 +133,16 @@ function twoElementInnerHTMLTest() {
   });
   ok(!callbackCalled, "Callback should not be called.");
   let p = document.createElement("p");
   p.innerHTML =
     "<x-html-obj-elem6></x-html-obj-elem6><x-html-obj-elem6></x-html-obj-elem6>";
   let el1 = p.firstChild;
   let el2 = p.lastChild;
   ok(callbackCalled, "Callback should be called.");
-  isnot(Object.getPrototypeOf(el1), XObjElement5.prototype, "Created element is not upgraded.");
-  isnot(Object.getPrototypeOf(el2), XObjElement5.prototype, "Created element is not upgraded.");
-  document.body.appendChild(p);
   is(Object.getPrototypeOf(el1), XObjElement5.prototype, "Created element should have the prototype of the custom type.");
   is(Object.getPrototypeOf(el2), XObjElement5.prototype, "Created element should have the prototype of the custom type.");
 }
 
 function startTest() {
   simpleTest();
   multipleDefinitionTest();
   throwIfDefined();