Bug 1275835 - Part 1: Move custom element codes from nsDocument to CustomElementsRegistry; r=wchen
authorEdgar Chen <echen@mozilla.com>
Tue, 30 Aug 2016 11:48:53 +0800
changeset 312393 1f2f37ec0fd3bd0156c504ccc6aeb05e0f973dc0
parent 312392 5ad8a73c2dd13b39f46834cbc5c7ab72c091b852
child 312394 9cc407277fd0e820882a760676144cd07f7c92ec
push id20447
push userkwierso@gmail.com
push dateFri, 02 Sep 2016 20:36:44 +0000
treeherderfx-team@969397f22187 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerswchen
bugs1275835
milestone51.0a1
Bug 1275835 - Part 1: Move custom element codes from nsDocument to CustomElementsRegistry; r=wchen MozReview-Commit-ID: 9gTSFrYW7o3
dom/base/CustomElementsRegistry.cpp
dom/base/CustomElementsRegistry.h
dom/base/DOMImplementation.cpp
dom/base/Element.cpp
dom/base/Element.h
dom/base/nsContentUtils.cpp
dom/base/nsContentUtils.h
dom/base/nsDocument.cpp
dom/base/nsDocument.h
dom/base/nsIDocument.h
dom/base/nsNodeUtils.cpp
dom/html/nsHTMLContentSink.cpp
dom/tests/mochitest/webcomponents/test_document_shared_registry.html
layout/build/nsLayoutStatics.cpp
parser/html/nsHtml5TreeOperation.cpp
--- a/dom/base/CustomElementsRegistry.cpp
+++ b/dom/base/CustomElementsRegistry.cpp
@@ -1,26 +1,158 @@
 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* vim:set ts=2 sw=2 sts=2 et cindent: */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "mozilla/dom/CustomElementsRegistry.h"
+
 #include "mozilla/dom/CustomElementsRegistryBinding.h"
 #include "mozilla/dom/WebComponentsBinding.h"
 
 namespace mozilla {
 namespace dom {
 
+void
+CustomElementCallback::Call()
+{
+  ErrorResult rv;
+  switch (mType) {
+    case nsIDocument::eCreated:
+    {
+      // For the duration of this callback invocation, the element is being created
+      // flag must be set to true.
+      mOwnerData->mElementIsBeingCreated = true;
+
+      // The callback hasn't actually been invoked yet, but we need to flip
+      // this now in order to enqueue the attached callback. This is a spec
+      // bug (w3c bug 27437).
+      mOwnerData->mCreatedCallbackInvoked = true;
+
+      // If ELEMENT is in a document and this document has a browsing context,
+      // enqueue attached callback for ELEMENT.
+      nsIDocument* document = mThisObject->GetComposedDoc();
+      if (document && document->GetDocShell()) {
+        nsContentUtils::EnqueueLifecycleCallback(
+          document, nsIDocument::eAttached, mThisObject);
+      }
+
+      static_cast<LifecycleCreatedCallback *>(mCallback.get())->Call(mThisObject, rv);
+      mOwnerData->mElementIsBeingCreated = false;
+      break;
+    }
+    case nsIDocument::eAttached:
+      static_cast<LifecycleAttachedCallback *>(mCallback.get())->Call(mThisObject, rv);
+      break;
+    case nsIDocument::eDetached:
+      static_cast<LifecycleDetachedCallback *>(mCallback.get())->Call(mThisObject, rv);
+      break;
+    case nsIDocument::eAttributeChanged:
+      static_cast<LifecycleAttributeChangedCallback *>(mCallback.get())->Call(mThisObject,
+        mArgs.name, mArgs.oldValue, mArgs.newValue, rv);
+      break;
+  }
+}
+
+void
+CustomElementCallback::Traverse(nsCycleCollectionTraversalCallback& aCb) const
+{
+  NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(aCb, "mThisObject");
+  aCb.NoteXPCOMChild(mThisObject);
+
+  NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(aCb, "mCallback");
+  aCb.NoteXPCOMChild(mCallback);
+}
+
+CustomElementCallback::CustomElementCallback(Element* aThisObject,
+                                             nsIDocument::ElementCallbackType aCallbackType,
+                                             mozilla::dom::CallbackFunction* aCallback,
+                                             CustomElementData* aOwnerData)
+  : mThisObject(aThisObject),
+    mCallback(aCallback),
+    mType(aCallbackType),
+    mOwnerData(aOwnerData)
+{
+}
+
+CustomElementData::CustomElementData(nsIAtom* aType)
+  : mType(aType),
+    mCurrentCallback(-1),
+    mElementIsBeingCreated(false),
+    mCreatedCallbackInvoked(true),
+    mAssociatedMicroTask(-1)
+{
+}
+
+void
+CustomElementData::RunCallbackQueue()
+{
+  // Note: It's possible to re-enter this method.
+  while (static_cast<uint32_t>(++mCurrentCallback) < mCallbackQueue.Length()) {
+    mCallbackQueue[mCurrentCallback]->Call();
+  }
+
+  mCallbackQueue.Clear();
+  mCurrentCallback = -1;
+}
 
 // Only needed for refcounted objects.
-NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(CustomElementsRegistry, mWindow)
+NS_IMPL_CYCLE_COLLECTION_CLASS(CustomElementsRegistry)
+
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(CustomElementsRegistry)
+  tmp->mCustomDefinitions.Clear();
+  NS_IMPL_CYCLE_COLLECTION_UNLINK(mWindow)
+  NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(CustomElementsRegistry)
+  for (auto iter = tmp->mCustomDefinitions.Iter(); !iter.Done(); iter.Next()) {
+    nsAutoPtr<LifecycleCallbacks>& callbacks = iter.UserData()->mCallbacks;
+
+    if (callbacks->mAttributeChangedCallback.WasPassed()) {
+      NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb,
+        "mCustomDefinitions->mCallbacks->mAttributeChangedCallback");
+      cb.NoteXPCOMChild(callbacks->mAttributeChangedCallback.Value());
+    }
+
+    if (callbacks->mCreatedCallback.WasPassed()) {
+      NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb,
+        "mCustomDefinitions->mCallbacks->mCreatedCallback");
+      cb.NoteXPCOMChild(callbacks->mCreatedCallback.Value());
+    }
+
+    if (callbacks->mAttachedCallback.WasPassed()) {
+      NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb,
+        "mCustomDefinitions->mCallbacks->mAttachedCallback");
+      cb.NoteXPCOMChild(callbacks->mAttachedCallback.Value());
+    }
+
+    if (callbacks->mDetachedCallback.WasPassed()) {
+      NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb,
+        "mCustomDefinitions->mCallbacks->mDetachedCallback");
+      cb.NoteXPCOMChild(callbacks->mDetachedCallback.Value());
+    }
+  }
+  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mWindow)
+  NS_IMPL_CYCLE_COLLECTION_TRAVERSE_SCRIPT_OBJECTS
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(CustomElementsRegistry)
+  for (auto iter = tmp->mCustomDefinitions.Iter(); !iter.Done(); iter.Next()) {
+    aCallbacks.Trace(&iter.UserData()->mPrototype,
+                     "mCustomDefinitions prototype",
+                     aClosure);
+  }
+  NS_IMPL_CYCLE_COLLECTION_TRACE_PRESERVED_WRAPPER
+NS_IMPL_CYCLE_COLLECTION_TRACE_END
+
 NS_IMPL_CYCLE_COLLECTING_ADDREF(CustomElementsRegistry)
 NS_IMPL_CYCLE_COLLECTING_RELEASE(CustomElementsRegistry)
