Bug 1275835 - Part 2: Implement CustomElementsRegistry define function; r=wchen
authorEdgar Chen <echen@mozilla.com>
Thu, 01 Sep 2016 15:11:32 +0800
changeset 312394 9cc407277fd0e820882a760676144cd07f7c92ec
parent 312393 1f2f37ec0fd3bd0156c504ccc6aeb05e0f973dc0
child 312395 04ddfc508fd59cca526f332ec7b45c9725fb9e58
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 2: Implement CustomElementsRegistry define function; r=wchen MozReview-Commit-ID: 10NPmzTRogc
dom/base/CustomElementsRegistry.cpp
dom/base/CustomElementsRegistry.h
dom/base/Element.h
dom/base/nsDocument.cpp
dom/bindings/Errors.msg
dom/tests/mochitest/webcomponents/mochitest.ini
dom/tests/mochitest/webcomponents/test_custom_element_register_invalid_callbacks.html
testing/web-platform/meta/custom-elements/custom-elements-registry/define.html.ini
--- a/dom/base/CustomElementsRegistry.cpp
+++ b/dom/base/CustomElementsRegistry.cpp
@@ -2,17 +2,20 @@
 /* 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/HTMLElementBinding.h"
 #include "mozilla/dom/WebComponentsBinding.h"
+#include "nsIParserService.h"
+#include "jsapi.h"
 
 namespace mozilla {
 namespace dom {
 
 void
 CustomElementCallback::Call()
 {
   ErrorResult rv;
@@ -133,16 +136,19 @@ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(
     }
   }
   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()->mConstructor,
+                     "mCustomDefinitions constructor",
+                     aClosure);
     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)
@@ -170,18 +176,18 @@ 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")) {
+  if (!Preferences::GetBool("dom.webcomponents.customelements.enabled") &&
+      !Preferences::GetBool("dom.webcomponents.enabled")) {
     return nullptr;
   }
 
   RefPtr<CustomElementsRegistry> customElementsRegistry =
     new CustomElementsRegistry(aWindow);
   return customElementsRegistry.forget();
 }
 
@@ -219,16 +225,17 @@ CustomElementsRegistry::XPCOMShutdown()
   sProcessingStack.reset();
 }
 
 /* static */ Maybe<nsTArray<RefPtr<CustomElementData>>>
 CustomElementsRegistry::sProcessingStack;
 
 CustomElementsRegistry::CustomElementsRegistry(nsPIDOMWindowInner* aWindow)
  : mWindow(aWindow)
+ , mIsCustomDefinitionRunning(false)
 {
   mozilla::HoldJSObjects(this);
 
   if (!sProcessingStack) {
     sProcessingStack.emplace();
     // Add the base queue sentinel to the processing stack.
     sProcessingStack->AppendElement((CustomElementData*) nullptr);
   }
@@ -241,18 +248,17 @@ CustomElementsRegistry::~CustomElementsR
 
 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);
+  CustomElementDefinition* data = mCustomDefinitions.Get(typeAtom);
   if (data && data->mLocalName == localNameAtom) {
     return data;
   }
 
   return nullptr;
 }
 
 void
@@ -263,28 +269,21 @@ CustomElementsRegistry::RegisterUnresolv
   // 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)) {
+  if (mCustomDefinitions.Get(typeName)) {
     return;
   }
 
-  nsTArray<nsWeakPtr>* unresolved = mCandidatesMap.Get(&key);
-  if (!unresolved) {
-    unresolved = new nsTArray<nsWeakPtr>();
-    // Ownership of unresolved is taken by customElements.
-    mCandidatesMap.Put(&key, unresolved);
-  }
-
+  nsTArray<nsWeakPtr>* unresolved = mCandidatesMap.LookupOrAdd(typeName);
   nsWeakPtr* elem = unresolved->AppendElement();
   *elem = do_GetWeakReference(aElement);
   aElement->AddStates(NS_EVENT_STATE_UNRESOLVED);
 
   return;
 }
 
 void
@@ -337,18 +336,17 @@ CustomElementsRegistry::EnqueueLifecycle
   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);
+    definition = mCustomDefinitions.Get(typeAtom);
     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) {
@@ -448,44 +446,354 @@ CustomElementsRegistry::EnqueueLifecycle
     }
   }
 }
 
 void
 CustomElementsRegistry::GetCustomPrototype(nsIAtom* aAtom,
                                            JS::MutableHandle<JSObject*> aPrototype)
 {
-  mozilla::dom::CustomElementHashKey key(kNameSpaceID_XHTML, aAtom);
-  mozilla::dom::CustomElementDefinition* definition = mCustomDefinitions.Get(&key);
+  mozilla::dom::CustomElementDefinition* definition = mCustomDefinitions.Get(aAtom);
   if (definition) {
     aPrototype.set(definition->mPrototype);
   } else {
     aPrototype.set(nullptr);
   }
 }
 
+void
+CustomElementsRegistry::UpgradeCandidates(JSContext* aCx,
+                                          nsIAtom* aKey,
+                                          CustomElementDefinition* aDefinition)
+{
+  nsAutoPtr<nsTArray<nsWeakPtr>> candidates;
+  mCandidatesMap.RemoveAndForget(aKey, 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);
+
+      // Make sure that the element name matches the name in the definition.
+      // (e.g. a definition for x-button extending button should match
+      // <button is="x-button"> but not <x-button>.
+      if (elem->NodeInfo()->NameAtom() != aDefinition->mLocalName) {
+        //Skip over this element because definition does not apply.
+        continue;
+      }
+
+      MOZ_ASSERT(elem->IsHTMLElement(aDefinition->mLocalName));
+      nsWrapperCache* cache;
+      CallQueryInterface(elem, &cache);
+      MOZ_ASSERT(cache, "Element doesn't support wrapper cache?");
+
+      // We want to set the custom prototype in the caller's comparment.
+      // In the case that element is in a different compartment,
+      // this will set the prototype on the element's wrapper and
+      // thus only visible in the wrapper's compartment.
+      JS::RootedObject wrapper(aCx);
+      JS::Rooted<JSObject*> prototype(aCx, aDefinition->mPrototype);
+      if ((wrapper = cache->GetWrapper()) && JS_WrapObject(aCx, &wrapper)) {
+        if (!JS_SetPrototype(aCx, wrapper, prototype)) {
+          continue;
+        }
+      }
+
+      nsContentUtils::EnqueueLifecycleCallback(
+        elem->OwnerDoc(), nsIDocument::eCreated, elem, nullptr, aDefinition);
+    }
+  }
+}
+
 JSObject*
 CustomElementsRegistry::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
 {
   return CustomElementsRegistryBinding::Wrap(aCx, this, aGivenProto);
 }
 
 nsISupports* CustomElementsRegistry::GetParentObject() const
 {
   return mWindow;
 }
 