+
 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(CustomElementsRegistry)
   NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
   NS_INTERFACE_MAP_ENTRY(nsISupports)
 NS_INTERFACE_MAP_END
 
 /* static */ bool
 CustomElementsRegistry::IsCustomElementsEnabled(JSContext* aCx, JSObject* aObject)
 {
@@ -38,45 +170,319 @@ CustomElementsRegistry::Create(nsPIDOMWi
 {
   MOZ_ASSERT(aWindow);
   MOZ_ASSERT(aWindow->IsInnerWindow());
 
   if (!aWindow->GetDocShell()) {
     return nullptr;
   }
 
+  if (!Preferences::GetBool("dom.webcomponents.enabled") &&
+      !Preferences::GetBool("dom.webcomponents.customelement.enabled")) {
+    return nullptr;
+  }
+
   RefPtr<CustomElementsRegistry> customElementsRegistry =
     new CustomElementsRegistry(aWindow);
   return customElementsRegistry.forget();
 }
 
+/* static */ void
+CustomElementsRegistry::ProcessTopElementQueue()
+{
+  MOZ_ASSERT(nsContentUtils::IsSafeToRunScript());
+
+  nsTArray<RefPtr<CustomElementData>>& 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
+CustomElementsRegistry::XPCOMShutdown()
+{
+  sProcessingStack.reset();
+}
+
+/* static */ Maybe<nsTArray<RefPtr<CustomElementData>>>
+CustomElementsRegistry::sProcessingStack;
+
 CustomElementsRegistry::CustomElementsRegistry(nsPIDOMWindowInner* aWindow)
  : mWindow(aWindow)
 {
+  mozilla::HoldJSObjects(this);
+
+  if (!sProcessingStack) {
+    sProcessingStack.emplace();
+    // Add the base queue sentinel to the processing stack.
+    sProcessingStack->AppendElement((CustomElementData*) nullptr);
+  }
 }
 
 CustomElementsRegistry::~CustomElementsRegistry()
 {
+  mozilla::DropJSObjects(this);
+}
+
+CustomElementDefinition*
+CustomElementsRegistry::LookupCustomElementDefinition(const nsAString& aLocalName,
+                                                      const nsAString* aIs) const
+{
+  nsCOMPtr<nsIAtom> localNameAtom = NS_Atomize(aLocalName);
+  nsCOMPtr<nsIAtom> typeAtom = aIs ? NS_Atomize(*aIs) : localNameAtom;
+
+  CustomElementHashKey key(kNameSpaceID_XHTML, typeAtom);
+  CustomElementDefinition* data = mCustomDefinitions.Get(&key);
+  if (data && data->mLocalName == localNameAtom) {
+    return data;
+  }
+
+  return nullptr;
+}
+
+void
+CustomElementsRegistry::RegisterUnresolvedElement(Element* aElement, nsIAtom* aTypeName)
+{
+  mozilla::dom::NodeInfo* info = aElement->NodeInfo();
+
+  // Candidate may be a custom element through extension,
+  // in which case the custom element type name will not
+  // match the element tag name. e.g. <button is="x-button">.
+  nsCOMPtr<nsIAtom> typeName = aTypeName;
+  if (!typeName) {
+    typeName = info->NameAtom();
+  }
+
+  CustomElementHashKey key(info->NamespaceID(), typeName);
+  if (mCustomDefinitions.Get(&key)) {
+    return;
+  }
+
+  nsTArray<nsWeakPtr>* unresolved = mCandidatesMap.Get(&key);
+  if (!unresolved) {
+    unresolved = new nsTArray<nsWeakPtr>();
+    // Ownership of unresolved is taken by customElements.
+    mCandidatesMap.Put(&key, unresolved);
+  }
+
+  nsWeakPtr* elem = unresolved->AppendElement();
+  *elem = do_GetWeakReference(aElement);
+  aElement->AddStates(NS_EVENT_STATE_UNRESOLVED);
+
+  return;
+}
+
+void
+CustomElementsRegistry::SetupCustomElement(Element* aElement,
+                                           const nsAString* aTypeExtension)
+{
+  nsCOMPtr<nsIAtom> tagAtom = aElement->NodeInfo()->NameAtom();
+  nsCOMPtr<nsIAtom> typeAtom = aTypeExtension ?
+    NS_Atomize(*aTypeExtension) : tagAtom;
+
+  if (aTypeExtension && !aElement->HasAttr(kNameSpaceID_None, nsGkAtoms::is)) {
+    // Custom element setup in the parser happens after the "is"
+    // attribute is added.
+    aElement->SetAttr(kNameSpaceID_None, nsGkAtoms::is, *aTypeExtension, true);
+  }
+
+  CustomElementDefinition* data = LookupCustomElementDefinition(
+    aElement->NodeInfo()->LocalName(), aTypeExtension);
+
+  if (!data) {
+    // The type extension doesn't exist in the registry,
+    // thus we don't need to enqueue callback or adjust
+    // the "is" attribute, but it is possibly an upgrade candidate.
+    RegisterUnresolvedElement(aElement, typeAtom);
+    return;
+  }
+
+  if (data->mLocalName != tagAtom) {
+    // The element doesn't match the local name for the
+    // definition, thus the element isn't a custom element
+    // and we don't need to do anything more.
+    return;
+  }
+
+  // Enqueuing the created callback will set the CustomElementData on the
+  // element, causing prototype swizzling to occur in Element::WrapObject.
+  EnqueueLifecycleCallback(nsIDocument::eCreated, aElement, nullptr, data);
+}
+
+void
+CustomElementsRegistry::EnqueueLifecycleCallback(nsIDocument::ElementCallbackType aType,
+                                                 Element* aCustomElement,
+                                                 LifecycleCallbackArgs* aArgs,
+                                                 CustomElementDefinition* aDefinition)
+{
+  CustomElementData* elementData = aCustomElement->GetCustomElementData();
+
+  // Let DEFINITION be ELEMENT's definition
+  CustomElementDefinition* definition = aDefinition;
+  if (!definition) {
+    mozilla::dom::NodeInfo* info = aCustomElement->NodeInfo();
+
+    // Make sure we get the correct definition in case the element
+    // is a extended custom element e.g. <button is="x-button">.
+    nsCOMPtr<nsIAtom> typeAtom = elementData ?
+      elementData->mType.get() : info->NameAtom();
+
+    CustomElementHashKey key(info->NamespaceID(), typeAtom);
+    definition = mCustomDefinitions.Get(&key);
+    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;
+    }
+  }
+
+  if (!elementData) {
+    // Create the custom element data the first time
+    // that we try to enqueue a callback.
+    elementData = new CustomElementData(definition->mType);
+    // aCustomElement takes ownership of elementData
+    aCustomElement->SetCustomElementData(elementData);
+    MOZ_ASSERT(aType == nsIDocument::eCreated,
+               "First callback should be the created callback");
+  }
+
+  // Let CALLBACK be the callback associated with the key NAME in CALLBACKS.
+  CallbackFunction* func = nullptr;
+  switch (aType) {
+    case nsIDocument::eCreated:
+      if (definition->mCallbacks->mCreatedCallback.WasPassed()) {
+        func = definition->mCallbacks->mCreatedCallback.Value();
+      }
+      break;
+
+    case nsIDocument::eAttached:
+      if (definition->mCallbacks->mAttachedCallback.WasPassed()) {
+        func = definition->mCallbacks->mAttachedCallback.Value();
+      }
+      break;
+
+    case nsIDocument::eDetached:
+      if (definition->mCallbacks->mDetachedCallback.WasPassed()) {
+        func = definition->mCallbacks->mDetachedCallback.Value();
+      }
+      break;
+
+    case nsIDocument::eAttributeChanged:
+      if (definition->mCallbacks->mAttributeChangedCallback.WasPassed()) {
+        func = definition->mCallbacks->mAttributeChangedCallback.Value();
+      }
+      break;
+  }
+
+  // If there is no such callback, stop.
+  if (!func) {
+    return;
+  }
+
+  if (aType == nsIDocument::eCreated) {
+    elementData->mCreatedCallbackInvoked = false;
+  } else if (!elementData->mCreatedCallbackInvoked) {
+    // Callbacks other than created callback must not be enqueued
+    // until after the created callback has been invoked.
+    return;
+  }
+
+  // Add CALLBACK to ELEMENT's callback queue.
+  CustomElementCallback* callback = new CustomElementCallback(aCustomElement,
+                                                              aType,
+                                                              func,
+                                                              elementData);
+  // Ownership of callback is taken by mCallbackQueue.
+  elementData->mCallbackQueue.AppendElement(callback);
+  if (aArgs) {
+    callback->SetArgs(*aArgs);
+  }
+
+  if (!elementData->mElementIsBeingCreated) {
+    CustomElementData* lastData =
+      sProcessingStack->SafeLastElement(nullptr);
+
+    // 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<int32_t>(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<int32_t>(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<nsIRunnable> runnable =
+        NS_NewRunnableFunction(&CustomElementsRegistry::ProcessTopElementQueue);
+      nsContentUtils::AddScriptRunner(runnable);
+    }
+  }
+}
+
+void
+CustomElementsRegistry::GetCustomPrototype(nsIAtom* aAtom,
+                                           JS::MutableHandle<JSObject*> aPrototype)
+{
+  mozilla::dom::CustomElementHashKey key(kNameSpaceID_XHTML, aAtom);
+  mozilla::dom::CustomElementDefinition* definition = mCustomDefinitions.Get(&key);
+  if (definition) {
+    aPrototype.set(definition->mPrototype);
+  } else {
+    aPrototype.set(nullptr);
+  }
 }
 
 JSObject*
 CustomElementsRegistry::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
 {
   return CustomElementsRegistryBinding::Wrap(aCx, this, aGivenProto);
 }
 
 nsISupports* CustomElementsRegistry::GetParentObject() const
 {
   return mWindow;
 }
 
-void CustomElementsRegistry::Define(const nsAString& aName,
-                                    Function& aFunctionConstructor,
-                                    const ElementDefinitionOptions& aOptions,
-                                    ErrorResult& aRv)
+void
+CustomElementsRegistry::Define(const nsAString& aName,
+                               Function& aFunctionConstructor,
+                               const ElementDefinitionOptions& aOptions,
+                               ErrorResult& aRv)
 {
   // TODO: This function will be implemented in bug 1275835
   aRv.Throw(NS_ERROR_NOT_IMPLEMENTED);
 }
 
 void
 CustomElementsRegistry::Get(JSContext* aCx, const nsAString& aName,
                             JS::MutableHandle<JS::Value> aRetVal,
--- a/dom/base/CustomElementsRegistry.h
+++ b/dom/base/CustomElementsRegistry.h
@@ -10,24 +10,97 @@
 #include "js/TypeDecls.h"
 #include "mozilla/Attributes.h"
 #include "mozilla/ErrorResult.h"
 #include "mozilla/dom/BindingDeclarations.h"
 #include "nsCycleCollectionParticipant.h"
 #include "nsWrapperCache.h"
 #include "mozilla/dom/FunctionBinding.h"
 
+class nsDocument;
+
 namespace mozilla {
 namespace dom {
 
+struct CustomElementData;
 struct ElementDefinitionOptions;
 struct LifecycleCallbacks;
+class CallbackFunction;
 class Function;
 class Promise;
 
+struct LifecycleCallbackArgs
+{
+  nsString name;
+  nsString oldValue;
+  nsString newValue;
+};
+
+class CustomElementCallback
+{
+public:
+  CustomElementCallback(Element* aThisObject,
+                        nsIDocument::ElementCallbackType aCallbackType,
+                        CallbackFunction* aCallback,
+                        CustomElementData* aOwnerData);
+  void Traverse(nsCycleCollectionTraversalCallback& aCb) const;
+  void Call();
+  void SetArgs(LifecycleCallbackArgs& aArgs)
+  {
+    MOZ_ASSERT(mType == nsIDocument::eAttributeChanged,
+               "Arguments are only used by attribute changed callback.");
+    mArgs = aArgs;
+  }
+
+private:
+  // The this value to use for invocation of the callback.
+  RefPtr<Element> mThisObject;
+  RefPtr<CallbackFunction> mCallback;
+  // The type of callback (eCreated, eAttached, etc.)
+  nsIDocument::ElementCallbackType mType;
+  // Arguments to be passed to the callback,
+  // used by the attribute changed callback.
+  LifecycleCallbackArgs mArgs;
+  // CustomElementData that contains this callback in the
+  // callback queue.
+  CustomElementData* mOwnerData;
+};
+
+// Each custom element has an associated callback queue and an element is
+// being created flag.
+struct CustomElementData
+{
+  NS_INLINE_DECL_REFCOUNTING(CustomElementData)
+
+  explicit CustomElementData(nsIAtom* aType);
+  // Objects in this array are transient and empty after each microtask
+  // checkpoint.
+  nsTArray<nsAutoPtr<CustomElementCallback>> mCallbackQueue;
+  // Custom element type, for <button is="x-button"> or <x-button>
+  // this would be x-button.
+  nsCOMPtr<nsIAtom> mType;
+  // The callback that is next to be processed upon calling RunCallbackQueue.
+  int32_t mCurrentCallback;
+  // Element is being created flag as described in the custom elements spec.
+  bool mElementIsBeingCreated;
+  // Flag to determine if the created callback has been invoked, thus it
+  // determines if other callbacks can be enqueued.
+  bool mCreatedCallbackInvoked;
+  // The microtask level associated with the callbacks in the callback queue,
+  // it is used to determine if a new queue needs to be pushed onto the
+  // processing stack.
+  int32_t mAssociatedMicroTask;
+
+  // Empties the callback queue.
+  void RunCallbackQueue();
+
+private:
+  virtual ~CustomElementData() {}
+};
+
 class CustomElementHashKey : public PLDHashEntryHdr
 {
 public:
   typedef CustomElementHashKey *KeyType;
   typedef const CustomElementHashKey *KeyTypePointer;
 
   CustomElementHashKey(int32_t aNamespaceID, nsIAtom *aAtom)
     : mNamespaceID(aNamespaceID),
@@ -96,30 +169,92 @@ struct CustomElementDefinition
 
   // The document custom element order.
   uint32_t mDocOrder;
 };
 
 class CustomElementsRegistry final : public nsISupports,
                                      public nsWrapperCache
 {
+  // Allow nsDocument to access mCustomDefinitions and mCandidatesMap.
+  friend class ::nsDocument;
+
 public:
   NS_DECL_CYCLE_COLLECTING_ISUPPORTS
   NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(CustomElementsRegistry)
 
 public:
   static bool IsCustomElementsEnabled(JSContext* aCx, JSObject* aObject);
   static already_AddRefed<CustomElementsRegistry> Create(nsPIDOMWindowInner* aWindow);
-  already_AddRefed<nsIDocument> GetOwnerDocument() const;
+  static void ProcessTopElementQueue();
+
+  static void XPCOMShutdown();
+
+  /**
+   * Looking up a custom element definition.
+   * https://html.spec.whatwg.org/#look-up-a-custom-element-definition
+   */
+  CustomElementDefinition* LookupCustomElementDefinition(
+    const nsAString& aLocalName, const nsAString* aIs = nullptr) const;
+
+  /**
+   * Enqueue created callback or register upgrade candidate for
+   * newly created custom elements, possibly extending an existing type.
+   * ex. <x-button>, <button is="x-button> (type extension)
+   */
+  void SetupCustomElement(Element* aElement, const nsAString* aTypeExtension);
+
+  void EnqueueLifecycleCallback(nsIDocument::ElementCallbackType aType,
+                                Element* aCustomElement,
+                                LifecycleCallbackArgs* aArgs,
+                                CustomElementDefinition* aDefinition);
+
+  void GetCustomPrototype(nsIAtom* aAtom,
+                          JS::MutableHandle<JSObject*> aPrototype);
 
 private:
   explicit CustomElementsRegistry(nsPIDOMWindowInner* aWindow);
   ~CustomElementsRegistry();
+
+  /**
+   * Registers an unresolved custom element that is a candidate for
+   * upgrade when the definition is registered via registerElement.
+   * |aTypeName| is the name of the custom element type, if it is not
+   * provided, then element name is used. |aTypeName| should be provided
+   * when registering a custom element that extends an existing
+   * element. e.g. <button is="x-button">.
+   */
+  void RegisterUnresolvedElement(Element* aElement,
+                                 nsIAtom* aTypeName = nullptr);
+
+  typedef nsClassHashtable<CustomElementHashKey, CustomElementDefinition>
+    DefinitionMap;
+  typedef nsClassHashtable<CustomElementHashKey, nsTArray<nsWeakPtr>>
+    CandidateMap;
+
+  // Hashtable for custom element definitions in web components.
+  // Custom prototypes are stored in the compartment where
+  // registerElement was called.
+  DefinitionMap mCustomDefinitions;
+
+  // 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;
+
   nsCOMPtr<nsPIDOMWindowInner> mWindow;
 
+  // Array representing the processing stack in the custom elements
+  // specification. The processing stack is conceptually a stack of
+  // element queues. Each queue is represented by a sequence of
+  // CustomElementData in this array, separated by nullptr that
+  // represent the boundaries of the items in the stack. The first
+  // queue in the stack is the base element queue.
+  static mozilla::Maybe<nsTArray<RefPtr<CustomElementData>>> sProcessingStack;
+
 public:
   nsISupports* GetParentObject() const;
 
   virtual JSObject* WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override;
 
   void Define(const nsAString& aName, Function& aFunctionConstructor,
               const ElementDefinitionOptions& aOptions, ErrorResult& aRv);
 
--- a/dom/base/DOMImplementation.cpp
+++ b/dom/base/DOMImplementation.cpp
@@ -128,17 +128,16 @@ DOMImplementation::CreateDocument(const 
 
   // When DOMImplementation's createDocument method is invoked with
   // namespace set to HTML Namespace use the registry of the associated
   // document to the new instance.
   nsCOMPtr<nsIDocument> doc = do_QueryInterface(document);
 
   if (aNamespaceURI.EqualsLiteral("http://www.w3.org/1999/xhtml")) {
     doc->SetContentType(NS_LITERAL_STRING("application/xhtml+xml"));
-    doc->UseRegistryFromDocument(mOwner);
   } else if (aNamespaceURI.EqualsLiteral("http://www.w3.org/2000/svg")) {
     doc->SetContentType(NS_LITERAL_STRING("image/svg+xml"));
   } else {
     doc->SetContentType(NS_LITERAL_STRING("application/xml"));
   }
 
   doc->SetReadyStateInternal(nsIDocument::READYSTATE_COMPLETE);
 
@@ -230,20 +229,16 @@ DOMImplementation::CreateHTMLDocument(co
     NS_ENSURE_SUCCESS(rv, rv);
   }
 
   nsCOMPtr<Element> body = doc->CreateElem(NS_LITERAL_STRING("body"), nullptr,
                                            kNameSpaceID_XHTML);
   rv = root->AppendChildTo(body, false);
   NS_ENSURE_SUCCESS(rv, rv);
 
-  // When the createHTMLDocument method is invoked,
-  // use the registry of the associated document to the new instance.
-  doc->UseRegistryFromDocument(mOwner);
-
   doc->SetReadyStateInternal(nsIDocument::READYSTATE_COMPLETE);
 
   doc.forget(aDocument);
   document.forget(aDOMDocument);
   return NS_OK;
 }
 
 already_AddRefed<nsIDocument>
--- a/dom/base/Element.cpp
+++ b/dom/base/Element.cpp
@@ -465,18 +465,18 @@ Element::WrapObject(JSContext *aCx, JS::
   JS::Rooted<JSObject*> givenProto(aCx, aGivenProto);
   JS::Rooted<JSObject*> customProto(aCx);
 
   if (!givenProto) {
     // Custom element prototype swizzling.
     CustomElementData* data = GetCustomElementData();
     if (data) {
       // If this is a registered custom element then fix the prototype.
-      nsDocument* document = static_cast<nsDocument*>(OwnerDoc());
-      document->GetCustomPrototype(NodeInfo()->NamespaceID(), data->mType, &customProto);
+      nsContentUtils::GetCustomPrototype(OwnerDoc(), NodeInfo()->NamespaceID(),
+                                         data->mType, &customProto);
       if (customProto &&
           NodePrincipal()->SubsumesConsideringDomain(nsContentUtils::ObjectPrincipal(customProto))) {
         // Just go ahead and create with the right proto up front.  Set
         // customProto to null to flag that we don't need to do any post-facto
         // proto fixups here.
         givenProto = customProto;
         customProto = nullptr;
       }
@@ -1603,17 +1603,18 @@ Element::BindToTree(nsIDocument* aDocume
   }
 
   nsIDocument* composedDoc = GetComposedDoc();
   if (composedDoc) {
     // Attached callback must be enqueued whenever custom element is inserted into a
     // document and this document has a browsing context.
     if (GetCustomElementData() && composedDoc->GetDocShell()) {
       // Enqueue an attached callback for the custom element.
-      composedDoc->EnqueueLifecycleCallback(nsIDocument::eAttached, this);
+      nsContentUtils::EnqueueLifecycleCallback(
+        composedDoc, nsIDocument::eAttached, this);
     }
   }
 
   // Propagate scoped style sheet tracking bit.
   if (mParent->IsContent()) {
     nsIContent* parent;
     ShadowRoot* shadowRootParent = ShadowRoot::FromNode(mParent);
     if (shadowRootParent) {
@@ -1884,17 +1885,18 @@ Element::UnbindFromTree(bool aDeep, bool
     }
 
     document->ClearBoxObjectFor(this);
 
     // Detached must be enqueued whenever custom element is removed from
     // the document and this document has a browsing context.
     if (GetCustomElementData() && document->GetDocShell()) {
       // Enqueue a detached callback for the custom element.
-      document->EnqueueLifecycleCallback(nsIDocument::eDetached, this);
+      nsContentUtils::EnqueueLifecycleCallback(
+        document, nsIDocument::eDetached, this);
     }
   }
 
   // Unset this since that's what the old code effectively did.
   UnsetFlags(NODE_FORCE_XBL_BINDINGS);
   bool clearBindingParent = true;
 
 #ifdef MOZ_XUL
@@ -2490,17 +2492,18 @@ Element::SetAttrAndNotify(int32_t aNames
     nsCOMPtr<nsIAtom> newValueAtom = valueForAfterSetAttr.GetAsAtom();
     LifecycleCallbackArgs args = {
       nsDependentAtomString(aName),
       aModType == nsIDOMMutationEvent::ADDITION ?
         NullString() : nsDependentAtomString(oldValueAtom),
       nsDependentAtomString(newValueAtom)
     };
 
-    ownerDoc->EnqueueLifecycleCallback(nsIDocument::eAttributeChanged, this, &args);
+    nsContentUtils::EnqueueLifecycleCallback(
+      ownerDoc, nsIDocument::eAttributeChanged, this, &args);
   }
 
   if (aCallAfterSetAttr) {
     rv = AfterSetAttr(aNamespaceID, aName, &valueForAfterSetAttr, aNotify);
     NS_ENSURE_SUCCESS(rv, rv);
 
     if (aNamespaceID == kNameSpaceID_None && aName == nsGkAtoms::dir) {
       OnSetDirAttr(this, &valueForAfterSetAttr,
@@ -2744,17 +2747,18 @@ Element::UnsetAttr(int32_t aNameSpaceID,
   if (ownerDoc && GetCustomElementData()) {
     nsCOMPtr<nsIAtom> oldValueAtom = oldValue.GetAsAtom();
     LifecycleCallbackArgs args = {
       nsDependentAtomString(aName),
       nsDependentAtomString(oldValueAtom),
       NullString()
     };
 
-    ownerDoc->EnqueueLifecycleCallback(nsIDocument::eAttributeChanged, this, &args);
+    nsContentUtils::EnqueueLifecycleCallback(
+      ownerDoc, nsIDocument::eAttributeChanged, this, &args);
   }
 
   if (aNotify) {
     // We can always pass oldValue here since there is no new value which could
     // have corrupted it.
     nsNodeUtils::AttributeChanged(this, aNameSpaceID, aName,
                                   nsIDOMMutationEvent::REMOVAL, &oldValue);
   }
--- a/dom/base/Element.h
+++ b/dom/base/Element.h
@@ -131,16 +131,17 @@ class EventChainPostVisitor;
 class EventChainPreVisitor;
 class EventChainVisitor;
 class EventListenerManager;
 class EventStateManager;
 
 namespace dom {
 
 class Animation;
+class CustomElementsRegistry;
 class Link;
 class UndoManager;
 class DOMRect;
 class DOMRectList;
 class DestinationInsertionPointList;
 class Grid;
 
 // IID for the dom::Element interface
@@ -421,16 +422,19 @@ protected:
 private:
   // Need to allow the ESM, nsGlobalWindow, and the focus manager to
   // set our state
   friend class mozilla::EventStateManager;
   friend class ::nsGlobalWindow;
   friend class ::nsFocusManager;
   friend class ::nsDocument;
 
+  // Allow CusomtElementRegistry to call AddStates.
+  friend class CustomElementsRegistry;
+
   // Also need to allow Link to call UpdateLinkState.
   friend class Link;
 
   void NotifyStateChange(EventStates aStates);
 
   void NotifyStyleStateChange(EventStates aStates);
 
   // Style state computed from element's state and style locks.
--- a/dom/base/nsContentUtils.cpp
+++ b/dom/base/nsContentUtils.cpp
@@ -33,16 +33,17 @@
 #include "mozilla/Attributes.h"
 #include "mozilla/AutoRestore.h"
 #include "mozilla/AutoTimelineMarker.h"
 #include "mozilla/Base64.h"
 #include "mozilla/CheckedInt.h"
 #include "mozilla/DebugOnly.h"
 #include "mozilla/LoadInfo.h"
 #include "mozilla/dom/ContentChild.h"
+#include "mozilla/dom/CustomElementsRegistry.h"
 #include "mozilla/dom/DocumentFragment.h"
 #include "mozilla/dom/DOMTypes.h"
 #include "mozilla/dom/Element.h"
 #include "mozilla/dom/HTMLMediaElement.h"
 #include "mozilla/dom/HTMLTemplateElement.h"
 #include "mozilla/dom/HTMLContentElement.h"
 #include "mozilla/dom/HTMLShadowElement.h"
 #include "mozilla/dom/ipc/BlobChild.h"
@@ -9354,8 +9355,128 @@ nsContentUtils::HttpsStateIsModern(nsIDo
     csm->IsOriginPotentiallyTrustworthy(principal, &isTrustworthyOrigin);
     if (isTrustworthyOrigin) {
       return true;
     }
   }
 
   return false;
 }
+
+/* static */ CustomElementDefinition*
+nsContentUtils::LookupCustomElementDefinition(nsIDocument* aDoc,
+                                              const nsAString& aLocalName,
+                                              uint32_t aNameSpaceID,
+                                              const nsAString* aIs)
+{
+  MOZ_ASSERT(aDoc);
+
+  // To support imported document.
+  nsCOMPtr<nsIDocument> doc = aDoc->MasterDocument();
+
+  if (aNameSpaceID != kNameSpaceID_XHTML ||
+      !doc->GetDocShell()) {
+    return nullptr;
+  }
+
+  nsCOMPtr<nsPIDOMWindowInner> window(doc->GetInnerWindow());
+  if (!window) {
+    return nullptr;
+  }
+
+  RefPtr<CustomElementsRegistry> registry(window->CustomElements());
+  if (!registry) {
+    return nullptr;
+  }
+
+  return registry->LookupCustomElementDefinition(aLocalName, aIs);
+}
+
+/* static */ void
+nsContentUtils::SetupCustomElement(Element* aElement,
+                                   const nsAString* aTypeExtension)
+{
+  MOZ_ASSERT(aElement);
+
+  nsCOMPtr<nsIDocument> doc = aElement->OwnerDoc();
+
+  if (!doc) {
+    return;
+  }
+
+  // To support imported document.
+  doc = doc->MasterDocument();
+
+  if (aElement->GetNameSpaceID() != kNameSpaceID_XHTML ||
+      !doc->GetDocShell()) {
+    return;
+  }
+
+  nsCOMPtr<nsPIDOMWindowInner> window(doc->GetInnerWindow());
+  if (!window) {
+    return;
+  }
+
+  RefPtr<CustomElementsRegistry> registry(window->CustomElements());
+  if (!registry) {
+    return;
+  }
+
+  return registry->SetupCustomElement(aElement, aTypeExtension);
+}
+
+/* static */ void
+nsContentUtils::EnqueueLifecycleCallback(nsIDocument* aDoc,
+                                         nsIDocument::ElementCallbackType aType,
+                                         Element* aCustomElement,
+                                         LifecycleCallbackArgs* aArgs,
+                                         CustomElementDefinition* aDefinition)
+{
+  MOZ_ASSERT(aDoc);
+
+  // To support imported document.
+  nsCOMPtr<nsIDocument> doc = aDoc->MasterDocument();
+
+  if (!doc->GetDocShell()) {
+    return;
+  }
+
+  nsCOMPtr<nsPIDOMWindowInner> window(doc->GetInnerWindow());
+  if (!window) {
+    return;
+  }
+
+  RefPtr<CustomElementsRegistry> registry(window->CustomElements());
+  if (!registry) {
+    return;
+  }
+
+  registry->EnqueueLifecycleCallback(aType, aCustomElement, aArgs, aDefinition);
+}
+
+/* static */ void
+nsContentUtils::GetCustomPrototype(nsIDocument* aDoc,
+                                   int32_t aNamespaceID,
+                                   nsIAtom* aAtom,
+                                   JS::MutableHandle<JSObject*> aPrototype)
+{
+  MOZ_ASSERT(aDoc);
+
+  // To support imported document.
+  nsCOMPtr<nsIDocument> doc = aDoc->MasterDocument();
+
+  if (aNamespaceID != kNameSpaceID_XHTML ||
+      !doc->GetDocShell()) {
+    return;
+  }
+
+  nsCOMPtr<nsPIDOMWindowInner> window(doc->GetInnerWindow());
+  if (!window) {
+    return;
+  }
+
+  RefPtr<CustomElementsRegistry> registry(window->CustomElements());
+  if (!registry) {
+    return;
+  }
+
+  return registry->GetCustomPrototype(aAtom, aPrototype);
+}
--- a/dom/base/nsContentUtils.h
+++ b/dom/base/nsContentUtils.h
@@ -29,16 +29,17 @@
 #include "Units.h"
 #include "mozilla/dom/AutocompleteInfoBinding.h"
 #include "mozilla/dom/ScriptSettings.h"
 #include "mozilla/FloatingPoint.h"
 #include "mozilla/net/ReferrerPolicy.h"
 #include "mozilla/Logging.h"
 #include "mozilla/NotNull.h"
 #include "nsIContentPolicy.h"
+#include "nsIDocument.h"
 #include "nsPIDOMWindow.h"
 
 #if defined(XP_WIN)
 // Undefine LoadImage to prevent naming conflict with Windows.
 #undef LoadImage
 #endif
 
 class imgICache;
@@ -50,17 +51,16 @@ class imgRequestProxy;
 class nsAutoScriptBlockerSuppressNodeRemoved;
 class nsHtml5StringParser;
 class nsIChannel;
 class nsIConsoleService;
 class nsIContent;
 class nsIContentPolicy;
 class nsIContentSecurityPolicy;
 class nsIDocShellTreeItem;
-class nsIDocument;
 class nsIDocumentLoaderFactory;
 class nsIDOMDocument;
 class nsIDOMDocumentFragment;
 class nsIDOMEvent;
 class nsIDOMHTMLInputElement;
 class nsIDOMKeyEvent;
 class nsIDOMNode;
 class nsIDragSession;
@@ -114,21 +114,23 @@ template<class K, class V> class nsDataH
 template<class K, class V> class nsRefPtrHashtable;
 template<class T> class nsReadingIterator;
 
 namespace mozilla {
 class ErrorResult;
 class EventListenerManager;
 
 namespace dom {
+struct CustomElementDefinition;
 class DocumentFragment;
 class Element;
 class EventTarget;
 class IPCDataTransfer;
 class IPCDataTransferItem;
+struct LifecycleCallbackArgs;
 class NodeInfo;
 class nsIContentChild;
 class nsIContentParent;
 class Selection;
 class TabParent;
 } // namespace dom
 
 namespace ipc {
@@ -2663,16 +2665,40 @@ public:
   /**
    * Returns true if the "HTTPS state" of the document should be "modern". See:
    *
    * https://html.spec.whatwg.org/#concept-document-https-state
    * https://fetch.spec.whatwg.org/#concept-response-https-state
    */
   static bool HttpsStateIsModern(nsIDocument* aDocument);
 
+  /**
+   * Looking up a custom element definition.
+   * https://html.spec.whatwg.org/#look-up-a-custom-element-definition
+   */
+  static mozilla::dom::CustomElementDefinition*
+    LookupCustomElementDefinition(nsIDocument* aDoc,
+                                  const nsAString& aLocalName,
+                                  uint32_t aNameSpaceID,
+                                  const nsAString* aIs = nullptr);
+
+  static void SetupCustomElement(Element* aElement,
+                                 const nsAString* aTypeExtension = nullptr);
+
+  static void EnqueueLifecycleCallback(nsIDocument* aDoc,
+                                       nsIDocument::ElementCallbackType aType,
+                                       Element* aCustomElement,
+                                       mozilla::dom::LifecycleCallbackArgs* aArgs = nullptr,
+                                       mozilla::dom::CustomElementDefinition* aDefinition = nullptr);
+
+  static void GetCustomPrototype(nsIDocument* aDoc,
+                                 int32_t aNamespaceID,
+                                 nsIAtom* aAtom,
+                                 JS::MutableHandle<JSObject*> prototype);
+
 private:
   static bool InitializeEventTable();
 
   static nsresult EnsureStringBundle(PropertiesFile aFile);
 
   static bool CanCallerAccess(nsIPrincipal* aSubjectPrincipal,
                                 nsIPrincipal* aPrincipal);
 
--- a/dom/base/nsDocument.cpp
+++ b/dom/base/nsDocument.cpp
@@ -213,16 +213,17 @@
 #include "mozilla/dom/HTMLBodyElement.h"
 #include "mozilla/dom/HTMLInputElement.h"
 #include "mozilla/dom/MediaQueryList.h"
 #include "mozilla/dom/NodeFilterBinding.h"
 #include "mozilla/OwningNonNull.h"
 #include "mozilla/dom/TabChild.h"
 #include "mozilla/dom/UndoManager.h"
 #include "mozilla/dom/WebComponentsBinding.h"
+#include "mozilla/dom/CustomElementsRegistry.h"
 #include "nsFrame.h"
 #include "nsDOMCaretPosition.h"
 #include "nsIDOMHTMLTextAreaElement.h"
 #include "nsViewportInfo.h"
 #include "mozilla/StaticPtr.h"
 #include "nsITextControlElement.h"
 #include "nsIDOMNSEditableElement.h"
 #include "nsIEditor.h"
@@ -363,166 +364,16 @@ nsIdentifierMapEntry::RemoveContentChang
     return;
   ChangeCallback cc = { aCallback, aData, aForImage };
   mChangeCallbacks->RemoveEntry(cc);
   if (mChangeCallbacks->Count() == 0) {
     mChangeCallbacks = nullptr;
   }
 }
 
-namespace mozilla {
-namespace dom {
-
-NS_IMPL_CYCLE_COLLECTION_CLASS(Registry)
-
-NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(Registry)
-  for (auto iter = tmp->mCustomDefinitions.Iter(); !iter.Done(); iter.Next()) {
-    aCallbacks.Trace(&iter.UserData()->mPrototype,
-                     "mCustomDefinitions prototype",
-                     aClosure);
-  }
-NS_IMPL_CYCLE_COLLECTION_TRACE_END
-
-NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(Registry)
-  for (auto iter = tmp->mCustomDefinitions.Iter(); !iter.Done(); iter.Next()) {
-    nsAutoPtr<LifecycleCallbacks>& callbacks = iter.UserData()->mCallbacks;
-
-    if (callbacks->mAttributeChangedCallback.WasPassed()) {
-      NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb,
-        "mCustomDefinitions->mCallbacks->mAttributeChangedCallback");
-      cb.NoteXPCOMChild(callbacks->mAttributeChangedCallback.Value());
-    }
-
-    if (callbacks->mCreatedCallback.WasPassed()) {
-      NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb,
-        "mCustomDefinitions->mCallbacks->mCreatedCallback");
-      cb.NoteXPCOMChild(callbacks->mCreatedCallback.Value());
-    }
-
-    if (callbacks->mAttachedCallback.WasPassed()) {
-      NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb,
-        "mCustomDefinitions->mCallbacks->mAttachedCallback");
-      cb.NoteXPCOMChild(callbacks->mAttachedCallback.Value());
-    }
-
-    if (callbacks->mDetachedCallback.WasPassed()) {
-      NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb,
-        "mCustomDefinitions->mCallbacks->mDetachedCallback");
-      cb.NoteXPCOMChild(callbacks->mDetachedCallback.Value());
-    }
-  }
-  NS_IMPL_CYCLE_COLLECTION_TRAVERSE_SCRIPT_OBJECTS
-NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
-
-NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(Registry)
-  tmp->mCustomDefinitions.Clear();
-NS_IMPL_CYCLE_COLLECTION_UNLINK_END
-
-NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(Registry)
-  NS_INTERFACE_MAP_ENTRY(nsISupports)
-NS_INTERFACE_MAP_END
-
-NS_IMPL_CYCLE_COLLECTING_ADDREF(Registry)
-NS_IMPL_CYCLE_COLLECTING_RELEASE(Registry)
-
-Registry::Registry()
-{
-  mozilla::HoldJSObjects(this);
-}
-
-Registry::~Registry()
-{
-  mozilla::DropJSObjects(this);
-}
-
-void
-CustomElementCallback::Call()
-{
-  ErrorResult rv;
-  switch (mType) {
-    case nsIDocument::eCreated:
-    {
-      // For the duration of this callback invocation, the element is being created
-      // flag must be set to true.
-      mOwnerData->mElementIsBeingCreated = true;
-
-      // The callback hasn't actually been invoked yet, but we need to flip
-      // this now in order to enqueue the attached callback. This is a spec
-      // bug (w3c bug 27437).
-      mOwnerData->mCreatedCallbackInvoked = true;
-
-      // If ELEMENT is in a document and this document has a browsing context,
-      // enqueue attached callback for ELEMENT.
-      nsIDocument* document = mThisObject->GetComposedDoc();
-      if (document && document->GetDocShell()) {
-        document->EnqueueLifecycleCallback(nsIDocument::eAttached, mThisObject);
-      }
-
-      static_cast<LifecycleCreatedCallback *>(mCallback.get())->Call(mThisObject, rv);
-      mOwnerData->mElementIsBeingCreated = false;
-      break;
-    }
-    case nsIDocument::eAttached:
-      static_cast<LifecycleAttachedCallback *>(mCallback.get())->Call(mThisObject, rv);
-      break;
-    case nsIDocument::eDetached:
-      static_cast<LifecycleDetachedCallback *>(mCallback.get())->Call(mThisObject, rv);
-      break;
-    case nsIDocument::eAttributeChanged:
-      static_cast<LifecycleAttributeChangedCallback *>(mCallback.get())->Call(mThisObject,
-        mArgs.name, mArgs.oldValue, mArgs.newValue, rv);
-      break;
-  }
-}
-
-void
-CustomElementCallback::Traverse(nsCycleCollectionTraversalCallback& aCb) const
-{
-  NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(aCb, "mThisObject");
-  aCb.NoteXPCOMChild(mThisObject);
-
-  NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(aCb, "mCallback");
-  aCb.NoteXPCOMChild(mCallback);
-}
-
-CustomElementCallback::CustomElementCallback(Element* aThisObject,
-                                             nsIDocument::ElementCallbackType aCallbackType,
-                                             mozilla::dom::CallbackFunction* aCallback,
-                                             CustomElementData* aOwnerData)
-  : mThisObject(aThisObject),
-    mCallback(aCallback),
-    mType(aCallbackType),
-    mOwnerData(aOwnerData)
-{
-}
-
-CustomElementData::CustomElementData(nsIAtom* aType)
-  : mType(aType),
-    mCurrentCallback(-1),
-    mElementIsBeingCreated(false),
-    mCreatedCallbackInvoked(true),
-    mAssociatedMicroTask(-1)
-{
-}
-
-void
-CustomElementData::RunCallbackQueue()
-{
-  // Note: It's possible to re-enter this method.
-  while (static_cast<uint32_t>(++mCurrentCallback) < mCallbackQueue.Length()) {
-    mCallbackQueue[mCurrentCallback]->Call();
-  }
-
-  mCallbackQueue.Clear();
-  mCurrentCallback = -1;
-}
-
-} // namespace dom
-} // namespace mozilla
-
 void
 nsIdentifierMapEntry::FireChangeCallbacks(Element* aOldElement,
                                           Element* aNewElement,
                                           bool aImageOnly)
 {
   if (!mChangeCallbacks)
     return;
 
@@ -1489,22 +1340,16 @@ nsDocument::nsDocument(const char* aCont
            ("DOCUMENT %p created", this));
 
   // Start out mLastStyleSheetSet as null, per spec
   SetDOMStringToNull(mLastStyleSheetSet);
 
   // void state used to differentiate an empty source from an unselected source
   mPreloadPictureFoundSource.SetIsVoid(true);
 
-  if (!sProcessingStack) {
-    sProcessingStack.emplace();
-    // Add the base queue sentinel to the processing stack.
-    sProcessingStack->AppendElement((CustomElementData*) nullptr);
-  }
-
   mEverInForeground = false;
 }
 
 void
 nsDocument::ClearAllBoxObjects()
 {
   if (mBoxObjectTable) {
     for (auto iter = mBoxObjectTable->Iter(); !iter.Done(); iter.Next()) {
@@ -1612,18 +1457,16 @@ nsDocument::~nsDocument()
     }
   }
 
   ReportUseCounters();
 
   mInDestructor = true;
   mInUnlinkOrDeletion = true;
 
-  mRegistry = nullptr;
-
   mozilla::DropJSObjects(this);
 
   // Clear mObservers to keep it in sync with the mutationobserver list
   mObservers.Clear();
 
   if (mStyleSheetSetList) {
     mStyleSheetSetList->Disconnect();
   }
@@ -1885,17 +1728,16 @@ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mOriginalDocument)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mCachedEncoder)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mStateObjectCached)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mUndoManager)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDocumentTimeline)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPendingAnimationTracker)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mTemplateContentsOwner)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mChildrenCollection)
-  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mRegistry)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mAnonymousContents)
 
   // Traverse all our nsCOMArrays.
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mStyleSheets)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mOnDemandBuiltInUASheets)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPreloadingImages)
 
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mSubImportLinks)
@@ -1970,17 +1812,16 @@ NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(ns
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mDOMImplementation)
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mImageMaps)
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mCachedEncoder)
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mUndoManager)
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mDocumentTimeline)
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mPendingAnimationTracker)
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mTemplateContentsOwner)
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mChildrenCollection)
-  NS_IMPL_CYCLE_COLLECTION_UNLINK(mRegistry)
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mMasterDocument)
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mOrientationPendingPromise)
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mImportManager)
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mSubImportLinks)
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mFontFaceSet)
 
   tmp->mParentDocument = nullptr;
 