+static const char* kLifeCycleCallbackNames[] = {
+  "connectedCallback",
+  "disconnectedCallback",
+  "adoptedCallback",
+  "attributeChangedCallback",
+  // The life cycle callbacks from v0 spec.
+  "createdCallback",
+  "attachedCallback",
+  "detachedCallback"
+};
+
+static void
+CheckLifeCycleCallbacks(JSContext* aCx,
+                        JS::Handle<JSObject*> aConstructor,
+                        ErrorResult& aRv)
+{
+  for (size_t i = 0; i < ArrayLength(kLifeCycleCallbackNames); ++i) {
+    const char* callbackName = kLifeCycleCallbackNames[i];
+    JS::Rooted<JS::Value> callbackValue(aCx);
+    if (!JS_GetProperty(aCx, aConstructor, callbackName, &callbackValue)) {
+      aRv.StealExceptionFromJSContext(aCx);
+      return;
+    }
+    if (!callbackValue.isUndefined()) {
+      if (!callbackValue.isObject()) {
+        aRv.ThrowTypeError<MSG_NOT_OBJECT>(NS_ConvertASCIItoUTF16(callbackName));
+        return;
+      }
+      JS::Rooted<JSObject*> callback(aCx, &callbackValue.toObject());
+      if (!JS::IsCallable(callback)) {
+        aRv.ThrowTypeError<MSG_NOT_CALLABLE>(NS_ConvertASCIItoUTF16(callbackName));
+        return;
+      }
+    }
+  }
+}
+
+// https://html.spec.whatwg.org/multipage/scripting.html#element-definition
 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);
+  aRv.MightThrowJSException();
+
+  AutoJSAPI jsapi;
+  if (NS_WARN_IF(!jsapi.Init(mWindow))) {
+    aRv.Throw(NS_ERROR_FAILURE);
+    return;
+  }
+
+  JSContext *cx = jsapi.cx();
+  JS::Rooted<JSObject*> constructor(cx, aFunctionConstructor.Callable());
+
+  /**
+   * 1. If IsConstructor(constructor) is false, then throw a TypeError and abort
+   *    these steps.
+   */
+  // For now, all wrappers are constructable if they are callable. So we need to
+  // unwrap constructor to check it is really constructable.
+  JS::Rooted<JSObject*> constructorUnwrapped(cx, js::CheckedUnwrap(constructor));
+  if (!constructorUnwrapped) {
+    // If the caller's compartment does not have permission to access the
+    // unwrapped constructor then throw.
+    aRv.Throw(NS_ERROR_DOM_SECURITY_ERR);
+    return;
+  }
+
+  if (!JS::IsConstructor(constructorUnwrapped)) {
+    aRv.ThrowTypeError<MSG_NOT_CONSTRUCTOR>(NS_LITERAL_STRING("Argument 2 of CustomElementsRegistry.define"));
+    return;
+  }
+
+  /**
+   * 2. If name is not a valid custom element name, then throw a "SyntaxError"
+   *    DOMException and abort these steps.
+   */
+  nsCOMPtr<nsIAtom> nameAtom(NS_Atomize(aName));
+  if (!nsContentUtils::IsCustomElementName(nameAtom)) {
+    aRv.Throw(NS_ERROR_DOM_SYNTAX_ERR);
+    return;
+  }
+
+  /**
+   * 3. If this CustomElementsRegistry contains an entry with name name, then
+   *    throw a "NotSupportedError" DOMException and abort these steps.
+   */
+  if (mCustomDefinitions.Get(nameAtom)) {
+    aRv.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
+    return;
+  }
+
+  /**
+   * 4. If this CustomElementsRegistry contains an entry with constructor constructor,
+   *    then throw a "NotSupportedError" DOMException and abort these steps.
+   */
+  // TODO: Step 3 of HTMLConstructor also needs a way to look up definition by
+  // using constructor. So I plans to figure out a solution to support both of
+  // them in bug 1274159.
+
+  /**
+   * 5. Let localName be name.
+   * 6. Let extends be the value of the extends member of options, or null if
+   *    no such member exists.
+   * 7. If extends is not null, then:
+   *    1. If extends is a valid custom element name, then throw a
+   *       "NotSupportedError" DOMException.
+   *    2. If the element interface for extends and the HTML namespace is
+   *       HTMLUnknownElement (e.g., if extends does not indicate an element
+   *       definition in this specification), then throw a "NotSupportedError"
+   *       DOMException.
+   *    3. Set localName to extends.
+   */
+  nsAutoString localName(aName);
+  if (aOptions.mExtends.WasPassed()) {
+    nsCOMPtr<nsIAtom> extendsAtom(NS_Atomize(aOptions.mExtends.Value()));
+    if (nsContentUtils::IsCustomElementName(extendsAtom)) {
+      aRv.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
+      return;
+    }
+
+    nsIParserService* ps = nsContentUtils::GetParserService();
+    if (!ps) {
+      aRv.Throw(NS_ERROR_UNEXPECTED);
+      return;
+    }
+
+    // bgsound and multicol are unknown html element.
+    int32_t tag = ps->HTMLCaseSensitiveAtomTagToId(extendsAtom);
+    if (tag == eHTMLTag_userdefined ||
+        tag == eHTMLTag_bgsound ||
+        tag == eHTMLTag_multicol) {
+      aRv.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
+      return;
+    }
+
+    localName.Assign(aOptions.mExtends.Value());
+  }
+
+  /**
+   * 8. If this CustomElementRegistry's element definition is running flag is set,
+   *    then throw a "NotSupportedError" DOMException and abort these steps.
+   */
+  if (mIsCustomDefinitionRunning) {
+    aRv.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
+    return;
+  }
+
+  JS::Rooted<JSObject*> constructorPrototype(cx);
+  nsAutoPtr<LifecycleCallbacks> callbacksHolder(new LifecycleCallbacks());
+  { // Set mIsCustomDefinitionRunning.
+    /**
+     * 9. Set this CustomElementRegistry's element definition is running flag.
+     */
+    AutoSetRunningFlag as(this);
+
+    { // Enter constructor's compartment.
+      /**
+       * 10.1. Let prototype be Get(constructor, "prototype"). Rethrow any exceptions.
+       */
+      JSAutoCompartment ac(cx, constructor);
+      JS::Rooted<JS::Value> prototypev(cx);
+      // The .prototype on the constructor passed from document.registerElement
+      // is the "expando" of a wrapper. So we should get it from wrapper instead
+      // instead of underlying object.
+      if (!JS_GetProperty(cx, constructor, "prototype", &prototypev)) {
+        aRv.StealExceptionFromJSContext(cx);
+        return;
+      }
+
+      /**
+       * 10.2. If Type(prototype) is not Object, then throw a TypeError exception.
+       */
+      if (!prototypev.isObject()) {
+        aRv.ThrowTypeError<MSG_NOT_OBJECT>(NS_LITERAL_STRING("constructor.prototype"));
+        return;
+      }
+
+      constructorPrototype = &prototypev.toObject();
+    } // Leave constructor's compartment.
+
+    JS::Rooted<JSObject*> constructorProtoUnwrapped(cx, js::CheckedUnwrap(constructorPrototype));
+    if (!constructorProtoUnwrapped) {
+      // If the caller's compartment does not have permission to access the
+      // unwrapped prototype then throw.
+      aRv.Throw(NS_ERROR_DOM_SECURITY_ERR);
+      return;
+    }
+
+    { // Enter constructorProtoUnwrapped's compartment
+      JSAutoCompartment ac(cx, constructorProtoUnwrapped);
+
+      /**
+       * 10.3. Let lifecycleCallbacks be a map with the four keys
+       *       "connectedCallback", "disconnectedCallback", "adoptedCallback", and
+       *       "attributeChangedCallback", each of which belongs to an entry whose
+       *       value is null.
+       * 10.4. For each of the four keys callbackName in lifecycleCallbacks:
+       *       1. Let callbackValue be Get(prototype, callbackName). Rethrow any
+       *          exceptions.
+       *       2. If callbackValue is not undefined, then set the value of the
+       *          entry in lifecycleCallbacks with key callbackName to the result
+       *          of converting callbackValue to the Web IDL Function callback type.
+       *          Rethrow any exceptions from the conversion.
+       */
+      // Will do the same checking for the life cycle callbacks from v0 spec.
+      CheckLifeCycleCallbacks(cx, constructorProtoUnwrapped, aRv);
+      if (aRv.Failed()) {
+        return;
+      }
+
+      /**
+       * 10.5. Let observedAttributes be an empty sequence<DOMString>.
+       * 10.6. If the value of the entry in lifecycleCallbacks with key
+       *       "attributeChangedCallback" is not null, then:
+       *       1. Let observedAttributesIterable be Get(constructor,
+       *          "observedAttributes"). Rethrow any exceptions.
+       *       2. If observedAttributesIterable is not undefined, then set
+       *          observedAttributes to the result of converting
+       *          observedAttributesIterable to a sequence<DOMString>. Rethrow
+       *          any exceptions from the conversion.
+       */
+      // TODO: Bug 1293921 - Implement connected/disconnected/adopted/attributeChanged lifecycle callbacks for custom elements
+
+      // Note: We call the init from the constructorProtoUnwrapped's compartment
+      //       here.
+      JS::RootedValue rootedv(cx, JS::ObjectValue(*constructorProtoUnwrapped));
+      if (!JS_WrapValue(cx, &rootedv) || !callbacksHolder->Init(cx, rootedv)) {
+        aRv.Throw(NS_ERROR_FAILURE);
+        return;
+      }
+    } // Leave constructorProtoUnwrapped's compartment.
+  } // Unset mIsCustomDefinitionRunning
+
+  /**
+   * 11. Let definition be a new custom element definition with name name,
+   *     local name localName, constructor constructor, prototype prototype,
+   *     observed attributes observedAttributes, and lifecycle callbacks
+   *     lifecycleCallbacks.
+   */
+  // Associate the definition with the custom element.
+  nsCOMPtr<nsIAtom> localNameAtom(NS_Atomize(localName));
+  LifecycleCallbacks* callbacks = callbacksHolder.forget();
+  CustomElementDefinition* definition =
+    new CustomElementDefinition(nameAtom,
+                                localNameAtom,
+                                constructor,
+                                constructorPrototype,
+                                callbacks,
+                                0 /* TODO dependent on HTML imports. Bug 877072 */);
+
+  /**
+   * 12. Add definition to this CustomElementsRegistry.
+   */
+  mCustomDefinitions.Put(nameAtom, definition);
+
+  /**
+   * 13. 14. 15. Upgrade candidates
+   */
+  // TODO: Bug 1299363 - Implement custom element v1 upgrade algorithm
+  UpgradeCandidates(cx, nameAtom, definition);
+
+  /**
+   * 16. If this CustomElementsRegistry's when-defined promise map contains an
+   *     entry with key name:
+   *     1. Let promise be the value of that entry.
+   *     2. Resolve promise with undefined.
+   *     3. Delete the entry with key name from this CustomElementsRegistry's
+   *        when-defined promise map.
+   */
+  // TODO: Bug 1275839 - Implement CustomElementsRegistry whenDefined function
 }
 
 void
 CustomElementsRegistry::Get(JSContext* aCx, const nsAString& aName,
                             JS::MutableHandle<JS::Value> aRetVal,
                             ErrorResult& aRv)
 {
   // TODO: This function will be implemented in bug 1275838
@@ -495,25 +803,25 @@ CustomElementsRegistry::Get(JSContext* a
 already_AddRefed<Promise>
 CustomElementsRegistry::WhenDefined(const nsAString& name, ErrorResult& aRv)
 {
   // TODO: This function will be implemented in bug 1275839
   aRv.Throw(NS_ERROR_NOT_IMPLEMENTED);
   return nullptr;
 }
 
-CustomElementDefinition::CustomElementDefinition(JSObject* aPrototype,
-                                                 nsIAtom* aType,
+CustomElementDefinition::CustomElementDefinition(nsIAtom* aType,
                                                  nsIAtom* aLocalName,
+                                                 JSObject* aConstructor,
+                                                 JSObject* aPrototype,
                                                  LifecycleCallbacks* aCallbacks,
-                                                 uint32_t aNamespaceID,
                                                  uint32_t aDocOrder)
-  : mPrototype(aPrototype),
-    mType(aType),
+  : mType(aType),
     mLocalName(aLocalName),
+    mConstructor(aConstructor),
+    mPrototype(aPrototype),
     mCallbacks(aCallbacks),
-    mNamespaceID(aNamespaceID),
     mDocOrder(aDocOrder)
 {
 }
 
 } // namespace dom
 } // namespace mozilla
\ No newline at end of file
--- a/dom/base/CustomElementsRegistry.h
+++ b/dom/base/CustomElementsRegistry.h
@@ -91,86 +91,48 @@ struct CustomElementData
 
   // 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),
-      mAtom(aAtom)
-  {}
-  explicit CustomElementHashKey(const CustomElementHashKey* aKey)
-    : mNamespaceID(aKey->mNamespaceID),
-      mAtom(aKey->mAtom)
-  {}
-  ~CustomElementHashKey()
-  {}
-
-  KeyType GetKey() const { return const_cast<KeyType>(this); }
-  bool KeyEquals(const KeyTypePointer aKey) const
-  {
-    MOZ_ASSERT(mNamespaceID != kNameSpaceID_Unknown,
-               "This equals method is not transitive, nor symmetric. "
-               "A key with a namespace of kNamespaceID_Unknown should "
-               "not be stored in a hashtable.");
-    return (kNameSpaceID_Unknown == aKey->mNamespaceID ||
-            mNamespaceID == aKey->mNamespaceID) &&
-           aKey->mAtom == mAtom;
-  }
-
-  static KeyTypePointer KeyToPointer(KeyType aKey) { return aKey; }
-  static PLDHashNumber HashKey(const KeyTypePointer aKey)
-  {
-    return aKey->mAtom->hash();
-  }
-  enum { ALLOW_MEMMOVE = true };
-
-private:
-  int32_t mNamespaceID;
-  nsCOMPtr<nsIAtom> mAtom;
-};
-
 // The required information for a custom element as defined in:
-// https://dvcs.w3.org/hg/webcomponents/raw-file/tip/spec/custom/index.html
+// https://html.spec.whatwg.org/multipage/scripting.html#custom-element-definition
 struct CustomElementDefinition
 {
-  CustomElementDefinition(JSObject* aPrototype,
-                          nsIAtom* aType,
+  CustomElementDefinition(nsIAtom* aType,
                           nsIAtom* aLocalName,
+                          JSObject* aConstructor,
+                          JSObject* aPrototype,
                           mozilla::dom::LifecycleCallbacks* aCallbacks,
-                          uint32_t aNamespaceID,
                           uint32_t aDocOrder);
 
-  // The prototype to use for new custom elements of this type.
-  JS::Heap<JSObject *> mPrototype;
-
   // The type (name) for this custom element.
   nsCOMPtr<nsIAtom> mType;
 
   // The localname to (e.g. <button is=type> -- this would be button).
   nsCOMPtr<nsIAtom> mLocalName;
 
+  // The custom element constructor.
+  JS::Heap<JSObject *> mConstructor;
+
+  // The prototype to use for new custom elements of this type.
+  JS::Heap<JSObject *> mPrototype;
+
   // The lifecycle callbacks to call for this custom element.
   nsAutoPtr<mozilla::dom::LifecycleCallbacks> mCallbacks;
 
   // Whether we're currently calling the created callback for a custom element
   // of this type.
   bool mElementIsBeingCreated;
 
-  // Element namespace.
-  int32_t mNamespaceID;
+  // A construction stack.
+  // TODO: Bug 1287348 - Implement construction stack for upgrading an element
 
   // The document custom element order.
   uint32_t mDocOrder;
 };
 
 class CustomElementsRegistry final : public nsISupports,
                                      public nsWrapperCache
 {
@@ -220,19 +182,23 @@ private:
    * |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>
+  void UpgradeCandidates(JSContext* aCx,
+                         nsIAtom* aKey,
+                         CustomElementDefinition* aDefinition);
+
+  typedef nsClassHashtable<nsISupportsHashKey, CustomElementDefinition>
     DefinitionMap;
-  typedef nsClassHashtable<CustomElementHashKey, nsTArray<nsWeakPtr>>
+  typedef nsClassHashtable<nsISupportsHashKey, 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
@@ -245,16 +211,38 @@ 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<CustomElementData>>> sProcessingStack;
 
+  // It is used to prevent reentrant invocations of element definition.
+  bool mIsCustomDefinitionRunning;
+
+private:
+  class MOZ_RAII AutoSetRunningFlag final {
+    public:
+      explicit AutoSetRunningFlag(CustomElementsRegistry* aRegistry)
+        : mRegistry(aRegistry)
+      {
+        MOZ_ASSERT(!mRegistry->mIsCustomDefinitionRunning,
+                   "IsCustomDefinitionRunning flag should be initially false");
+        mRegistry->mIsCustomDefinitionRunning = true;
+      }
+
+      ~AutoSetRunningFlag() {
+        mRegistry->mIsCustomDefinitionRunning = false;
+      }
+
+    private:
+      CustomElementsRegistry* mRegistry;
+  };
+
 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/Element.h
+++ b/dom/base/Element.h
@@ -420,17 +420,16 @@ 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);
--- 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/CustomElementsRegistryBinding.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"
@@ -5705,26 +5706,25 @@ nsDocument::CustomElementConstructor(JSC
   }
 
   RefPtr<mozilla::dom::CustomElementsRegistry> registry = window->CustomElements();
   if (!registry) {
     return true;
   }
 
   nsCOMPtr<nsIAtom> typeAtom(NS_Atomize(elemName));
-  CustomElementHashKey key(kNameSpaceID_Unknown, typeAtom);
-  CustomElementDefinition* definition = registry->mCustomDefinitions.Get(&key);
+  CustomElementDefinition* definition = registry->mCustomDefinitions.Get(typeAtom);
   if (!definition) {
     return true;
   }
 
   nsDependentAtomString localName(definition->mLocalName);
 
   nsCOMPtr<Element> element =
-    document->CreateElem(localName, nullptr, definition->mNamespaceID);
+    document->CreateElem(localName, nullptr, kNameSpaceID_XHTML);
   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.
     nsContentUtils::SetupCustomElement(element, &elemName);
   }