@@ -2220,23 +2061,16 @@ nsDocument::ResetToURI(nsIURI *aURI, nsI
       mChildren.RemoveChildAt(i);
       nsNodeUtils::ContentRemoved(this, content, i, previousSibling);
       content->UnbindFromTree();
     }
     mCachedRootElement = nullptr;
   }
   mInUnlinkOrDeletion = oldVal;
 
-  if (!mMasterDocument) {
-    // "When creating an import, use the registry of the master document."
-    // Note: at this point the mMasterDocument is already set for imports
-    // (and only for imports)
-    mRegistry = nullptr;
-  }
-
   // Reset our stylesheets
   ResetStylesheetsToURI(aURI);
 
   // Release the listener manager
   if (mListenerManager) {
     mListenerManager->Disconnect();
     mListenerManager = nullptr;
   }
@@ -4727,20 +4561,16 @@ nsDocument::SetScriptGlobalObject(nsIScr
 #endif
         bool allowDNSPrefetch;
         docShell->GetAllowDNSPrefetch(&allowDNSPrefetch);
         mAllowDNSPrefetch = allowDNSPrefetch;
       }
     }
 
     MaybeRescheduleAnimationFrameNotifications();
-    if (Preferences::GetBool("dom.webcomponents.enabled") ||
-        Preferences::GetBool("dom.webcomponents.customelements.enabled")) {
-      mRegistry = new Registry();
-    }
   }
 
   // Remember the pointer to our window (or lack there of), to avoid
   // having to QI every time it's asked for.
   nsCOMPtr<nsPIDOMWindowInner> window = do_QueryInterface(mScriptGlobalObject);
   mWindow = window;
 
   // Now that we know what our window is, we can flush the CSP errors to the