@@ -5778,224 +5778,76 @@ nsDocument::RegisterElement(JSContext* a
     rv.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
     return;
   }
 
   // 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()) {
-    nsContentUtils::ASCIIToLower(aOptions.mExtends, lcName);
-  } else {
-    lcName.Assign(aOptions.mExtends);
-  }
-
-  nsCOMPtr<nsIAtom> typeAtom(NS_Atomize(lcType));
-  if (!nsContentUtils::IsCustomElementName(typeAtom)) {
-    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 (registry->mCustomDefinitions.Get(&duplicateFinder)) {
-    rv.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
-    return;
-  }
-
   nsIGlobalObject* sgo = GetScopeObject();
   if (!sgo) {
     rv.Throw(NS_ERROR_UNEXPECTED);
     return;
   }
 
   JS::Rooted<JSObject*> global(aCx, sgo->GetGlobalJSObject());
-  nsCOMPtr<nsIAtom> nameAtom;
-  int32_t namespaceID = kNameSpaceID_XHTML;
   JS::Rooted<JSObject*> protoObject(aCx);
-  {
+
+  if (!aOptions.mPrototype) {
     JS::Rooted<JSObject*> htmlProto(aCx);
-    {
-      JSAutoCompartment ac(aCx, global);
-
-      htmlProto = HTMLElementBinding::GetProtoObjectHandle(aCx);
-      if (!htmlProto) {
-        rv.Throw(NS_ERROR_OUT_OF_MEMORY);
-        return;
-      }
-    }
-
-    if (!aOptions.mPrototype) {
-      if (!JS_WrapObject(aCx, &htmlProto)) {
-        rv.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
-        return;
-      }
-
-      protoObject = JS_NewObjectWithGivenProto(aCx, nullptr, htmlProto);
-      if (!protoObject) {
-        rv.Throw(NS_ERROR_UNEXPECTED);
-        return;
-      }
-    } else {
-      protoObject = aOptions.mPrototype;
-
-      // Get the unwrapped prototype to do some checks.
-      JS::Rooted<JSObject*> protoObjectUnwrapped(aCx, js::CheckedUnwrap(protoObject));
-      if (!protoObjectUnwrapped) {
-        // If the caller's compartment does not have permission to access the
-        // unwrapped prototype then throw.
-        rv.Throw(NS_ERROR_DOM_SECURITY_ERR);
-        return;
-      }
-
-      // If PROTOTYPE is already an interface prototype object for any interface
-      // object or PROTOTYPE has a non-configurable property named constructor,
-      // throw a NotSupportedError and stop.
-      const js::Class* clasp = js::GetObjectClass(protoObjectUnwrapped);
-      if (IsDOMIfaceAndProtoClass(clasp)) {
-        rv.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
-        return;
-      }
-
-      JS::Rooted<JS::PropertyDescriptor> descRoot(aCx);
-      JS::MutableHandle<JS::PropertyDescriptor> desc(&descRoot);
-      // This check may go through a wrapper, but as we checked above
-      // it should be transparent or an xray. This should be fine for now,
-      // until the spec is sorted out.
-      if (!JS_GetPropertyDescriptor(aCx, protoObject, "constructor", desc)) {
-        rv.Throw(NS_ERROR_UNEXPECTED);
-        return;
-      }
-
-      if (!desc.configurable()) {
-        rv.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
-        return;
-      }
-
-      JS::Rooted<JSObject*> protoProto(aCx, protoObject);
-
-      if (!JS_WrapObject(aCx, &htmlProto)) {
-        rv.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
-        return;
-      }
-
-      while (protoProto) {
-        if (protoProto == htmlProto) {
-          break;
-        }
-
-        if (!JS_GetPrototype(aCx, protoProto, &protoProto)) {
-          rv.Throw(NS_ERROR_UNEXPECTED);
-          return;
-        }
-      }
-    } // Done with the checks, leave prototype's compartment.
-
-    // If name was provided and not null...
-    if (!lcName.IsEmpty()) {
-      // Let BASE be the element interface for NAME and NAMESPACE.
-      nameAtom = NS_Atomize(lcName);
-      nsIParserService* ps = nsContentUtils::GetParserService();
-      if (!ps) {
-        rv.Throw(NS_ERROR_UNEXPECTED);
-        return;
-      }
-
-      bool known =
-        ps->HTMLCaseSensitiveAtomTagToId(nameAtom) != eHTMLTag_userdefined;
-
-      // If BASE does not exist or is an interface for a custom element, set ERROR
-      // to InvalidName and stop.
-      // If BASE exists, then it cannot be an interface for a custom element.
-      if (!known) {
-        rv.Throw(NS_ERROR_DOM_SYNTAX_ERR);
-        return;
-      }
-    } else {
-      nameAtom = typeAtom;
-    }
-  } // Leaving the document's compartment for the LifecycleCallbacks init
-
-  JS::Rooted<JSObject*> wrappedProto(aCx, protoObject);
-  if (!JS_WrapObject(aCx, &wrappedProto)) {
-    rv.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
-    return;
-  }
-
-  // Note: We call the init from the caller compartment here
-  nsAutoPtr<LifecycleCallbacks> callbacksHolder(new LifecycleCallbacks());
-  JS::RootedValue rootedv(aCx, JS::ObjectValue(*wrappedProto));
-  if (!JS_WrapValue(aCx, &rootedv) || !callbacksHolder->Init(aCx, rootedv)) {
-    rv.Throw(NS_ERROR_FAILURE);
-    return;
-  }
-
-  // Associate the definition with the custom element.
-  CustomElementHashKey key(namespaceID, typeAtom);
-  LifecycleCallbacks* callbacks = callbacksHolder.forget();
-  CustomElementDefinition* definition =
-    new CustomElementDefinition(wrappedProto,
-                                typeAtom,
-                                nameAtom,
-                                callbacks,
-                                namespaceID,
-                                0 /* TODO dependent on HTML imports. Bug 877072 */);
-  registry->mCustomDefinitions.Put(&key, definition);
-
-  // Do element upgrade.
-  nsAutoPtr<nsTArray<nsWeakPtr>> 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);
-
-      // Make sure that the element name matches the name in the definition.
-      // (e.g. a definition for x-button extending button should match
-      // <button is="x-button"> but not <x-button>.
-      if (elem->NodeInfo()->NameAtom() != nameAtom) {
-        //Skip over this element because definition does not apply.
-        continue;
-      }
-
-      MOZ_ASSERT(elem->IsHTMLElement(nameAtom));
-      nsWrapperCache* cache;
-      CallQueryInterface(elem, &cache);
-      MOZ_ASSERT(cache, "Element doesn't support wrapper cache?");
-
-      // We want to set the custom prototype in the caller's comparment.
-      // In the case that element is in a different compartment,
-      // this will set the prototype on the element's wrapper and
-      // 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;
-        }
-      }
-
-      if (GetDocShell()) {
-        nsContentUtils::EnqueueLifecycleCallback(
-          this, nsIDocument::eCreated, elem, nullptr, definition);
-      }
+    htmlProto = HTMLElementBinding::GetProtoObjectHandle(aCx);
+    if (!htmlProto) {
+      rv.Throw(NS_ERROR_OUT_OF_MEMORY);
+      return;
+    }
+
+    protoObject = JS_NewObjectWithGivenProto(aCx, nullptr, htmlProto);
+    if (!protoObject) {
+      rv.Throw(NS_ERROR_UNEXPECTED);
+      return;
+    }
+  } else {
+    protoObject = aOptions.mPrototype;
+
+    // Get the unwrapped prototype to do some checks.
+    JS::Rooted<JSObject*> protoObjectUnwrapped(aCx, js::CheckedUnwrap(protoObject));
+    if (!protoObjectUnwrapped) {
+      // If the caller's compartment does not have permission to access the
+      // unwrapped prototype then throw.
+      rv.Throw(NS_ERROR_DOM_SECURITY_ERR);
+      return;
+    }
+
+    // If PROTOTYPE is already an interface prototype object for any interface
+    // object or PROTOTYPE has a non-configurable property named constructor,
+    // throw a NotSupportedError and stop.
+    const js::Class* clasp = js::GetObjectClass(protoObjectUnwrapped);
+    if (IsDOMIfaceAndProtoClass(clasp)) {
+      rv.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
+      return;
+    }
+
+    JS::Rooted<JS::PropertyDescriptor> descRoot(aCx);
+    JS::MutableHandle<JS::PropertyDescriptor> desc(&descRoot);
+    // This check may go through a wrapper, but as we checked above
+    // it should be transparent or an xray. This should be fine for now,
+    // until the spec is sorted out.
+    if (!JS_GetPropertyDescriptor(aCx, protoObject, "constructor", desc)) {
+      rv.Throw(NS_ERROR_UNEXPECTED);
+      return;
+    }
+
+    if (!desc.configurable()) {
+      rv.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
+      return;
     }
   }
 
   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
     // definition is registered) when it is called.
     JSAutoCompartment ac(aCx, global);
 
     // Create constructor to return. Store the name of the custom element as the
     // name of the function.