@@ -5556,36 +5386,39 @@ bool IsLowercaseASCII(const nsAString& a
     char16_t c = aValue[i];
     if (!(0x0061 <= (c) && ((c) <= 0x007a))) {
       return false;
     }
   }
   return true;
 }
 
-CustomElementDefinition*
-nsDocument::LookupCustomElementDefinition(const nsAString& aLocalName,
-                                          uint32_t aNameSpaceID,
-                                          const nsAString* aIs)
-{
-  if (!mRegistry || aNameSpaceID != kNameSpaceID_XHTML) {
+already_AddRefed<mozilla::dom::CustomElementsRegistry>
+nsDocument::GetCustomElementsRegistry()
+{
+  nsAutoString contentType;
+  GetContentType(contentType);
+  if (!IsHTMLDocument() &&
+      !contentType.EqualsLiteral("application/xhtml+xml")) {
     return nullptr;
   }
 
-  nsCOMPtr<nsIAtom> localNameAtom = NS_Atomize(aLocalName);
-  nsCOMPtr<nsIAtom> typeAtom = aIs ? NS_Atomize(*aIs) : localNameAtom;
-
-  CustomElementDefinition* data;
-  CustomElementHashKey key(aNameSpaceID, typeAtom);
-  if (mRegistry->mCustomDefinitions.Get(&key, &data) &&
-      data->mLocalName == localNameAtom) {
-    return data;
-  }
-
-  return nullptr;
+  nsCOMPtr<nsPIDOMWindowInner> window(
+    do_QueryInterface(mScriptGlobalObject ? mScriptGlobalObject
+                                          : GetScopeObject()));
+  if (!window) {
+    return nullptr;
+  }
+
+  RefPtr<CustomElementsRegistry> registry = window->CustomElements();
+  if (!registry) {
+    return nullptr;
+  }
+
+  return registry.forget();
 }
 
 already_AddRefed<Element>
 nsDocument::CreateElement(const nsAString& aTagName,
                           const ElementCreationOptions& aOptions,
                           ErrorResult& rv)
 {
   rv = nsContentUtils::CheckQName(aTagName, false);
@@ -5607,58 +5440,16 @@ nsDocument::CreateElement(const nsAStrin
   }
 
   RefPtr<Element> elem = CreateElem(
     needsLowercase ? lcTagName : aTagName, nullptr, mDefaultElementType, is);
 
   return elem.forget();
 }
 
-void
-nsDocument::SetupCustomElement(Element* aElement,
-                               uint32_t aNamespaceID,
-                               const nsAString* aTypeExtension)
-{
-  if (!mRegistry || aNamespaceID != kNameSpaceID_XHTML) {
-    return;
-  }
-
-  nsCOMPtr<nsIAtom> tagAtom = aElement->NodeInfo()->NameAtom();
-  nsCOMPtr<nsIAtom> typeAtom = aTypeExtension ?
-    NS_Atomize(*aTypeExtension) : tagAtom;
-
-  if (aTypeExtension && !aElement->HasAttr(kNameSpaceID_None, nsGkAtoms::is)) {
-    // Custom element setup in the parser happens after the "is"
-    // attribute is added.
-    aElement->SetAttr(kNameSpaceID_None, nsGkAtoms::is, *aTypeExtension, true);
-  }
-
-  CustomElementDefinition* data = LookupCustomElementDefinition(
-    aElement->NodeInfo()->LocalName(), aNamespaceID, aTypeExtension);
-
-  if (!data) {
-    // The type extension doesn't exist in the registry,
-    // thus we don't need to enqueue callback or adjust
-    // the "is" attribute, but it is possibly an upgrade candidate.
-    RegisterUnresolvedElement(aElement, typeAtom);
-    return;
-  }
-
-  if (data->mLocalName != tagAtom) {
-    // The element doesn't match the local name for the
-    // definition, thus the element isn't a custom element
-    // and we don't need to do anything more.
-    return;
-  }
-
-  // Enqueuing the created callback will set the CustomElementData on the
-  // element, causing prototype swizzling to occur in Element::WrapObject.
-  EnqueueLifecycleCallback(nsIDocument::eCreated, aElement, nullptr, data);
-}
-
 NS_IMETHODIMP
 nsDocument::CreateElementNS(const nsAString& aNamespaceURI,
                             const nsAString& aQualifiedName,
                             nsIDOMElement** aReturn)
 {
   *aReturn = nullptr;
   ElementCreationOptions options;
   ErrorResult rv;
@@ -5908,35 +5699,39 @@ nsDocument::CustomElementConstructor(JSC
   // Function name is the type of the custom element.
   JSString* jsFunName =
     JS_GetFunctionId(JS_ValueToFunction(aCx, args.calleev()));
   nsAutoJSString elemName;
   if (!elemName.init(aCx, jsFunName)) {
     return true;
   }
 
+  RefPtr<mozilla::dom::CustomElementsRegistry> registry = window->CustomElements();
+  if (!registry) {
+    return true;
+  }
+
   nsCOMPtr<nsIAtom> typeAtom(NS_Atomize(elemName));
   CustomElementHashKey key(kNameSpaceID_Unknown, typeAtom);
-  CustomElementDefinition* definition;
-  if (!document->mRegistry ||
-      !document->mRegistry->mCustomDefinitions.Get(&key, &definition)) {
+  CustomElementDefinition* definition = registry->mCustomDefinitions.Get(&key);
+  if (!definition) {
     return true;
   }
 
   nsDependentAtomString localName(definition->mLocalName);
 
   nsCOMPtr<Element> element =
     document->CreateElem(localName, nullptr, definition->mNamespaceID);
   NS_ENSURE_TRUE(element, true);
 
   if (definition->mLocalName != typeAtom) {
     // This element is a custom element by extension, thus we need to
     // do some special setup. For non-extended custom elements, this happens
     // when the element is created.
-    document->SetupCustomElement(element, definition->mNamespaceID, &elemName);
+    nsContentUtils::SetupCustomElement(element, &elemName);
   }
 
   nsresult rv = nsContentUtils::WrapNative(aCx, element, element, args.rval());
   NS_ENSURE_SUCCESS(rv, true);
 
   return true;
 }
 
@@ -5967,238 +5762,28 @@ nsDocument::IsWebComponentsEnabled(JSCon
     NS_ENSURE_SUCCESS(rv, false);
 
     return perm == nsIPermissionManager::ALLOW_ACTION;
   }
 
   return false;
 }
 
-nsresult
-nsDocument::RegisterUnresolvedElement(Element* aElement, nsIAtom* aTypeName)
-{
-  if (!mRegistry) {
-    return NS_OK;
-  }
-
-  mozilla::dom::NodeInfo* info = aElement->NodeInfo();
-
-  // Candidate may be a custom element through extension,
-  // in which case the custom element type name will not
-  // match the element tag name. e.g. <button is="x-button">.
-  nsCOMPtr<nsIAtom> typeName = aTypeName;
-  if (!typeName) {
-    typeName = info->NameAtom();
-  }
-
-  CustomElementHashKey key(info->NamespaceID(), typeName);
-  if (mRegistry->mCustomDefinitions.Get(&key)) {
-    return NS_OK;
-  }
-
-  nsTArray<nsWeakPtr>* unresolved;
-  mRegistry->mCandidatesMap.Get(&key, &unresolved);
-  if (!unresolved) {
-    unresolved = new nsTArray<nsWeakPtr>();
-    // Ownership of unresolved is taken by mCandidatesMap.
-    mRegistry->mCandidatesMap.Put(&key, unresolved);
-  }
-
-  nsWeakPtr* elem = unresolved->AppendElement();
-  *elem = do_GetWeakReference(aElement);
-  aElement->AddStates(NS_EVENT_STATE_UNRESOLVED);
-
-  return NS_OK;
-}
-
-void
-nsDocument::EnqueueLifecycleCallback(nsIDocument::ElementCallbackType aType,
-                                     Element* aCustomElement,
-                                     LifecycleCallbackArgs* aArgs,
-                                     CustomElementDefinition* aDefinition)
-{
-  if (!mRegistry) {
-    // The element might not belong to a document that
-    // has a browsing context, and thus no registry.
-    return;
-  }
-
-  CustomElementData* elementData = aCustomElement->GetCustomElementData();
-
-  // Let DEFINITION be ELEMENT's definition
-  CustomElementDefinition* definition = aDefinition;
-  if (!definition) {
-    mozilla::dom::NodeInfo* info = aCustomElement->NodeInfo();
-
-    // Make sure we get the correct definition in case the element
-    // is a extended custom element e.g. <button is="x-button">.
-    nsCOMPtr<nsIAtom> typeAtom = elementData ?
-      elementData->mType.get() : info->NameAtom();
-
-    CustomElementHashKey key(info->NamespaceID(), typeAtom);
-    if (!mRegistry->mCustomDefinitions.Get(&key, &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;
-    }
-  }
-
-  if (!elementData) {
-    // Create the custom element data the first time
-    // that we try to enqueue a callback.
-    elementData = new CustomElementData(definition->mType);
-    // aCustomElement takes ownership of elementData
-    aCustomElement->SetCustomElementData(elementData);
-    MOZ_ASSERT(aType == nsIDocument::eCreated,
-               "First callback should be the created callback");
-  }
-
-  // Let CALLBACK be the callback associated with the key NAME in CALLBACKS.
-  CallbackFunction* func = nullptr;
-  switch (aType) {
-    case nsIDocument::eCreated:
-      if (definition->mCallbacks->mCreatedCallback.WasPassed()) {
-        func = definition->mCallbacks->mCreatedCallback.Value();
-      }
-      break;
-
-    case nsIDocument::eAttached:
-      if (definition->mCallbacks->mAttachedCallback.WasPassed()) {
-        func = definition->mCallbacks->mAttachedCallback.Value();
-      }
-      break;
-
-    case nsIDocument::eDetached:
-      if (definition->mCallbacks->mDetachedCallback.WasPassed()) {
-        func = definition->mCallbacks->mDetachedCallback.Value();
-      }
-      break;
-
-    case nsIDocument::eAttributeChanged:
-      if (definition->mCallbacks->mAttributeChangedCallback.WasPassed()) {
-        func = definition->mCallbacks->mAttributeChangedCallback.Value();
-      }
-      break;
-  }
-
-  // If there is no such callback, stop.
-  if (!func) {
-    return;
-  }
-
-  if (aType == nsIDocument::eCreated) {
-    elementData->mCreatedCallbackInvoked = false;
-  } else if (!elementData->mCreatedCallbackInvoked) {
-    // Callbacks other than created callback must not be enqueued
-    // until after the created callback has been invoked.
-    return;
-  }
-
-  // Add CALLBACK to ELEMENT's callback queue.
-  CustomElementCallback* callback = new CustomElementCallback(aCustomElement,
-                                                              aType,
-                                                              func,
-                                                              elementData);
-  // Ownership of callback is taken by mCallbackQueue.
-  elementData->mCallbackQueue.AppendElement(callback);
-  if (aArgs) {
-    callback->SetArgs(*aArgs);
-  }
-
-  if (!elementData->mElementIsBeingCreated) {
-    CustomElementData* lastData =
-      sProcessingStack->SafeLastElement(nullptr);
-
-    // 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<int32_t>(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<int32_t>(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<nsIRunnable> runnable =
-        NS_NewRunnableFunction(&nsDocument::ProcessTopElementQueue);
-      nsContentUtils::AddScriptRunner(runnable);
-    }
-  }
-}
-
-// static
-void
-nsDocument::ProcessTopElementQueue()
-{
-  MOZ_ASSERT(nsContentUtils::IsSafeToRunScript());
-
-  nsTArray<RefPtr<CustomElementData>>& 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);
-  }
-}
-
-bool
-nsDocument::RegisterEnabled()
-{
-  static bool sPrefValue =
-    Preferences::GetBool("dom.webcomponents.enabled", false);
-  return sPrefValue;
-}
-
-// static
-Maybe<nsTArray<RefPtr<mozilla::dom::CustomElementData>>>
-nsDocument::sProcessingStack;
-
 void
 nsDocument::RegisterElement(JSContext* aCx, const nsAString& aType,
                             const ElementRegistrationOptions& aOptions,
                             JS::MutableHandle<JSObject*> aRetval,
                             ErrorResult& rv)
 {
-  if (!mRegistry) {
+  RefPtr<CustomElementsRegistry> registry(GetCustomElementsRegistry());
+  if (!registry) {
     rv.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
     return;
   }
 
-  Registry::DefinitionMap& definitions = mRegistry->mCustomDefinitions;
-
   // Unconditionally convert TYPE to lowercase.
   nsAutoString lcType;
   nsContentUtils::ASCIIToLower(aType, lcType);
 
   // Only convert NAME to lowercase in HTML documents. Note that NAME is
   // options.extends.
   nsAutoString lcName;
   if (IsHTMLDocument()) {
@@ -6212,17 +5797,17 @@ nsDocument::RegisterElement(JSContext* a
     rv.Throw(NS_ERROR_DOM_SYNTAX_ERR);
     return;
   }
 
   // If there already exists a definition with the same TYPE, set ERROR to
   // DuplicateDefinition and stop.
   // Note that we need to find existing custom elements from either namespace.
   CustomElementHashKey duplicateFinder(kNameSpaceID_Unknown, typeAtom);
-  if (definitions.Get(&duplicateFinder)) {
+  if (registry->mCustomDefinitions.Get(&duplicateFinder)) {
     rv.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
     return;
   }
 
   nsIGlobalObject* sgo = GetScopeObject();
   if (!sgo) {
     rv.Throw(NS_ERROR_UNEXPECTED);
     return;
@@ -6354,21 +5939,21 @@ nsDocument::RegisterElement(JSContext* a
   LifecycleCallbacks* callbacks = callbacksHolder.forget();
   CustomElementDefinition* definition =
     new CustomElementDefinition(wrappedProto,
                                 typeAtom,
                                 nameAtom,
                                 callbacks,
                                 namespaceID,
                                 0 /* TODO dependent on HTML imports. Bug 877072 */);
-  definitions.Put(&key, definition);
+  registry->mCustomDefinitions.Put(&key, definition);
 
   // Do element upgrade.
   nsAutoPtr<nsTArray<nsWeakPtr>> candidates;
-  mRegistry->mCandidatesMap.RemoveAndForget(&key, candidates);
+  registry->mCandidatesMap.RemoveAndForget(&key, candidates);
   if (candidates) {
     for (size_t i = 0; i < candidates->Length(); ++i) {
       nsCOMPtr<Element> elem = do_QueryReferent(candidates->ElementAt(i));
       if (!elem) {
         continue;
       }
 
       elem->RemoveStates(NS_EVENT_STATE_UNRESOLVED);
@@ -6392,17 +5977,20 @@ nsDocument::RegisterElement(JSContext* a
       // thus only visible in the wrapper's compartment.
       JS::RootedObject wrapper(aCx);
       if ((wrapper = cache->GetWrapper()) && JS_WrapObject(aCx, &wrapper)) {
         if (!JS_SetPrototype(aCx, wrapper, wrappedProto)) {
           continue;
         }
       }
 
-      EnqueueLifecycleCallback(nsIDocument::eCreated, elem, nullptr, definition);
+      if (GetDocShell()) {
+        nsContentUtils::EnqueueLifecycleCallback(
+          this, nsIDocument::eCreated, elem, nullptr, definition);
+      }
     }
   }
 
   JS::Rooted<JSFunction*> constructor(aCx);
 
   {
     // Go into the document's global compartment when creating the constructor
     // function because we want to get the correct document (where the
@@ -6430,24 +6018,16 @@ nsDocument::RegisterElement(JSContext* a
   if (!JS_LinkConstructorAndPrototype(aCx, wrappedConstructor, protoObject)) {
     rv.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
     return;
   }
 
   aRetval.set(wrappedConstructor);
 }
 
-void
-nsDocument::UseRegistryFromDocument(nsIDocument* aDocument)
-{
-  nsDocument* doc = static_cast<nsDocument*>(aDocument);
-  MOZ_ASSERT(!mRegistry, "There should be no existing registry.");
-  mRegistry = doc->mRegistry;
-}
-
 NS_IMETHODIMP
 nsDocument::GetElementsByTagName(const nsAString& aTagname,
                                  nsIDOMNodeList** aReturn)
 {
   RefPtr<nsContentList> list = GetElementsByTagName(aTagname);
   NS_ENSURE_TRUE(list, NS_ERROR_OUT_OF_MEMORY);
 
   // transfer ref to aReturn
@@ -8982,18 +8562,16 @@ nsDocument::Destroy()
   mInUnlinkOrDeletion = oldVal;
 
   mLayoutHistoryState = nullptr;
 
   // Shut down our external resource map.  We might not need this for
   // leak-fixing if we fix nsDocumentViewer to do cycle-collection, but
   // tearing down all those frame trees right now is the right thing to do.
   mExternalResourceMap.Shutdown();
-
-  mRegistry = nullptr;
 }
 
 void
 nsDocument::RemovedFromDocShell()
 {
   if (mRemovedFromDocShell)
     return;
 
@@ -12573,22 +12151,16 @@ nsDocument::OnAppThemeChanged()
     }
     bool willNotify;
     bool isAlternate;
     link->UpdateStyleSheet(nullptr, &willNotify, &isAlternate, true);
   }
 }
 
 void
-nsDocument::XPCOMShutdown()
-{
-  sProcessingStack.reset();
-}
-
-void
 nsDocument::UpdateVisibilityState()
 {
   dom::VisibilityState oldState = mVisibilityState;
   mVisibilityState = GetVisibilityState();
   if (oldState != mVisibilityState) {
     nsContentUtils::DispatchTrustedEvent(this, static_cast<nsIDocument*>(this),
                                          NS_LITERAL_STRING("visibilitychange"),
                                          /* bubbles = */ true,
@@ -13462,14 +13034,15 @@ nsDocument::CheckCustomElementName(const
   if (!aOptions.mIs.WasPassed() ||
       !Preferences::GetBool("dom.webcomponents.enabled")) {
       return nullptr;
   }
 
   nsString* is = const_cast<nsString*>(&(aOptions.mIs.Value()));
 
   // Throw NotFoundError if 'is' is not-null and definition is null
-  if (!LookupCustomElementDefinition(aLocalName, aNamespaceID, is)) {
+  if (!nsContentUtils::LookupCustomElementDefinition(this, aLocalName,
+                                                     aNamespaceID, is)) {
       rv.Throw(NS_ERROR_DOM_NOT_FOUND_ERR);
   }
 
   return is;
 }
--- a/dom/base/nsDocument.h
+++ b/dom/base/nsDocument.h
@@ -258,117 +258,16 @@ private:
   nsTArray<Element*> mIdContentList;
   RefPtr<nsBaseContentList> mNameContentList;
   nsAutoPtr<nsTHashtable<ChangeCallbackEntry> > mChangeCallbacks;
   RefPtr<Element> mImageElement;
 };
 
 namespace mozilla {
 namespace dom {
-struct LifecycleCallbackArgs
-{
-  nsString name;
-  nsString oldValue;
-  nsString newValue;
-};
-
-struct CustomElementData;
-
-class CustomElementCallback
-{
-public:
-  CustomElementCallback(Element* aThisObject,
-                        nsIDocument::ElementCallbackType aCallbackType,
-                        mozilla::dom::CallbackFunction* aCallback,
-                        CustomElementData* aOwnerData);
-  void Traverse(nsCycleCollectionTraversalCallback& aCb) const;
-  void Call();
-  void SetArgs(LifecycleCallbackArgs& aArgs)
-  {
-    MOZ_ASSERT(mType == nsIDocument::eAttributeChanged,
-               "Arguments are only used by attribute changed callback.");
-    mArgs = aArgs;
-  }
-
-private:
-  // The this value to use for invocation of the callback.
-  RefPtr<mozilla::dom::Element> mThisObject;
-  RefPtr<mozilla::dom::CallbackFunction> mCallback;
-  // The type of callback (eCreated, eAttached, etc.)
-  nsIDocument::ElementCallbackType mType;
-  // Arguments to be passed to the callback,
-  // used by the attribute changed callback.
-  LifecycleCallbackArgs mArgs;
-  // CustomElementData that contains this callback in the
-  // callback queue.
-  CustomElementData* mOwnerData;
-};
-
-// Each custom element has an associated callback queue and an element is
-// being created flag.
-struct CustomElementData
-{
-  NS_INLINE_DECL_REFCOUNTING(CustomElementData)
-
-  explicit CustomElementData(nsIAtom* aType);
-  // Objects in this array are transient and empty after each microtask
-  // checkpoint.
-  nsTArray<nsAutoPtr<CustomElementCallback>> mCallbackQueue;
-  // Custom element type, for <button is="x-button"> or <x-button>
-  // this would be x-button.
-  nsCOMPtr<nsIAtom> mType;
-  // The callback that is next to be processed upon calling RunCallbackQueue.
-  int32_t mCurrentCallback;
-  // Element is being created flag as described in the custom elements spec.
-  bool mElementIsBeingCreated;
-  // Flag to determine if the created callback has been invoked, thus it
-  // determines if other callbacks can be enqueued.
-  bool mCreatedCallbackInvoked;
-  // The microtask level associated with the callbacks in the callback queue,
-  // it is used to determine if a new queue needs to be pushed onto the
-  // processing stack.
-  int32_t mAssociatedMicroTask;
-
-  // Empties the callback queue.
-  void RunCallbackQueue();
-
-private:
-  virtual ~CustomElementData() {}
-};
-
-class Registry : public nsISupports
-{
-public:
-  friend class ::nsDocument;
-
-  NS_DECL_CYCLE_COLLECTING_ISUPPORTS
-  NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(Registry)
-
-  Registry();
-
-protected:
-  virtual ~Registry();
-
-  typedef nsClassHashtable<mozilla::dom::CustomElementHashKey,
-                           mozilla::dom::CustomElementDefinition>
-    DefinitionMap;
-  typedef nsClassHashtable<mozilla::dom::CustomElementHashKey,
-                           nsTArray<nsWeakPtr>>
-    CandidateMap;
-
-  // Hashtable for custom element definitions in web components.
-  // Custom prototypes are stored in the compartment where
-  // registerElement was called.
-  DefinitionMap mCustomDefinitions;
-
-  // 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;
-};
 
 } // namespace dom
 } // namespace mozilla
 
 class nsDocHeaderData
 {
 public:
   nsDocHeaderData(nsIAtom* aField, const nsAString& aData)
@@ -1213,46 +1112,16 @@ public:
   // to notify window when the page was first visited.
   void MaybeActiveMediaComponents();
 
   virtual void DocAddSizeOfExcludingThis(nsWindowSizes* aWindowSizes) const override;
   // DocAddSizeOfIncludingThis is inherited from nsIDocument.
 
   virtual nsIDOMNode* AsDOMNode() override { return this; }
 
-  virtual void EnqueueLifecycleCallback(nsIDocument::ElementCallbackType aType,
-                                        Element* aCustomElement,
-                                        mozilla::dom::LifecycleCallbackArgs* aArgs = nullptr,
-                                        mozilla::dom::CustomElementDefinition* aDefinition = nullptr) override;
-
-  static void ProcessTopElementQueue();
-
-  void GetCustomPrototype(int32_t aNamespaceID,
-                          nsIAtom* aAtom,
-                          JS::MutableHandle<JSObject*> prototype)
-  {
-    if (!mRegistry) {
-      prototype.set(nullptr);
-      return;
-    }
-
-    mozilla::dom::CustomElementHashKey key(aNamespaceID, aAtom);
-    mozilla::dom::CustomElementDefinition* definition;
-    if (mRegistry->mCustomDefinitions.Get(&key, &definition)) {
-      prototype.set(definition->mPrototype);
-    } else {
-      prototype.set(nullptr);
-    }
-  }
-
-  static bool RegisterEnabled();
-
-  virtual nsresult RegisterUnresolvedElement(mozilla::dom::Element* aElement,
-                                             nsIAtom* aTypeName = nullptr) override;
-
   // WebIDL bits
   virtual mozilla::dom::DOMImplementation*
     GetImplementation(mozilla::ErrorResult& rv) override;
   virtual void
     RegisterElement(JSContext* aCx, const nsAString& aName,
                     const mozilla::dom::ElementRegistrationOptions& aOptions,
                     JS::MutableHandle<JSObject*> aRetval,
                     mozilla::ErrorResult& rv) override;
@@ -1263,29 +1132,27 @@ public:
   virtual void EnableStyleSheetsForSet(const nsAString& aSheetSet) override;
   virtual already_AddRefed<Element> CreateElement(const nsAString& aTagName,
                                                   const mozilla::dom::ElementCreationOptions& aOptions,
                                                   ErrorResult& rv) override;
   virtual already_AddRefed<Element> CreateElementNS(const nsAString& aNamespaceURI,
                                                     const nsAString& aQualifiedName,
                                                     const mozilla::dom::ElementCreationOptions& aOptions,
                                                     mozilla::ErrorResult& rv) override;
-  virtual void UseRegistryFromDocument(nsIDocument* aDocument) override;
 
   virtual nsIDocument* MasterDocument() override
   {
     return mMasterDocument ? mMasterDocument.get()
                            : this;
   }
 
   virtual void SetMasterDocument(nsIDocument* master) override
   {
     MOZ_ASSERT(master);
     mMasterDocument = master;
-    UseRegistryFromDocument(mMasterDocument);
   }
 
   virtual bool IsMasterDocument() override
   {
     return !mMasterDocument;
   }
 
   virtual mozilla::dom::ImportManager* ImportManager() override
@@ -1392,18 +1259,16 @@ protected:
   Element* GetTitleElement();
 
 public:
   // Get our title
   virtual void GetTitle(nsString& aTitle) override;
   // Set our title
   virtual void SetTitle(const nsAString& aTitle, mozilla::ErrorResult& rv) override;
 
-  static void XPCOMShutdown();
-
   bool mIsTopLevelContentDocument: 1;
   bool mIsContentDocument: 1;
 
   bool IsTopLevelContentDocument();
   void SetIsTopLevelContentDocument(bool aIsTopLevelContentDocument);
 
   bool IsContentDocument() const;
   void SetIsContentDocument(bool aIsContentDocument);
@@ -1498,60 +1363,38 @@ protected:
   // pop one off this stack, restoring the previous full-screen state
   nsTArray<nsWeakPtr> mFullScreenStack;
 
   // The root of the doc tree in which this document is in. This is only
   // non-null when this document is in fullscreen mode.
   nsWeakPtr mFullscreenRoot;
 
 private:
-  // Array representing the processing stack in the custom elements
-  // specification. The processing stack is conceptually a stack of
-  // element queues. Each queue is represented by a sequence of
-  // CustomElementData in this array, separated by nullptr that
-  // represent the boundaries of the items in the stack. The first
-  // queue in the stack is the base element queue.
-  static mozilla::Maybe<nsTArray<RefPtr<mozilla::dom::CustomElementData>>> sProcessingStack;
-
   static bool CustomElementConstructor(JSContext* aCx, unsigned aArgc, JS::Value* aVp);
 
   /**
-   * Looking up a custom element definition.
-   * https://html.spec.whatwg.org/#look-up-a-custom-element-definition
-   */
-  mozilla::dom::CustomElementDefinition* LookupCustomElementDefinition(
-    const nsAString& aLocalName, uint32_t aNameSpaceID, const nsAString* aIs);
-
-  /**
    * Check if the passed custom element name, aOptions.mIs, is a registered
    * custom element type or not, then return the custom element name for future
    * usage.
    *
    * If there is no existing custom element definition for this name, throw a
    * NotFoundError.
    */
   nsString* CheckCustomElementName(
     const mozilla::dom::ElementCreationOptions& aOptions,
     const nsAString& aLocalName,
     uint32_t aNamespaceID,
     ErrorResult& rv);
 
 public:
-  // Enqueue created callback or register upgrade candidate for
-  // newly created custom elements, possibly extending an existing type.
-  // ex. <x-button>, <button is="x-button> (type extension)
-  virtual void SetupCustomElement(Element* aElement,
-                                  uint32_t aNamespaceID,
-                                  const nsAString* aTypeExtension) override;
+  virtual already_AddRefed<mozilla::dom::CustomElementsRegistry>
+    GetCustomElementsRegistry() override;
 
   static bool IsWebComponentsEnabled(JSContext* aCx, JSObject* aObject);
 
-  // The "registry" from the web components spec.
-  RefPtr<mozilla::dom::Registry> mRegistry;
-
   RefPtr<mozilla::EventListenerManager> mListenerManager;
   RefPtr<mozilla::dom::StyleSheetList> mDOMStyleSheets;
   RefPtr<nsDOMStyleSheetSetList> mStyleSheetSetList;
   RefPtr<nsScriptLoader> mScriptLoader;
   nsDocHeaderData* mHeaderData;
   /* mIdentifierMap works as follows for IDs:
    * 1) Attribute changes affect the table immediately (removing and adding
    *    entries as needed).
--- a/dom/base/nsIDocument.h
+++ b/dom/base/nsIDocument.h
@@ -2477,44 +2477,23 @@ public:
     eCreated,
     eAttached,
     eDetached,
     eAttributeChanged
   };
 
   nsIDocument* GetTopLevelContentDocument();
 
-  /**
-   * Registers an unresolved custom element that is a candidate for
-   * upgrade when the definition is registered via registerElement.
-   * |aTypeName| is the name of the custom element type, if it is not
-   * provided, then element name is used. |aTypeName| should be provided
-   * when registering a custom element that extends an existing
-   * element. e.g. <button is="x-button">.
-   */
-  virtual nsresult RegisterUnresolvedElement(Element* aElement,
-                                             nsIAtom* aTypeName = nullptr) = 0;
-  virtual void EnqueueLifecycleCallback(ElementCallbackType aType,
-                                        Element* aCustomElement,
-                                        mozilla::dom::LifecycleCallbackArgs* aArgs = nullptr,
-                                        mozilla::dom::CustomElementDefinition* aDefinition = nullptr) = 0;
-  virtual void SetupCustomElement(Element* aElement,
-                                  uint32_t aNamespaceID,
-                                  const nsAString* aTypeExtension = nullptr) = 0;
   virtual void
     RegisterElement(JSContext* aCx, const nsAString& aName,
                     const mozilla::dom::ElementRegistrationOptions& aOptions,
                     JS::MutableHandle<JSObject*> aRetval,
                     mozilla::ErrorResult& rv) = 0;
-
-  /**
-   * In some cases, new document instances must be associated with
-   * an existing web components custom element registry as specified.
-   */
-  virtual void UseRegistryFromDocument(nsIDocument* aDocument) = 0;
+  virtual already_AddRefed<mozilla::dom::CustomElementsRegistry>
+    GetCustomElementsRegistry() = 0;
 
   already_AddRefed<nsContentList>
   GetElementsByTagName(const nsAString& aTagName)
   {
     return NS_GetContentList(this, kNameSpaceID_Unknown, aTagName);
   }
   already_AddRefed<nsContentList>
     GetElementsByTagNameNS(const nsAString& aNamespaceURI,
--- a/dom/base/nsNodeUtils.cpp
+++ b/dom/base/nsNodeUtils.cpp
@@ -466,25 +466,24 @@ nsNodeUtils::CloneAndAdopt(nsINode *aNod
     rv = aNode->Clone(nodeInfo, getter_AddRefs(clone));
     NS_ENSURE_SUCCESS(rv, rv);
 
     if (clone->IsElement()) {
       // The cloned node may be a custom element that may require
       // enqueing created callback and prototype swizzling.
       Element* elem = clone->AsElement();
       if (nsContentUtils::IsCustomElementName(nodeInfo->NameAtom())) {
-        elem->OwnerDoc()->SetupCustomElement(elem, nodeInfo->NamespaceID());
+        nsContentUtils::SetupCustomElement(elem);
       } else {
         // Check if node may be custom element by type extension.
         // ex. <button is="x-button">
         nsAutoString extension;
         if (elem->GetAttr(kNameSpaceID_None, nsGkAtoms::is, extension) &&
             !extension.IsEmpty()) {
-          elem->OwnerDoc()->SetupCustomElement(elem, nodeInfo->NamespaceID(),
-                                               &extension);
+          nsContentUtils::SetupCustomElement(elem, &extension);
         }
       }
     }
 
     if (aParent) {
       // If we're cloning we need to insert the cloned children into the cloned
       // parent.
       rv = aParent->AppendChildTo(static_cast<nsIContent*>(clone.get()),
--- a/dom/html/nsHTMLContentSink.cpp
+++ b/dom/html/nsHTMLContentSink.cpp
@@ -252,24 +252,22 @@ NS_NewHTMLElement(Element** aResult, alr
                "Trying to HTML elements that don't have the XHTML namespace");
 
   // Per the Custom Element specification, unknown tags that are valid custom
   // element names should be HTMLElement instead of HTMLUnknownElement.
   int32_t tag = parserService->HTMLCaseSensitiveAtomTagToId(name);
   if ((tag == eHTMLTag_userdefined &&
       nsContentUtils::IsCustomElementName(name)) ||
       aIs) {
-    nsIDocument* doc = nodeInfo->GetDocument();
-
     NS_IF_ADDREF(*aResult = NS_NewHTMLElement(nodeInfo.forget(), aFromParser));
     if (!*aResult) {
       return NS_ERROR_OUT_OF_MEMORY;
     }
 
-    doc->SetupCustomElement(*aResult, kNameSpaceID_XHTML, aIs);
+    nsContentUtils::SetupCustomElement(*aResult, aIs);
 
     return NS_OK;
   }
 
   *aResult = CreateHTMLElement(tag,
                                nodeInfo.forget(), aFromParser).take();
   return *aResult ? NS_OK : NS_ERROR_OUT_OF_MEMORY;
 }
--- a/dom/tests/mochitest/webcomponents/test_document_shared_registry.html
+++ b/dom/tests/mochitest/webcomponents/test_document_shared_registry.html
@@ -24,31 +24,16 @@ function createdCallbackFromMainDoc() {
     createdCallbackCalled = true;
     runNextTest();
   };
 
   assocDoc.registerElement("x-associated-doc-callback-elem", { prototype: proto });
   document.createElement("x-associated-doc-callback-elem");
 }
 
-function createdCallbackFromAssociatedDoc() {
-  var createdCallbackCalled = false;
-  var assocDoc = document.implementation.createHTMLDocument();
-
-  var proto = Object.create(HTMLElement.prototype);
-  proto.createdCallback = function() {
-    is(createdCallbackCalled, false, "created callback should only be called once in this tests.");
-    createdCallbackCalled = true;
-    runNextTest();
-  };
-
-  assocDoc.registerElement("x-main-doc-callback-elem", { prototype: proto });
-  assocDoc.createElement("x-main-doc-callback-elem");
-}
-
 function createdCallbackFromDocHTMLNamespace() {
   var createdCallbackCalled = false;
   var assocDoc = document.implementation.createDocument("http://www.w3.org/1999/xhtml", "html", null);
   var somediv = assocDoc.createElement("div");
 
   var proto = Object.create(HTMLElement.prototype);
   proto.createdCallback = function() {
     is(createdCallbackCalled, false, "created callback should only be called once in this tests.");
@@ -75,17 +60,16 @@ function registerNoRegistryDoc() {
 function runNextTest() {
   if (testFunctions.length > 0) {
     var nextTestFunction = testFunctions.shift();
     nextTestFunction();
   }
 }
 
 var testFunctions = [
-  createdCallbackFromAssociatedDoc,
   createdCallbackFromMainDoc,
   createdCallbackFromDocHTMLNamespace,
   registerNoRegistryDoc,
   SimpleTest.finish
 ];
 
 SimpleTest.waitForExplicitFinish();
 
--- a/layout/build/nsLayoutStatics.cpp
+++ b/layout/build/nsLayoutStatics.cpp
@@ -111,20 +111,20 @@ using namespace mozilla::system;
 #include "nsHyphenationManager.h"
 #include "nsEditorSpellCheck.h"
 #include "nsWindowMemoryReporter.h"
 #include "mozilla/dom/ContentParent.h"
 #include "mozilla/ProcessPriorityManager.h"
 #include "nsPermissionManager.h"
 #include "nsCookieService.h"
 #include "nsApplicationCacheService.h"
+#include "mozilla/dom/CustomElementsRegistry.h"
 #include "mozilla/dom/time/DateCacheCleaner.h"
 #include "mozilla/EventDispatcher.h"
 #include "mozilla/IMEStateManager.h"
-#include "nsDocument.h"
 #include "mozilla/dom/HTMLVideoElement.h"
 #include "CameraPreferences.h"
 #include "TouchManager.h"
 #include "MediaDecoder.h"
 #include "MediaPrefs.h"
 #include "mozilla/dom/devicestorage/DeviceStorageStatics.h"
 #include "mozilla/ServoBindings.h"
 #include "mozilla/StaticPresData.h"
@@ -432,17 +432,17 @@ nsLayoutStatics::Shutdown()
 
   nsHyphenationManager::Shutdown();
   nsDOMMutationObserver::Shutdown();
 
   ContentParent::ShutDown();
 
   DisplayItemClip::Shutdown();
 
-  nsDocument::XPCOMShutdown();
+  CustomElementsRegistry::XPCOMShutdown();
 
   CacheObserver::Shutdown();
 
   CameraPreferences::Shutdown();
 
   PromiseDebugging::Shutdown();
 
 #ifdef MOZ_B2G_BT
--- a/parser/html/nsHtml5TreeOperation.cpp
+++ b/parser/html/nsHtml5TreeOperation.cpp
@@ -435,19 +435,17 @@ nsHtml5TreeOperation::CreateElement(int3
       newContent->SetAttr(nsuri,
                           localName,
                           prefix,
                           value,
                           false);
 
       // Custom element setup may be needed if there is an "is" attribute.
       if (kNameSpaceID_None == nsuri && !prefix && nsGkAtoms::is == localName) {
-        newContent->OwnerDoc()->SetupCustomElement(newContent,
-                                                   newContent->GetNameSpaceID(),
-                                                   &value);
+        nsContentUtils::SetupCustomElement(newContent, &value);
       }
     }
   }
   return newContent;
 }
 
 void
 nsHtml5TreeOperation::SetFormElement(nsIContent* aNode, nsIContent* aParent)