@@ -6015,16 +5867,31 @@ nsDocument::RegisterElement(JSContext* a
     return;
   }
 
   if (!JS_LinkConstructorAndPrototype(aCx, wrappedConstructor, protoObject)) {
     rv.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
     return;
   }
 
+  ElementDefinitionOptions options;
+  if (!aOptions.mExtends.IsVoid()) {
+    // Only convert NAME to lowercase in HTML documents.
+    nsAutoString lcName;
+    IsHTMLDocument() ? nsContentUtils::ASCIIToLower(aOptions.mExtends, lcName)
+                     : lcName.Assign(aOptions.mExtends);
+
+    options.mExtends.Construct(lcName);
+  }
+
+  RootedCallback<OwningNonNull<binding_detail::FastFunction>> functionConstructor(aCx);
+  functionConstructor = new binding_detail::FastFunction(aCx, wrappedConstructor, sgo);
+
+  registry->Define(lcType, functionConstructor, options, rv);
+
   aRetval.set(wrappedConstructor);
 }
 
 NS_IMETHODIMP
 nsDocument::GetElementsByTagName(const nsAString& aTagname,
                                  nsIDOMNodeList** aReturn)
 {
   RefPtr<nsContentList> list = GetElementsByTagName(aTagname);
--- a/dom/bindings/Errors.msg
+++ b/dom/bindings/Errors.msg
@@ -21,16 +21,17 @@
  * {X} where X  is an integer representing the argument number that will
  * be replaced with a string value when the error is reported.
  */
 
 MSG_DEF(MSG_INVALID_ENUM_VALUE, 3, JSEXN_TYPEERR, "{0} '{1}' is not a valid value for enumeration {2}.")
 MSG_DEF(MSG_MISSING_ARGUMENTS, 1, JSEXN_TYPEERR, "Not enough arguments to {0}.")
 MSG_DEF(MSG_NOT_OBJECT, 1, JSEXN_TYPEERR, "{0} is not an object.")
 MSG_DEF(MSG_NOT_CALLABLE, 1, JSEXN_TYPEERR, "{0} is not callable.")
+MSG_DEF(MSG_NOT_CONSTRUCTOR, 1, JSEXN_TYPEERR, "{0} is not a constructor.")
 MSG_DEF(MSG_DOES_NOT_IMPLEMENT_INTERFACE, 2, JSEXN_TYPEERR, "{0} does not implement interface {1}.")
 MSG_DEF(MSG_METHOD_THIS_DOES_NOT_IMPLEMENT_INTERFACE, 2, JSEXN_TYPEERR, "'{0}' called on an object that does not implement interface {1}.")
 MSG_DEF(MSG_METHOD_THIS_UNWRAPPING_DENIED, 1, JSEXN_TYPEERR, "Permission to call '{0}' denied.")
 MSG_DEF(MSG_THIS_DOES_NOT_IMPLEMENT_INTERFACE, 1, JSEXN_TYPEERR, "\"this\" object does not implement interface {0}.")
 MSG_DEF(MSG_NOT_IN_UNION, 2, JSEXN_TYPEERR, "{0} could not be converted to any of: {1}.")
 MSG_DEF(MSG_ILLEGAL_CONSTRUCTOR, 0, JSEXN_TYPEERR, "Illegal constructor.")
 MSG_DEF(MSG_CONSTRUCTOR_WITHOUT_NEW, 1, JSEXN_TYPEERR, "Constructor {0} requires 'new'")
 MSG_DEF(MSG_ENFORCE_RANGE_NON_FINITE, 1, JSEXN_TYPEERR, "Non-finite value is out of range for {0}.")
--- a/dom/tests/mochitest/webcomponents/mochitest.ini
+++ b/dom/tests/mochitest/webcomponents/mochitest.ini
@@ -8,16 +8,17 @@ support-files =
 [test_bug1276240.html]
 [test_content_element.html]
 [test_custom_element_adopt_callbacks.html]
 [test_custom_element_callback_innerhtml.html]
 [test_custom_element_clone_callbacks.html]
 [test_custom_element_clone_callbacks_extended.html]
 [test_custom_element_import_node_created_callback.html]
 [test_custom_element_in_shadow.html]
+[test_custom_element_register_invalid_callbacks.html]
 [test_nested_content_element.html]
 [test_dest_insertion_points.html]
 [test_dest_insertion_points_shadow.html]
 [test_fallback_dest_insertion_points.html]
 [test_detached_style.html]
 [test_dynamic_content_element_matching.html]
 [test_document_adoptnode.html]
 [test_document_importnode.html]
new file mode 100644
--- /dev/null
+++ b/dom/tests/mochitest/webcomponents/test_custom_element_register_invalid_callbacks.html
@@ -0,0 +1,72 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1275835
+-->
+<head>
+  <title>Test registering invalid lifecycle callbacks for custom elements.</title>
+  <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1275835">Bug 1275835</a>
+<iframe id="iframe"></iframe>
+<script>
+
+// Use window from iframe to isolate the test.
+const testWindow = iframe.contentDocument.defaultView;
+
+// This is for backward compatibility.
+// We should do the same checks for the callbacks from v0 spec.
+[
+  'createdCallback',
+  'attachedCallback',
+  'detachedCallback',
+  'attributeChangedCallback',
+].forEach(callback => {
+  var c = class {};
+  var p = c.prototype;
+
+  // Test getting callback throws exception.
+  Object.defineProperty(p, callback, {
+    get() {
+      const e = new Error('this is rethrown');
+      e.name = 'rethrown';
+      throw e;
+    }
+  });
+
+  SimpleTest.doesThrow(() => {
+    testWindow.document.registerElement(`test-register-${callback}-rethrown`,
+                                        { prototype: p });
+  }, `document.registerElement should throw exception if prototype.${callback} throws`);
+
+  SimpleTest.doesThrow(() => {
+    testWindow.customElements.define(`test-define-${callback}-rethrown`, c);
+  }, `customElements.define should throw exception if constructor.${callback} throws`);
+
+  // Test callback is not callable.
+  [
+    { name: 'null', value: null },
+    { name: 'object', value: {} },
+    { name: 'integer', value: 1 },
+  ].forEach(data => {
+    var c = class {};
+    var p = c.prototype;
+
+    p[callback] = data.value;
+
+    SimpleTest.doesThrow(() => {
+      testWindow.document.registerElement(`test-register-${callback}-${data.name}`,
+                                             { prototype: p });
+    }, `document.registerElement should throw exception if ${callback} is ${data.name}`);
+
+    SimpleTest.doesThrow(() => {
+      testWindow.customElements.define(`test-define-${callback}-${data.name}`, c);
+    }, `customElements.define should throw exception if ${callback} is ${data.name}`);
+  });
+});
+
+</script>
+</body>
+</html>
--- a/testing/web-platform/meta/custom-elements/custom-elements-registry/define.html.ini
+++ b/testing/web-platform/meta/custom-elements/custom-elements-registry/define.html.ini
@@ -1,229 +1,10 @@
 [define.html]
   type: testharness
-  [If constructor is arrow function, should throw a TypeError]
-    expected: FAIL
-
-  [If constructor is method, should throw a TypeError]
-    expected: FAIL
-
-  [Element names: defining an element named a- should succeed]
-    expected: FAIL
-
-  [Element names: defining an element named a-a should succeed]
-    expected: FAIL
-
-  [Element names: defining an element named aa- should succeed]
-    expected: FAIL
-
-  [Element names: defining an element named aa-a should succeed]
-    expected: FAIL
-
-  [Element names: defining an element named a-.-_ should succeed]
-    expected: FAIL
-
-  [Element names: defining an element named a-0123456789 should succeed]
-    expected: FAIL
-
-  [Element names: defining an element named a-漢字 should succeed]
-    expected: FAIL
-
-  [Element names: defining an element named a-𠀋 should succeed]
-    expected: FAIL
-
-  [Element names: defining an element named undefined should throw a SyntaxError]
-    expected: FAIL
-
-  [Element names: defining an element named null should throw a SyntaxError]
-    expected: FAIL
-
-  [Element names: defining an element named  should throw a SyntaxError]
-    expected: FAIL
-
-  [Element names: defining an element named - should throw a SyntaxError]
-    expected: FAIL
-
-  [Element names: defining an element named a should throw a SyntaxError]
-    expected: FAIL
-
-  [Element names: defining an element named input should throw a SyntaxError]
-    expected: FAIL
-
-  [Element names: defining an element named mycustomelement should throw a SyntaxError]
-    expected: FAIL
-
-  [Element names: defining an element named A should throw a SyntaxError]
-    expected: FAIL
-
-  [Element names: defining an element named A- should throw a SyntaxError]
-    expected: FAIL
-
-  [Element names: defining an element named 0- should throw a SyntaxError]
-    expected: FAIL
-
-  [Element names: defining an element named a-A should throw a SyntaxError]
-    expected: FAIL
-
-  [Element names: defining an element named a-Z should throw a SyntaxError]
-    expected: FAIL
-
-  [Element names: defining an element named A-a should throw a SyntaxError]
-    expected: FAIL
-
-  [Element names: defining an element named a-a× should throw a SyntaxError]
-    expected: FAIL
-
-  [Element names: defining an element named a-a  should throw a SyntaxError]
-    expected: FAIL
-
-  [Element names: defining an element named a-a󰀀 should throw a SyntaxError]
-    expected: FAIL
-
-  [Element names: defining an element named annotation-xml should throw a SyntaxError]
-    expected: FAIL
-
-  [Element names: defining an element named color-profile should throw a SyntaxError]
-    expected: FAIL
-
-  [Element names: defining an element named font-face should throw a SyntaxError]
-    expected: FAIL
-
-  [Element names: defining an element named font-face-src should throw a SyntaxError]
-    expected: FAIL
-
-  [Element names: defining an element named font-face-uri should throw a SyntaxError]
-    expected: FAIL
-
-  [Element names: defining an element named font-face-format should throw a SyntaxError]
-    expected: FAIL
-
-  [Element names: defining an element named font-face-name should throw a SyntaxError]
-    expected: FAIL
-
-  [Element names: defining an element named missing-glyph should throw a SyntaxError]
-    expected: FAIL
-
-  [If the name is already defined, should throw a NotSupportedError]
-    expected: FAIL
-
-  [If the constructor is already defined, should throw a NotSupportedError]
-    expected: FAIL
-
-  [If extends is a-, should throw a NotSupportedError]
-    expected: FAIL
-
-  [If extends is a-a, should throw a NotSupportedError]
-    expected: FAIL
-
-  [If extends is aa-, should throw a NotSupportedError]
-    expected: FAIL
-
-  [If extends is aa-a, should throw a NotSupportedError]
-    expected: FAIL
-
-  [If extends is a-.-_, should throw a NotSupportedError]
-    expected: FAIL
-
-  [If extends is a-0123456789, should throw a NotSupportedError]
-    expected: FAIL
-
-  [If extends is a-漢字, should throw a NotSupportedError]
-    expected: FAIL
-
-  [If extends is a-𠀋, should throw a NotSupportedError]
-    expected: FAIL
-
-  [If extends is bgsound, should throw a NotSupportedError]
-    expected: FAIL
-
-  [If extends is blink, should throw a NotSupportedError]
-    expected: FAIL
-
-  [If extends is isindex, should throw a NotSupportedError]
-    expected: FAIL
-
-  [If extends is multicol, should throw a NotSupportedError]
-    expected: FAIL
-
-  [If extends is nextid, should throw a NotSupportedError]
-    expected: FAIL
-
-  [If extends is spacer, should throw a NotSupportedError]
-    expected: FAIL
-
-  [If extends is elementnametobeunknownelement, should throw a NotSupportedError]
-    expected: FAIL
-
-  [If constructor.observedAttributes throws, should rethrow]
-    expected: FAIL
-
-  [If constructor.prototype throws, should rethrow]
-    expected: FAIL
-
-  [If Type(constructor.prototype) is undefined, should throw a TypeError]
-    expected: FAIL
-
-  [If Type(constructor.prototype) is string, should throw a TypeError]
-    expected: FAIL
-
-  [If constructor.prototype.connectedCallback throws, should rethrow]
-    expected: FAIL
-
-  [If constructor.prototype.connectedCallback is undefined, should succeed]
-    expected: FAIL
-
-  [If constructor.prototype.connectedCallback is function, should succeed]
-    expected: FAIL
-
-  [If constructor.prototype.connectedCallback is null, should throw a TypeError]
-    expected: FAIL
-
-  [If constructor.prototype.connectedCallback is object, should throw a TypeError]
-    expected: FAIL
-
-  [If constructor.prototype.connectedCallback is integer, should throw a TypeError]
-    expected: FAIL
-
-  [If constructor.prototype.disconnectedCallback throws, should rethrow]
-    expected: FAIL
-
-  [If constructor.prototype.disconnectedCallback is undefined, should succeed]
-    expected: FAIL
-
-  [If constructor.prototype.disconnectedCallback is function, should succeed]
-    expected: FAIL
-
-  [If constructor.prototype.disconnectedCallback is null, should throw a TypeError]
-    expected: FAIL
-
-  [If constructor.prototype.disconnectedCallback is object, should throw a TypeError]
-    expected: FAIL
-
-  [If constructor.prototype.disconnectedCallback is integer, should throw a TypeError]
-    expected: FAIL
-
-  [If constructor.prototype.attributeChangedCallback throws, should rethrow]
-    expected: FAIL
-
-  [If constructor.prototype.attributeChangedCallback is undefined, should succeed]
-    expected: FAIL
-
-  [If constructor.prototype.attributeChangedCallback is function, should succeed]
-    expected: FAIL
-
-  [If constructor.prototype.attributeChangedCallback is null, should throw a TypeError]
-    expected: FAIL
-
-  [If constructor.prototype.attributeChangedCallback is object, should throw a TypeError]
-    expected: FAIL
-
-  [If constructor.prototype.attributeChangedCallback is integer, should throw a TypeError]
-    expected: FAIL
-
   [If constructor is HTMLElement, should throw a TypeError]
     expected: FAIL
 
   [If constructor is HTMLButtonElement, should throw a TypeError]
     expected: FAIL
 
   [If constructor is HTMLImageElement, should throw a TypeError]
     expected: FAIL
@@ -235,8 +16,25 @@
     expected: FAIL
 
   [If constructor is Audio, should throw a TypeError]
     expected: FAIL
 
   [If constructor is Option, should throw a TypeError]
     expected: FAIL
 
+  [Element names: defining an element named a-𠀋 should succeed]
+    expected: FAIL
+
+  [Element names: defining an element named A- should throw a SyntaxError]
+    expected: FAIL
+
+  [Element names: defining an element named a-A should throw a SyntaxError]
+    expected: FAIL
+
+  [Element names: defining an element named a-Z should throw a SyntaxError]
+    expected: FAIL
+
+  [Element names: defining an element named A-a should throw a SyntaxError]
+    expected: FAIL
+
+  [If the constructor is already defined, should throw a NotSupportedError]
+    expected: FAIL