Bug 856140 - Sync our document.registerElement implementation to the current version of the spec. r=mrbkap
authorWilliam Chen <wchen@mozilla.com>
Sun, 23 Feb 2014 13:01:26 -0800
changeset 170489 174ec7df74aec1229fcb0df50ee08c42175ccd67
parent 170488 1ba35735fe928757f6a5ee61b835d13cb800643d
child 170490 02959a932fb7886d002f86cb29bc4ce57ac5c146
push id270
push userpvanderbeken@mozilla.com
push dateThu, 06 Mar 2014 09:24:21 +0000
reviewersmrbkap
bugs856140
milestone30.0a1
Bug 856140 - Sync our document.registerElement implementation to the current version of the spec. r=mrbkap
content/base/public/Element.h
content/base/public/FragmentOrElement.h
content/base/public/nsContentUtils.h
content/base/public/nsIContent.h
content/base/public/nsIDocument.h
content/base/src/Element.cpp
content/base/src/FragmentOrElement.cpp
content/base/src/moz.build
content/base/src/nsContentUtils.cpp
content/base/src/nsDocument.cpp
content/base/src/nsDocument.h
content/base/src/nsGenericDOMDataNode.cpp
content/base/src/nsGenericDOMDataNode.h
content/base/src/nsGkAtomList.h
content/base/test/chrome/test_document_register.xul
content/html/content/src/HTMLUnknownElement.cpp
content/html/document/src/nsHTMLContentSink.cpp
content/html/document/src/nsHTMLDocument.h
content/svg/content/src/SVGElementFactory.cpp
content/svg/content/src/SVGElementFactory.h
content/xul/document/src/XULDocument.h
dom/tests/mochitest/webcomponents/mochitest.ini
dom/tests/mochitest/webcomponents/test_document_register.html
dom/tests/mochitest/webcomponents/test_document_register_base_queue.html
dom/tests/mochitest/webcomponents/test_document_register_lifecycle.html
dom/tests/mochitest/webcomponents/test_document_register_parser.html
dom/tests/mochitest/webcomponents/test_document_register_stack.html
dom/webidl/Document.webidl
dom/webidl/DummyBinding.webidl
dom/webidl/WebComponents.webidl
parser/html/nsHtml5TreeOperation.cpp
--- a/content/base/public/Element.h
+++ b/content/base/public/Element.h
@@ -954,17 +954,18 @@ protected:
    *
    * For the boolean parameters, consider using the named bools above to aid
    * code readability.
    *
    * @param aNamespaceID  namespace of attribute
    * @param aAttribute    local-name of attribute
    * @param aPrefix       aPrefix of attribute
    * @param aOldValue     previous value of attribute. Only needed if
-   *                      aFireMutation is true.
+   *                      aFireMutation is true or if the element is a
+   *                      custom element (in web components).
    * @param aParsedValue  parsed new value of attribute
    * @param aModType      nsIDOMMutationEvent::MODIFICATION or ADDITION.  Only
    *                      needed if aFireMutation or aNotify is true.
    * @param aFireMutation should mutation-events be fired?
    * @param aNotify       should we notify document-observers?
    * @param aCallAfterSetAttr should we call AfterSetAttr?
    */
   nsresult SetAttrAndNotify(int32_t aNamespaceID,
--- a/content/base/public/FragmentOrElement.h
+++ b/content/base/public/FragmentOrElement.h
@@ -202,20 +202,23 @@ public:
   virtual void AppendTextTo(nsAString& aResult) MOZ_OVERRIDE;
   virtual nsIContent *GetBindingParent() const MOZ_OVERRIDE;
   virtual nsXBLBinding *GetXBLBinding() const MOZ_OVERRIDE;
   virtual void SetXBLBinding(nsXBLBinding* aBinding,
                              nsBindingManager* aOldBindingManager = nullptr) MOZ_OVERRIDE;
   virtual ShadowRoot *GetShadowRoot() const MOZ_OVERRIDE;
   virtual ShadowRoot *GetContainingShadow() const MOZ_OVERRIDE;
   virtual void SetShadowRoot(ShadowRoot* aBinding) MOZ_OVERRIDE;
-  virtual nsIContent *GetXBLInsertionParent() const;
-  virtual void SetXBLInsertionParent(nsIContent* aContent);
+  virtual nsIContent *GetXBLInsertionParent() const MOZ_OVERRIDE;
+  virtual void SetXBLInsertionParent(nsIContent* aContent) MOZ_OVERRIDE;
   virtual bool IsLink(nsIURI** aURI) const MOZ_OVERRIDE;
 
+  virtual CustomElementData *GetCustomElementData() const MOZ_OVERRIDE;
+  virtual void SetCustomElementData(CustomElementData* aData) MOZ_OVERRIDE;
+
   virtual void DestroyContent() MOZ_OVERRIDE;
   virtual void SaveSubtreeState() MOZ_OVERRIDE;
 
   virtual const nsAttrValue* DoGetClasses() const MOZ_OVERRIDE;
   NS_IMETHOD WalkContentStyleRules(nsRuleWalker* aRuleWalker) MOZ_OVERRIDE;
 
   nsIHTMLCollection* Children();
   uint32_t ChildElementCount()
@@ -373,16 +376,21 @@ public:
      * XBL binding installed on the element.
      */
     nsRefPtr<nsXBLBinding> mXBLBinding;
 
     /**
      * XBL binding installed on the lement.
      */
     nsCOMPtr<nsIContent> mXBLInsertionParent;
+
+    /**
+     * Web components custom element data.
+     */
+    nsAutoPtr<CustomElementData> mCustomElementData;
   };
 
 protected:
   void GetMarkup(bool aIncludeSelf, nsAString& aMarkup);
   void SetInnerHTMLInternal(const nsAString& aInnerHTML, ErrorResult& aError);
 
   // Override from nsINode
   virtual nsINode::nsSlots* CreateSlots() MOZ_OVERRIDE;
--- a/content/base/public/nsContentUtils.h
+++ b/content/base/public/nsContentUtils.h
@@ -522,16 +522,22 @@ public:
    * @param aBuffer the buffer to check
    * @param aLength the length of the buffer
    * @param aCharset empty if not found
    * @return boolean indicating whether a BOM was detected.
    */
   static bool CheckForBOM(const unsigned char* aBuffer, uint32_t aLength,
                           nsACString& aCharset);
 
+  /**
+   * Returns true if |aName| is a valid name to be registered via
+   * document.registerElement.
+   */
+  static bool IsCustomElementName(nsIAtom* aName);
+
   static nsresult CheckQName(const nsAString& aQualifiedName,
                              bool aNamespaceAware = true,
                              const char16_t** aColon = nullptr);
 
   static nsresult SplitQName(const nsIContent* aNamespaceResolver,
                              const nsAFlatString& aQName,
                              int32_t *aNamespace, nsIAtom **aLocalName);
 
--- a/content/base/public/nsIContent.h
+++ b/content/base/public/nsIContent.h
@@ -18,16 +18,17 @@ class nsAttrValue;
 class nsAttrName;
 class nsTextFragment;
 class nsIFrame;
 class nsXBLBinding;
 
 namespace mozilla {
 namespace dom {
 class ShadowRoot;
+struct CustomElementData;
 } // namespace dom
 namespace widget {
 struct IMEState;
 } // namespace widget
 } // namespace mozilla
 
 enum nsLinkState {
   eLinkState_Unvisited  = 1,
@@ -672,16 +673,32 @@ public:
    * tree. For nodes that are not filtered into an insertion point, this
    * simply returns their DOM parent in the original DOM tree.
    *
    * @return the flattened tree parent
    */
   nsIContent *GetFlattenedTreeParent() const;
 
   /**
+   * Gets the custom element data used by web components custom element.
+   * Custom element data is created at the first attempt to enqueue a callback.
+   *
+   * @return The custom element data or null if none.
+   */
+  virtual mozilla::dom::CustomElementData *GetCustomElementData() const = 0;
+
+  /**
+   * Sets the custom element data, ownership of the
+   * callback data is taken by this content.
+   *
+   * @param aCallbackData The custom element data.
+   */
+  virtual void SetCustomElementData(mozilla::dom::CustomElementData* aData) = 0;
+
+  /**
    * API to check if this is a link that's traversed in response to user input
    * (e.g. a click event). Specializations for HTML/SVG/generic XML allow for
    * different types of link in different types of content.
    *
    * @param aURI Required out param. If this content is a link, a new nsIURI
    *             set to this link's URI will be passed out.
    *
    * @note The out param, aURI, is guaranteed to be set to a non-null pointer
--- a/content/base/public/nsIDocument.h
+++ b/content/base/public/nsIDocument.h
@@ -91,24 +91,26 @@ namespace css {
 class Loader;
 class ImageLoader;
 } // namespace css
 
 namespace dom {
 class Attr;
 class CDATASection;
 class Comment;
+struct CustomElementDefinition;
 class DocumentFragment;
 class DocumentType;
 class DOMImplementation;
 class Element;
 struct ElementRegistrationOptions;
 class EventTarget;
 class FrameRequestCallback;
 class HTMLBodyElement;
+struct LifecycleCallbackArgs;
 class Link;
 class GlobalObject;
 class NodeFilter;
 class NodeIterator;
 class ProcessingInstruction;
 class Touch;
 class TreeWalker;
 class UndoManager;
@@ -117,18 +119,18 @@ template<typename> class OwningNonNull;
 template<typename> class Sequence;
 
 template<typename, typename> class CallbackObjectHolder;
 typedef CallbackObjectHolder<NodeFilter, nsIDOMNodeFilter> NodeFilterHolder;
 } // namespace dom
 } // namespace mozilla
 
 #define NS_IDOCUMENT_IID \
-{ 0x56a350f4, 0xc286, 0x440c, \
-  { 0x85, 0xb1, 0xb6, 0x55, 0x77, 0xeb, 0x63, 0xfd } }
+{ 0x595492bc, 0xa26d, 0x46a9, \
+  { 0xa9, 0x35, 0x0c, 0x40, 0xdd, 0xc2, 0x77, 0x51 } }
 
 // Flag for AddStyleSheet().
 #define NS_STYLESHEET_FROM_CATALOG                (1 << 0)
 
 // Enum for requesting a particular type of document when creating a doc
 enum DocumentFlavor {
   DocumentFlavorLegacyGuess, // compat with old code until made HTML5-compliant
   DocumentFlavorHTML, // HTMLDocument with HTMLness bit set to true
@@ -1981,20 +1983,46 @@ public:
   void GetCompatMode(nsString& retval) const;
   void GetCharacterSet(nsAString& retval) const;
   // Skip GetContentType, because our NS_IMETHOD version above works fine here.
   // GetDoctype defined above
   Element* GetDocumentElement() const
   {
     return GetRootElement();
   }
+
+  enum ElementCallbackType {
+    eCreated,
+    eEnteredView,
+    eLeftView,
+    eAttributeChanged
+  };
+
+  /**
+   * 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 SwizzleCustomElement(Element* aElement,
+                                    const nsAString& aTypeExtension,
+                                    uint32_t aNamespaceID,
+                                    mozilla::ErrorResult& rv) = 0;
   virtual JSObject*
-  Register(JSContext* aCx, const nsAString& aName,
-           const mozilla::dom::ElementRegistrationOptions& aOptions,
-           mozilla::ErrorResult& rv) = 0;
+    RegisterElement(JSContext* aCx, const nsAString& aName,
+                    const mozilla::dom::ElementRegistrationOptions& aOptions,
+                    mozilla::ErrorResult& rv) = 0;
   already_AddRefed<nsContentList>
   GetElementsByTagName(const nsAString& aTagName)
   {
     return NS_GetContentList(this, kNameSpaceID_Unknown, aTagName);
   }
   already_AddRefed<nsContentList>
     GetElementsByTagNameNS(const nsAString& aNamespaceURI,
                            const nsAString& aLocalName,
@@ -2002,16 +2030,23 @@ public:
   already_AddRefed<nsContentList>
     GetElementsByClassName(const nsAString& aClasses);
   // GetElementById defined above
   already_AddRefed<Element> CreateElement(const nsAString& aTagName,
                                           mozilla::ErrorResult& rv);
   already_AddRefed<Element> CreateElementNS(const nsAString& aNamespaceURI,
                                             const nsAString& aQualifiedName,
                                             mozilla::ErrorResult& rv);
+  virtual already_AddRefed<Element> CreateElement(const nsAString& aTagName,
+                                                  const nsAString& aTypeExtension,
+                                                  mozilla::ErrorResult& rv) = 0;
+  virtual already_AddRefed<Element> CreateElementNS(const nsAString& aNamespaceURI,
+                                                    const nsAString& aQualifiedName,
+                                                    const nsAString& aTypeExtension,
+                                                    mozilla::ErrorResult& rv) = 0;
   already_AddRefed<mozilla::dom::DocumentFragment>
     CreateDocumentFragment() const;
   already_AddRefed<nsTextNode> CreateTextNode(const nsAString& aData) const;
   already_AddRefed<mozilla::dom::Comment>
     CreateComment(const nsAString& aData) const;
   already_AddRefed<mozilla::dom::ProcessingInstruction>
     CreateProcessingInstruction(const nsAString& target, const nsAString& data,
                                 mozilla::ErrorResult& rv) const;
--- a/content/base/src/Element.cpp
+++ b/content/base/src/Element.cpp
@@ -356,16 +356,32 @@ Element::GetBindingURL(nsIDocument *aDoc
 JSObject*
 Element::WrapObject(JSContext *aCx, JS::Handle<JSObject*> aScope)
 {
   JS::Rooted<JSObject*> obj(aCx, nsINode::WrapObject(aCx, aScope));
   if (!obj) {
     return nullptr;
   }
 
+  // Custom element prototype swizzling.
+  CustomElementData* data = GetCustomElementData();
+  if (obj && data) {
+    // If this is a registered custom element then fix the prototype.
+    JSAutoCompartment ac(aCx, obj);
+    nsDocument* document = static_cast<nsDocument*>(OwnerDoc());
+    JS::Rooted<JSObject*> prototype(aCx);
+    document->GetCustomPrototype(NodeInfo()->NamespaceID(), data->mType, &prototype);
+    if (prototype) {
+      if (!JS_WrapObject(aCx, &prototype) || !JS_SetPrototype(aCx, obj, prototype)) {
+        dom::Throw(aCx, NS_ERROR_FAILURE);
+        return nullptr;
+      }
+    }
+  }
+
   nsIDocument* doc;
   if (HasFlag(NODE_FORCE_XBL_BINDINGS)) {
     doc = OwnerDoc();
   }
   else {
     doc = GetCurrentDoc();
   }
 
@@ -1136,16 +1152,21 @@ Element::BindToTree(nsIDocument* aDocume
 
     // We no longer need to track the subtree pointer (and in fact we'll assert
     // if we do this any later).
     ClearSubtreeRootPointer();
 
     // Being added to a document.
     SetInDocument();
 
+    if (GetCustomElementData()) {
+      // Enqueue an enteredView callback for the custom element.
+      aDocument->EnqueueLifecycleCallback(nsIDocument::eEnteredView, this);
+    }
+
     // Unset this flag since we now really are in a document.
     UnsetFlags(NODE_FORCE_XBL_BINDINGS |
                // And clear the lazy frame construction bits.
                NODE_NEEDS_FRAME | NODE_DESCENDANTS_NEED_FRAMES |
                // And the restyle bits
                ELEMENT_ALL_RESTYLE_FLAGS);
 
     // Propagate scoped style sheet tracking bit.
@@ -1293,16 +1314,21 @@ Element::UnbindFromTree(bool aDeep, bool
     // anonymous content that the document is changing.
     if (HasFlag(NODE_MAY_BE_IN_BINDING_MNGR)) {
       nsContentUtils::AddScriptRunner(
         new RemoveFromBindingManagerRunnable(document->BindingManager(), this,
                                              document));
     }
 
     document->ClearBoxObjectFor(this);
+
+    if (GetCustomElementData()) {
+      // Enqueue a leftView callback for the custom element.
+      document->EnqueueLifecycleCallback(nsIDocument::eLeftView, this);
+    }
   }
 
   // Ensure that CSS transitions don't continue on an element at a
   // different place in the tree (even if reinserted before next
   // animation refresh).
   // FIXME (Bug 522599): Need a test for this.
   if (HasFlag(NODE_HAS_PROPERTIES)) {
     DeleteProperty(nsGkAtoms::transitionsOfBeforeProperty);
@@ -1660,18 +1686,20 @@ Element::MaybeCheckSameAttrVal(int32_t a
   // coming from the content sink and will almost certainly have no previous
   // value.  Even if we do, setting the value is cheap when we have no
   // listeners and don't plan to notify.  The check for aNotify here is an
   // optimization, the check for *aHasListeners is a correctness issue.
   if (*aHasListeners || aNotify) {
     nsAttrInfo info(GetAttrInfo(aNamespaceID, aName));
     if (info.mValue) {
       // Check whether the old value is the same as the new one.  Note that we
-      // only need to actually _get_ the old value if we have listeners.
-      if (*aHasListeners) {
+      // only need to actually _get_ the old value if we have listeners or
+      // if the element is a custom element (because it may have an
+      // attribute changed callback).
+      if (*aHasListeners || GetCustomElementData()) {
         // Need to store the old value.
         //
         // If the current attribute value contains a pointer to some other data
         // structure that gets updated in the process of setting the attribute
         // we'll no longer have the old value of the attribute. Therefore, we
         // should serialize the attribute value now to keep a snapshot.
         //
         // We have to serialize the value anyway in order to create the
@@ -1847,16 +1875,30 @@ Element::SetAttrAndNotify(int32_t aNames
     nsRefPtr<nsXBLBinding> binding = GetXBLBinding();
     if (binding) {
       binding->AttributeChanged(aName, aNamespaceID, false, aNotify);
     }
   }
 
   UpdateState(aNotify);
 
+  nsIDocument* ownerDoc = OwnerDoc();
+  if (ownerDoc && GetCustomElementData()) {
+    nsCOMPtr<nsIAtom> oldValueAtom = aOldValue.GetAsAtom();
+    nsCOMPtr<nsIAtom> newValueAtom = aValueForAfterSetAttr.GetAsAtom();
+    LifecycleCallbackArgs args = {
+      nsDependentAtomString(aName),
+      aModType == nsIDOMMutationEvent::ADDITION ?
+        NullString() : nsDependentAtomString(oldValueAtom),
+      nsDependentAtomString(newValueAtom)
+    };
+
+    ownerDoc->EnqueueLifecycleCallback(nsIDocument::eAttributeChanged, this, &args);
+  }
+
   if (aCallAfterSetAttr) {
     rv = AfterSetAttr(aNamespaceID, aName, &aValueForAfterSetAttr, aNotify);
     NS_ENSURE_SUCCESS(rv, rv);
 
     if (aNamespaceID == kNameSpaceID_None && aName == nsGkAtoms::dir) {
       OnSetDirAttr(this, &aValueForAfterSetAttr,
                    hadValidDir, hadDirAuto, aNotify);
     }
@@ -2030,16 +2072,28 @@ Element::UnsetAttr(int32_t aNameSpaceID,
     nsRefPtr<nsXBLBinding> binding = GetXBLBinding();
     if (binding) {
       binding->AttributeChanged(aName, aNameSpaceID, true, aNotify);
     }
   }
 
   UpdateState(aNotify);
 
+  nsIDocument* ownerDoc = OwnerDoc();
+  if (ownerDoc && GetCustomElementData()) {
+    nsCOMPtr<nsIAtom> oldValueAtom = oldValue.GetAsAtom();
+    LifecycleCallbackArgs args = {
+      nsDependentAtomString(aName),
+      nsDependentAtomString(oldValueAtom),
+      NullString()
+    };
+
+    ownerDoc->EnqueueLifecycleCallback(nsIDocument::eAttributeChanged, this, &args);
+  }
+
   if (aNotify) {
     nsNodeUtils::AttributeChanged(this, aNameSpaceID, aName,
                                   nsIDOMMutationEvent::REMOVAL);
   }
 
   rv = AfterSetAttr(aNameSpaceID, aName, nullptr, aNotify);
   NS_ENSURE_SUCCESS(rv, rv);
 
--- a/content/base/src/FragmentOrElement.cpp
+++ b/content/base/src/FragmentOrElement.cpp
@@ -558,16 +558,22 @@ FragmentOrElement::nsDOMSlots::Traverse(
   NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, "mSlots->mContainingShadow");
   cb.NoteXPCOMChild(NS_ISUPPORTS_CAST(nsIContent*, mContainingShadow));
 
   NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, "mSlots->mChildrenList");
   cb.NoteXPCOMChild(NS_ISUPPORTS_CAST(nsIDOMNodeList*, mChildrenList));
 
   NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, "mSlots->mClassList");
   cb.NoteXPCOMChild(mClassList.get());
+
+  if (mCustomElementData) {
+    for (uint32_t i = 0; i < mCustomElementData->mCallbackQueue.Length(); i++) {
+      mCustomElementData->mCallbackQueue[i]->Traverse(cb);
+    }
+  }
 }
 
 void
 FragmentOrElement::nsDOMSlots::Unlink(bool aIsXUL)
 {
   mStyle = nullptr;
   mSMILOverrideStyle = nullptr;
   if (mAttributeMap) {
@@ -577,16 +583,17 @@ FragmentOrElement::nsDOMSlots::Unlink(bo
   if (aIsXUL)
     NS_IF_RELEASE(mControllers);
   mXBLBinding = nullptr;
   mXBLInsertionParent = nullptr;
   mShadowRoot = nullptr;
   mContainingShadow = nullptr;
   mChildrenList = nullptr;
   mUndoManager = nullptr;
+  mCustomElementData = nullptr;
   if (mClassList) {
     mClassList->DropReference();
     mClassList = nullptr;
   }
 }
 
 size_t
 FragmentOrElement::nsDOMSlots::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const
@@ -1008,16 +1015,34 @@ FragmentOrElement::SetXBLInsertionParent
 {
   nsDOMSlots *slots = DOMSlots();
   if (aContent) {
     SetFlags(NODE_MAY_BE_IN_BINDING_MNGR);
   }
   slots->mXBLInsertionParent = aContent;
 }
 
+CustomElementData*
+FragmentOrElement::GetCustomElementData() const
+{
+  nsDOMSlots *slots = GetExistingDOMSlots();
+  if (slots) {
+    return slots->mCustomElementData;
+  }
+  return nullptr;
+}
+
+void
+FragmentOrElement::SetCustomElementData(CustomElementData* aData)
+{
+  nsDOMSlots *slots = DOMSlots();
+  MOZ_ASSERT(!slots->mCustomElementData, "Custom element data may not be changed once set.");
+  slots->mCustomElementData = aData;
+}
+
 nsresult
 FragmentOrElement::InsertChildAt(nsIContent* aKid,
                                 uint32_t aIndex,
                                 bool aNotify)
 {
   NS_PRECONDITION(aKid, "null ptr");
 
   return doInsertChildAt(aKid, aIndex, aNotify, mAttrsAndChildren);
--- a/content/base/src/moz.build
+++ b/content/base/src/moz.build
@@ -193,16 +193,17 @@ MSVC_ENABLE_PGO = True
 
 include('/ipc/chromium/chromium-config.mozbuild')
 
 FINAL_LIBRARY = 'gklayout'
 LOCAL_INCLUDES += [
     '/caps/include',
     '/content/html/content/src',
     '/content/html/document/src',
+    '/content/svg/content/src',
     '/content/xml/content/src',
     '/content/xml/document/src',
     '/content/xul/content/src',
     '/content/xul/document/src',
     '/docshell/base',
     '/dom/base',
     '/dom/events',
     '/dom/ipc',
--- a/content/base/src/nsContentUtils.cpp
+++ b/content/base/src/nsContentUtils.cpp
@@ -60,16 +60,17 @@
 #include "nsContentList.h"
 #include "nsContentPolicyUtils.h"
 #include "nsCPrefetchService.h"
 #include "nsCRT.h"
 #include "nsCycleCollectionParticipant.h"
 #include "nsCycleCollector.h"
 #include "nsDataHashtable.h"
 #include "nsDocShellCID.h"
+#include "nsDocument.h"
 #include "nsDOMCID.h"
 #include "nsDOMDataTransfer.h"
 #include "nsDOMJSUtils.h"
 #include "nsDOMMutationObserver.h"
 #include "nsDOMTouchEvent.h"
 #include "nsError.h"
 #include "nsEventDispatcher.h"
 #include "nsEventListenerManager.h"
@@ -2418,16 +2419,49 @@ nsContentUtils::NewURIWithDocumentCharse
                                           nsIURI* aBaseURI)
 {
   return NS_NewURI(aResult, aSpec,
                    aDocument ? aDocument->GetDocumentCharacterSet().get() : nullptr,
                    aBaseURI, sIOService);
 }
 
 // static
+bool
+nsContentUtils::IsCustomElementName(nsIAtom* aName)
+{
+  // The custom element name identifies a custom element and is a sequence of
+  // alphanumeric ASCII characters that must match the NCName production and
+  // contain a U+002D HYPHEN-MINUS character.
+  nsDependentAtomString str(aName);
+  const char16_t* colon;
+  if (NS_FAILED(nsContentUtils::CheckQName(str, false, &colon)) || colon ||
+      str.FindChar('-') == -1) {
+    return false;
+  }
+
+  // The custom element name must not be one of the following values:
+  //  annotation-xml
+  //  color-profile
+  //  font-face
+  //  font-face-src
+  //  font-face-uri
+  //  font-face-format
+  //  font-face-name
+  //  missing-glyph
+  return aName != nsGkAtoms::annotation_xml_ &&
+         aName != nsGkAtoms::colorProfile &&
+         aName != nsGkAtoms::font_face &&
+         aName != nsGkAtoms::font_face_src &&
+         aName != nsGkAtoms::font_face_uri &&
+         aName != nsGkAtoms::font_face_format &&
+         aName != nsGkAtoms::font_face_name &&
+         aName != nsGkAtoms::missingGlyph;
+}
+
+// static
 nsresult
 nsContentUtils::CheckQName(const nsAString& aQualifiedName,
                            bool aNamespaceAware,
                            const char16_t** aColon)
 {
   const char* colon = nullptr;
   const char16_t* begin = aQualifiedName.BeginReading();
   const char16_t* end = aQualifiedName.EndReading();
@@ -4752,16 +4786,17 @@ nsContentUtils::EnterMicroTask()
 }
 
 void
 nsContentUtils::LeaveMicroTask()
 {
   MOZ_ASSERT(NS_IsMainThread());
   if (--sMicroTaskLevel == 0) {
     nsDOMMutationObserver::HandleMutations();
+    nsDocument::ProcessBaseElementQueue();
   }
 }
 
 bool
 nsContentUtils::IsInMicroTask()
 {
   MOZ_ASSERT(NS_IsMainThread());
   return sMicroTaskLevel != 0;
--- a/content/base/src/nsDocument.cpp
+++ b/content/base/src/nsDocument.cpp
@@ -158,30 +158,32 @@
 #include "mozilla/dom/EncodingUtils.h"
 #include "mozilla/dom/quota/QuotaManager.h"
 #include "nsDOMNavigationTiming.h"
 #include "nsEventStateManager.h"
 
 #include "nsSMILAnimationController.h"
 #include "imgIContainer.h"
 #include "nsSVGUtils.h"
+#include "SVGElementFactory.h"
 
 #include "nsRefreshDriver.h"
 
 // FOR CSP (autogenerated by xpidl)
 #include "nsIContentSecurityPolicy.h"
 #include "nsCSPService.h"
 #include "nsHTMLStyleSheet.h"
 #include "nsHTMLCSSStyleSheet.h"
 #include "mozilla/dom/DOMImplementation.h"
 #include "mozilla/dom/ShadowRoot.h"
 #include "mozilla/dom/Comment.h"
 #include "nsTextNode.h"
 #include "mozilla/dom/Link.h"
 #include "mozilla/dom/HTMLElementBinding.h"
+#include "mozilla/dom/SVGElementBinding.h"
 #include "nsXULAppAPI.h"
 #include "nsDOMTouchEvent.h"
 #include "mozilla/dom/Touch.h"
 #include "DictionaryHelpers.h"
 #include "GeneratedEvents.h"
 
 #include "mozilla/Preferences.h"
 
@@ -307,16 +309,105 @@ nsIdentifierMapEntry::RemoveContentChang
 
 struct FireChangeArgs {
   Element* mFrom;
   Element* mTo;
   bool mImageOnly;
   bool mHaveImageOverride;
 };
 
+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;
+      mOwnerData->mCreatedCallbackInvoked = true;
+      static_cast<LifecycleCreatedCallback *>(mCallback.get())->Call(mThisObject, rv);
+      mOwnerData->mElementIsBeingCreated = false;
+      break;
+    case nsIDocument::eEnteredView:
+      static_cast<LifecycleEnteredViewCallback *>(mCallback.get())->Call(mThisObject, rv);
+      break;
+    case nsIDocument::eLeftView:
+      static_cast<LifecycleLeftViewCallback *>(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)
+{
+}
+
+CustomElementDefinition::CustomElementDefinition(JSObject* aPrototype,
+                                                 nsIAtom* aType,
+                                                 nsIAtom* aLocalName,
+                                                 LifecycleCallbacks* aCallbacks,
+                                                 uint32_t aNamespaceID,
+                                                 uint32_t aDocOrder)
+  : mPrototype(aPrototype),
+    mType(aType),
+    mLocalName(aLocalName),
+    mCallbacks(aCallbacks),
+    mNamespaceID(aNamespaceID),
+    mDocOrder(aDocOrder)
+{
+}
+
+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
+
 static PLDHashOperator
 FireChangeEnumerator(nsIdentifierMapEntry::ChangeCallbackEntry *aEntry, void *aArg)
 {
   FireChangeArgs* args = static_cast<FireChangeArgs*>(aArg);
   // Don't fire image changes for non-image observers, and don't fire element
   // changes for image observers when an image override is active.
   if (aEntry->mKey.mForImage ? (args->mHaveImageOverride && !args->mImageOnly) :
                                args->mImageOnly)
@@ -1433,16 +1524,22 @@ nsDocument::nsDocument(const char* aCont
            ("DOCUMENT %p created", this));
 
   if (!gCspPRLog)
     gCspPRLog = PR_NewLogModule("CSP");
 #endif
 
   // Start out mLastStyleSheetSet as null, per spec
   SetDOMStringToNull(mLastStyleSheetSet);
+
+  if (sProcessingStack.empty()) {
+    sProcessingStack.construct();
+    // Add the base queue sentinel to the processing stack.
+    sProcessingStack.ref().AppendElement((CustomElementData*) nullptr);
+  }
 }
 
 static PLDHashOperator
 ClearAllBoxObjects(nsIContent* aKey, nsPIBoxObject* aBoxObject, void* aUserArg)
 {
   if (aBoxObject) {
     aBoxObject->Clear();
   }
@@ -1508,17 +1605,19 @@ nsDocument::~nsDocument()
       }
       Accumulate(Telemetry::MIXED_CONTENT_PAGE_LOAD, mixedContentLevel);
     }
   }
 
   mInDestructor = true;
   mInUnlinkOrDeletion = true;
 
-  mCustomPrototypes.Clear();
+  if (mRegistry) {
+    mRegistry->Clear();
+  }
 
   mozilla::DropJSObjects(this);
 
   // Clear mObservers to keep it in sync with the mutationobserver list
   mObservers.Clear();
 
   if (mStyleSheetSetList) {
     mStyleSheetSetList->Disconnect();
@@ -1678,16 +1777,67 @@ NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_IN_CC_
   return Element::CanSkipInCC(tmp);
 NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_IN_CC_END
 
 NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_THIS_BEGIN(nsDocument)
   return Element::CanSkipThis(tmp);
 NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_THIS_END
 
 static PLDHashOperator
+CustomDefinitionsTraverse(CustomElementHashKey* aKey,
+                          CustomElementDefinition* aDefinition,
+                          void* aArg)
+{
+  nsCycleCollectionTraversalCallback* cb =
+    static_cast<nsCycleCollectionTraversalCallback*>(aArg);
+
+  nsAutoPtr<LifecycleCallbacks>& callbacks = aDefinition->mCallbacks;
+
+  if (callbacks->mAttributeChangedCallback.WasPassed()) {
+    NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(*cb,
+      "mCustomDefinitions->mCallbacks->mAttributeChangedCallback");
+    cb->NoteXPCOMChild(aDefinition->mCallbacks->mAttributeChangedCallback.Value());
+  }
+
+  if (callbacks->mCreatedCallback.WasPassed()) {
+    NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(*cb,
+      "mCustomDefinitions->mCallbacks->mCreatedCallback");
+    cb->NoteXPCOMChild(aDefinition->mCallbacks->mCreatedCallback.Value());
+  }
+
+  if (callbacks->mEnteredViewCallback.WasPassed()) {
+    NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(*cb,
+      "mCustomDefinitions->mCallbacks->mEnteredViewCallback");
+    cb->NoteXPCOMChild(aDefinition->mCallbacks->mEnteredViewCallback.Value());
+  }
+
+  if (callbacks->mLeftViewCallback.WasPassed()) {
+    NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(*cb,
+      "mCustomDefinitions->mCallbacks->mLeftViewCallback");
+    cb->NoteXPCOMChild(aDefinition->mCallbacks->mLeftViewCallback.Value());
+  }
+
+  return PL_DHASH_NEXT;
+}
+
+static PLDHashOperator
+CandidatesTraverse(CustomElementHashKey* aKey,
+                   nsTArray<nsRefPtr<Element>>* aData,
+                   void* aArg)
+{
+  nsCycleCollectionTraversalCallback *cb =
+    static_cast<nsCycleCollectionTraversalCallback*>(aArg);
+  for (size_t i = 0; i < aData->Length(); ++i) {
+    NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(*cb, "mCandidatesMap->Element");
+    cb->NoteXPCOMChild(aData->ElementAt(i));
+  }
+  return PL_DHASH_NEXT;
+}
+
+static PLDHashOperator
 SubDocTraverser(PLDHashTable *table, PLDHashEntryHdr *hdr, uint32_t number,
                 void *arg)
 {
   SubDocMapEntry *entry = static_cast<SubDocMapEntry*>(hdr);
   nsCycleCollectionTraversalCallback *cb =
     static_cast<nsCycleCollectionTraversalCallback*>(arg);
 
   NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(*cb, "mSubDocuments entry->mKey");
@@ -1842,50 +1992,60 @@ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_
     cb.NoteXPCOMChild(tmp->mFrameRequestCallbacks[i].mCallback.GetISupports());
   }
 
   // Traverse animation components
   if (tmp->mAnimationController) {
     tmp->mAnimationController->Traverse(&cb);
   }
 
+  if (tmp->mRegistry) {
+    tmp->mRegistry->mCustomDefinitions.EnumerateRead(CustomDefinitionsTraverse, &cb);
+    tmp->mRegistry->mCandidatesMap.EnumerateRead(CandidatesTraverse, &cb);
+  }
+
   if (tmp->mSubDocuments && tmp->mSubDocuments->ops) {
     PL_DHashTableEnumerate(tmp->mSubDocuments, SubDocTraverser, &cb);
   }
 
   if (tmp->mCSSLoader) {
     tmp->mCSSLoader->TraverseCachedSheets(cb);
   }
 
   for (uint32_t i = 0; i < tmp->mHostObjectURIs.Length(); ++i) {
     nsHostObjectProtocolHandler::Traverse(tmp->mHostObjectURIs[i], cb);
   }
 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
 
-
-struct CustomPrototypeTraceArgs {
+struct CustomDefinitionTraceArgs
+{
   const TraceCallbacks& callbacks;
   void* closure;
 };
 
-
 static PLDHashOperator
-CustomPrototypeTrace(const nsAString& aName, JS::Heap<JSObject*>& aObject, void *aArg)
-{
-  CustomPrototypeTraceArgs* traceArgs = static_cast<CustomPrototypeTraceArgs*>(aArg);
-  MOZ_ASSERT(aObject, "Protocol object value must not be null");
-  traceArgs->callbacks.Trace(&aObject, "mCustomPrototypes entry", traceArgs->closure);
+CustomDefinitionTrace(CustomElementHashKey *aKey,
+                      CustomElementDefinition *aData,
+                      void *aArg)
+{
+  CustomDefinitionTraceArgs* traceArgs = static_cast<CustomDefinitionTraceArgs*>(aArg);
+  MOZ_ASSERT(aData, "Definition must not be null");
+  traceArgs->callbacks.Trace(&aData->mPrototype, "mCustomDefinitions prototype",
+                             traceArgs->closure);
   return PL_DHASH_NEXT;
 }
 
 NS_IMPL_CYCLE_COLLECTION_CLASS(nsDocument)
 
 NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(nsDocument)
-  CustomPrototypeTraceArgs customPrototypeArgs = { aCallbacks, aClosure };
-  tmp->mCustomPrototypes.Enumerate(CustomPrototypeTrace, &customPrototypeArgs);
+  CustomDefinitionTraceArgs customDefinitionArgs = { aCallbacks, aClosure };
+  if (tmp->mRegistry) {
+    tmp->mRegistry->mCustomDefinitions.EnumerateRead(CustomDefinitionTrace,
+                                                     &customDefinitionArgs);
+  }
   if (tmp->PreservingWrapper()) {
     NS_IMPL_CYCLE_COLLECTION_TRACE_JSVAL_MEMBER_CALLBACK(mExpandoAndGeneration.expando);
   }
   NS_IMPL_CYCLE_COLLECTION_TRACE_PRESERVED_WRAPPER
 NS_IMPL_CYCLE_COLLECTION_TRACE_END
 
 
 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(nsDocument)
@@ -1946,17 +2106,19 @@ NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(ns
 
   // nsDocument has a pretty complex destructor, so we're going to
   // assume that *most* cycles you actually want to break somewhere
   // else, and not unlink an awful lot here.
 
   tmp->mIdentifierMap.Clear();
   tmp->mExpandoAndGeneration.Unlink();
 
-  tmp->mCustomPrototypes.Clear();
+  if (tmp->mRegistry) {
+    tmp->mRegistry->Clear();
+  }
 
   if (tmp->mAnimationController) {
     tmp->mAnimationController->Unlink();
   }
 
   tmp->mPendingTitleChangeEvent.Revoke();
 
   if (tmp->mCSSLoader) {
@@ -2150,17 +2312,19 @@ nsDocument::ResetToURI(nsIURI *aURI, nsI
       mChildren.RemoveChildAt(i);
       nsNodeUtils::ContentRemoved(this, content, i, previousSibling);
       content->UnbindFromTree();
     }
     mCachedRootElement = nullptr;
   }
   mInUnlinkOrDeletion = oldVal;
 
-  mCustomPrototypes.Clear();
+  if (mRegistry) {
+    mRegistry->Clear();
+  }
 
   // Reset our stylesheets
   ResetStylesheetsToURI(aURI);
 
   // Release the listener manager
   if (mListenerManager) {
     mListenerManager->Disconnect();
     mListenerManager = nullptr;
@@ -4342,16 +4506,17 @@ nsDocument::SetScriptGlobalObject(nsIScr
 #endif
         bool allowDNSPrefetch;
         docShell->GetAllowDNSPrefetch(&allowDNSPrefetch);
         mAllowDNSPrefetch = allowDNSPrefetch;
       }
     }
 
     MaybeRescheduleAnimationFrameNotifications();
+    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<nsPIDOMWindow> window = do_QueryInterface(mScriptGlobalObject);
   mWindow = window;
 
   // Now that we know what our window is, we can flush the CSP errors to the
@@ -4985,16 +5150,74 @@ nsIDocument::CreateElement(const nsAStri
   rv = CreateElem(needsLowercase ? lcTagName : aTagName,
                   nullptr, mDefaultElementType, getter_AddRefs(content));
   if (rv.Failed()) {
     return nullptr;
   }
   return dont_AddRef(content.forget().get()->AsElement());
 }
 
+void
+nsDocument::SwizzleCustomElement(Element* aElement,
+                                 const nsAString& aTypeExtension,
+                                 uint32_t aNamespaceID,
+                                 ErrorResult& rv)
+{
+  nsCOMPtr<nsIAtom> typeAtom(do_GetAtom(aTypeExtension));
+  nsCOMPtr<nsIAtom> tagAtom = aElement->Tag();
+  if (!mRegistry || tagAtom == typeAtom) {
+    return;
+  }
+
+  CustomElementDefinition* data;
+  CustomElementHashKey key(aNamespaceID, typeAtom);
+  if (!mRegistry->mCustomDefinitions.Get(&key, &data)) {
+    // The type extension doesn't exist in the registry,
+    // thus we don't need to swizzle, 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;
+  }
+
+  if (!aElement->HasAttr(kNameSpaceID_None, nsGkAtoms::is)) {
+    // Swizzling in the parser happens after the "is" attribute is added.
+    aElement->SetAttr(kNameSpaceID_None, nsGkAtoms::is, aTypeExtension, true);
+  }
+
+  // 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);
+}
+
+already_AddRefed<Element>
+nsDocument::CreateElement(const nsAString& aTagName,
+                          const nsAString& aTypeExtension,
+                          ErrorResult& rv)
+{
+  nsRefPtr<Element> elem = nsIDocument::CreateElement(aTagName, rv);
+  if (rv.Failed()) {
+    return nullptr;
+  }
+
+  SwizzleCustomElement(elem, aTypeExtension,
+                       GetDefaultNamespaceID(), rv);
+  if (rv.Failed()) {
+    return nullptr;
+  }
+
+  return elem.forget();
+}
+
 NS_IMETHODIMP
 nsDocument::CreateElementNS(const nsAString& aNamespaceURI,
                             const nsAString& aQualifiedName,
                             nsIDOMElement** aReturn)
 {
   *aReturn = nullptr;
   ErrorResult rv;
   nsCOMPtr<Element> element =
@@ -5022,16 +5245,46 @@ nsIDocument::CreateElementNS(const nsASt
   rv = NS_NewElement(getter_AddRefs(element), nodeInfo.forget(),
                      NOT_FROM_PARSER);
   if (rv.Failed()) {
     return nullptr;
   }
   return element.forget();
 }
 
+already_AddRefed<Element>
+nsDocument::CreateElementNS(const nsAString& aNamespaceURI,
+                            const nsAString& aQualifiedName,
+                            const nsAString& aTypeExtension,
+                            ErrorResult& rv)
+{
+  nsRefPtr<Element> elem = nsIDocument::CreateElementNS(aNamespaceURI,
+                                                        aQualifiedName,
+                                                        rv);
+  if (rv.Failed()) {
+    return nullptr;
+  }
+
+  int32_t nameSpaceId = kNameSpaceID_Wildcard;
+  if (!aNamespaceURI.EqualsLiteral("*")) {
+    rv = nsContentUtils::NameSpaceManager()->RegisterNameSpace(aNamespaceURI,
+                                                               nameSpaceId);
+    if (rv.Failed()) {
+      return nullptr;
+    }
+  }
+
+  SwizzleCustomElement(elem, aTypeExtension, nameSpaceId, rv);
+  if (rv.Failed()) {
+    return nullptr;
+  }
+
+  return elem.forget();
+}
+
 NS_IMETHODIMP
 nsDocument::CreateTextNode(const nsAString& aData, nsIDOMText** aReturn)
 {
   *aReturn = nsIDocument::CreateTextNode(aData).get();
   return NS_OK;
 }
 
 already_AddRefed<nsTextNode>
@@ -5206,67 +5459,358 @@ nsIDocument::CreateAttributeNS(const nsA
     return nullptr;
   }
 
   nsRefPtr<Attr> attribute = new Attr(nullptr, nodeInfo.forget(),
                                       EmptyString(), true);
   return attribute.forget();
 }
 
-static bool
-CustomElementConstructor(JSContext *aCx, unsigned aArgc, JS::Value* aVp)
+bool
+nsDocument::CustomElementConstructor(JSContext* aCx, unsigned aArgc, JS::Value* aVp)
 {
   JS::CallArgs args = JS::CallArgsFromVp(aArgc, aVp);
 
   JS::Rooted<JSObject*> global(aCx,
     JS_GetGlobalForObject(aCx, &args.callee()));
   nsCOMPtr<nsPIDOMWindow> window = do_QueryWrapper(aCx, global);
   MOZ_ASSERT(window, "Should have a non-null window");
 
-  nsIDocument* document = window->GetDoc();
+  nsDocument* document = static_cast<nsDocument*>(window->GetDoc());
 
   // Function name is the type of the custom element.
   JSString* jsFunName =
     JS_GetFunctionId(JS_ValueToFunction(aCx, args.calleev()));
   nsDependentJSString elemName;
   if (!elemName.init(aCx, jsFunName)) {
-    return false;
-  }
+    return true;
+  }
+
+  nsCOMPtr<nsIAtom> typeAtom(do_GetAtom(elemName));
+  CustomElementHashKey key(kNameSpaceID_None, typeAtom);
+  CustomElementDefinition* definition;
+  if (!document->mRegistry->mCustomDefinitions.Get(&key, &definition)) {
+    return true;
+  }
+
+  nsDependentAtomString localName(definition->mLocalName);
 
   nsCOMPtr<nsIContent> newElement;
-  nsresult rv = document->CreateElem(elemName, nullptr, kNameSpaceID_XHTML,
+  nsresult rv = document->CreateElem(localName, nullptr,
+                                     definition->mNamespaceID,
                                      getter_AddRefs(newElement));
+  NS_ENSURE_SUCCESS(rv, true);
+
+  ErrorResult errorResult;
+  nsCOMPtr<Element> element = do_QueryInterface(newElement);
+  document->SwizzleCustomElement(element, elemName, definition->mNamespaceID,
+                                 errorResult);
+  if (errorResult.Failed()) {
+    return true;
+  }
+
   rv = nsContentUtils::WrapNative(aCx, global, newElement, newElement,
                                   args.rval());
-  NS_ENSURE_SUCCESS(rv, false);
+  NS_ENSURE_SUCCESS(rv, true);
 
   return true;
 }
 
+nsresult
+nsDocument::RegisterUnresolvedElement(Element* aElement, nsIAtom* aTypeName)
+{
+  if (!mRegistry) {
+    return NS_OK;
+  }
+
+  nsINodeInfo* 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<nsRefPtr<Element>>* unresolved;
+  mRegistry->mCandidatesMap.Get(&key, &unresolved);
+  if (!unresolved) {
+    unresolved = new nsTArray<nsRefPtr<Element>>();
+    // Ownership of unresolved is taken by mCandidatesMap.
+    mRegistry->mCandidatesMap.Put(&key, unresolved);
+  }
+
+  nsRefPtr<Element>* elem = unresolved->AppendElement();
+  *elem = aElement;
+
+  return NS_OK;
+}
+
+namespace {
+
+class ProcessStackRunner MOZ_FINAL : public nsIRunnable
+{
+  public:
+  ProcessStackRunner(bool aIsBaseQueue = false)
+    : mIsBaseQueue(aIsBaseQueue)
+  {
+  }
+  NS_DECL_ISUPPORTS
+  NS_IMETHOD Run() MOZ_OVERRIDE
+  {
+    nsDocument::ProcessTopElementQueue(mIsBaseQueue);
+    return NS_OK;
+  }
+  bool mIsBaseQueue;
+};
+
+NS_IMPL_ISUPPORTS1(ProcessStackRunner, nsIRunnable);
+
+} // anonymous namespace
+
+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) {
+    nsINodeInfo* 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::eEnteredView:
+      if (definition->mCallbacks->mEnteredViewCallback.WasPassed()) {
+        func = definition->mCallbacks->mEnteredViewCallback.Value();
+      }
+      break;
+
+    case nsIDocument::eLeftView:
+      if (definition->mCallbacks->mLeftViewCallback.WasPassed()) {
+        func = definition->mCallbacks->mLeftViewCallback.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.ref().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.
+    // Don't push a queue for the level 0 microtask (base element queue)
+    // because we don't want to process the queue until the
+    // microtask checkpoint.
+    bool shouldPushElementQueue = nsContentUtils::MicroTaskLevel() > 0 &&
+      (!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.ref().AppendElement((CustomElementData*) nullptr);
+    }
+
+    sProcessingStack.ref().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.
+      nsContentUtils::AddScriptRunner(new ProcessStackRunner());
+    }
+  }
+}
+
+// static
+void
+nsDocument::ProcessBaseElementQueue()
+{
+  // Prevent re-entrance. Also, if a microtask checkpoint is reached
+  // and there is no processing stack to process, then we are done.
+  if (sProcessingBaseElementQueue || sProcessingStack.empty()) {
+    return;
+  }
+
+  MOZ_ASSERT(nsContentUtils::MicroTaskLevel() == 0);
+  sProcessingBaseElementQueue = true;
+  nsContentUtils::AddScriptRunner(new ProcessStackRunner(true));
+}
+
+// static
+void
+nsDocument::ProcessTopElementQueue(bool aIsBaseQueue)
+{
+  MOZ_ASSERT(nsContentUtils::IsSafeToRunScript());
+
+  nsTArray<CustomElementData*>& stack = sProcessingStack.ref();
+  uint32_t firstQueue = stack.LastIndexOf((CustomElementData*) nullptr);
+
+  if (aIsBaseQueue && firstQueue != 0) {
+    return;
+  }
+
+  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);
+    sProcessingBaseElementQueue = false;
+  }
+}
+
 bool
 nsDocument::RegisterEnabled()
 {
   static bool sPrefValue =
     Preferences::GetBool("dom.webcomponents.enabled", false);
   return sPrefValue;
 }
 
+// static
+Maybe<nsTArray<mozilla::dom::CustomElementData*>>
+nsDocument::sProcessingStack;
+
+// static
+bool
+nsDocument::sProcessingBaseElementQueue;
+
 JSObject*
-nsDocument::Register(JSContext* aCx, const nsAString& aName,
-                     const ElementRegistrationOptions& aOptions,
-                     ErrorResult& rv)
-{
+nsDocument::RegisterElement(JSContext* aCx, const nsAString& aType,
+                            const ElementRegistrationOptions& aOptions,
+                            ErrorResult& rv)
+{
+  if (!mRegistry) {
+    rv.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
+    return nullptr;
+  }
+
+  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;
-  nsContentUtils::ASCIIToLower(aName, lcName);
-  if (!StringBeginsWith(lcName, NS_LITERAL_STRING("x-"))) {
-    rv.Throw(NS_ERROR_DOM_INVALID_CHARACTER_ERR);
+  if (IsHTML()) {
+    nsContentUtils::ASCIIToLower(aOptions.mExtends, lcName);
+  } else {
+    lcName.Assign(aOptions.mExtends);
+  }
+
+  nsCOMPtr<nsIAtom> typeAtom(do_GetAtom(lcType));
+  if (!nsContentUtils::IsCustomElementName(typeAtom)) {
+    rv.Throw(NS_ERROR_DOM_SYNTAX_ERR);
     return nullptr;
   }
-  if (NS_FAILED(nsContentUtils::CheckQName(lcName, false))) {
-    rv.Throw(NS_ERROR_DOM_INVALID_CHARACTER_ERR);
+
+  // 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_None, typeAtom);
+  if (definitions.Get(&duplicateFinder)) {
+    rv.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
     return nullptr;
   }
 
   nsIGlobalObject* sgo = GetScopeObject();
   if (!sgo) {
     rv.Throw(NS_ERROR_UNEXPECTED);
     return nullptr;
   }
@@ -5276,119 +5820,181 @@ nsDocument::Register(JSContext* aCx, con
 
   JS::Handle<JSObject*> htmlProto(
     HTMLElementBinding::GetProtoObject(aCx, global));
   if (!htmlProto) {
     rv.Throw(NS_ERROR_OUT_OF_MEMORY);
     return nullptr;
   }
 
+  int32_t namespaceID = kNameSpaceID_XHTML;
   JS::Rooted<JSObject*> protoObject(aCx);
   if (!aOptions.mPrototype) {
     protoObject = JS_NewObject(aCx, nullptr, htmlProto, JS::NullPtr());
     if (!protoObject) {
       rv.Throw(NS_ERROR_UNEXPECTED);
       return nullptr;
     }
   } else {
-    // If a prototype is provided, we must check to ensure that it inherits
-    // from HTMLElement.
+    // If a prototype is provided, we must check to ensure that it is from the
+    // same browsing context as us.
     protoObject = aOptions.mPrototype;
-    if (!JS_WrapObject(aCx, &protoObject)) {
+    if (JS_GetGlobalForObject(aCx, protoObject) != global) {
+      rv.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
+      return nullptr;
+    }
+
+    // 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(protoObject);
+    if (IsDOMIfaceAndProtoClass(clasp)) {
+      rv.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
+      return nullptr;
+    }
+
+    JS::Rooted<JSPropertyDescriptor> descRoot(aCx);
+    JS::MutableHandle<JSPropertyDescriptor> desc(&descRoot);
+    if(!JS_GetPropertyDescriptor(aCx, protoObject, "constructor", 0, desc)) {
       rv.Throw(NS_ERROR_UNEXPECTED);
       return nullptr;
     }
 
-    // Check the proto chain for HTMLElement prototype.
-    JS::Rooted<JSObject*> protoProto(aCx);
-    if (!JS_GetPrototype(aCx, protoObject, &protoProto)) {
-      rv.Throw(NS_ERROR_UNEXPECTED);
+    // Check if non-configurable
+    if (desc.isPermanent()) {
+      rv.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
       return nullptr;
     }
+
+    JS::Handle<JSObject*> svgProto(
+      SVGElementBinding::GetProtoObject(aCx, global));
+    if (!svgProto) {
+      rv.Throw(NS_ERROR_OUT_OF_MEMORY);
+      return nullptr;
+    }
+
+    JS::Rooted<JSObject*> protoProto(aCx, protoObject);
+
+    // If PROTOTYPE's interface inherits from SVGElement, set NAMESPACE to SVG
+    // Namespace.
     while (protoProto) {
       if (protoProto == htmlProto) {
         break;
       }
+
+      if (protoProto == svgProto) {
+        namespaceID = kNameSpaceID_SVG;
+        break;
+      }
+
       if (!JS_GetPrototype(aCx, protoProto, &protoProto)) {
         rv.Throw(NS_ERROR_UNEXPECTED);
         return nullptr;
       }
     }
-
-    if (!protoProto) {
-      rv.Throw(NS_ERROR_DOM_TYPE_MISMATCH_ERR);
+  }
+
+  // If name was provided and not null...
+  nsCOMPtr<nsIAtom> nameAtom;
+  if (!lcName.IsEmpty()) {
+    // Let BASE be the element interface for NAME and NAMESPACE.
+    bool known = false;
+    nameAtom = do_GetAtom(lcName);
+    if (namespaceID == kNameSpaceID_XHTML) {
+      nsIParserService* ps = nsContentUtils::GetParserService();
+      if (!ps) {
+        rv.Throw(NS_ERROR_UNEXPECTED);
+        return nullptr;
+      }
+
+      known =
+        ps->HTMLCaseSensitiveAtomTagToId(nameAtom) != eHTMLTag_userdefined;
+    } else {
+      known = SVGElementFactory::Exists(nameAtom);
+    }
+
+    // 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 nullptr;
     }
-  }
-
-  // Associate the prototype with the custom element.
-  mCustomPrototypes.Put(lcName, protoObject);
+  } else {
+    // If NAMESPACE is SVG Namespace, set ERROR to InvalidName and stop.
+    if (namespaceID == kNameSpaceID_SVG) {
+      rv.Throw(NS_ERROR_UNEXPECTED);
+      return nullptr;
+    }
+
+    nameAtom = typeAtom;
+  }
+
+  nsAutoPtr<LifecycleCallbacks> callbacksHolder(new LifecycleCallbacks());
+  JS::RootedValue rootedv(aCx, JS::ObjectValue(*protoObject));
+  if (!callbacksHolder->Init(aCx, rootedv)) {
+    return nullptr;
+  }
+
+  // Associate the definition with the custom element.
+  CustomElementHashKey key(namespaceID, typeAtom);
+  LifecycleCallbacks* callbacks = callbacksHolder.forget();
+  CustomElementDefinition* definition =
+    new CustomElementDefinition(protoObject,
+                                typeAtom,
+                                nameAtom,
+                                callbacks,
+                                namespaceID,
+                                0 /* TODO dependent on HTML imports. Bug 877072 */);
+  definitions.Put(&key, definition);
 
   // Do element upgrade.
-  nsRefPtr<nsContentList> list = GetElementsByTagName(lcName);
-  for (uint32_t i = 0; i < list->Length(false); i++) {
-    nsCOMPtr<nsINode> oldNode = list->Item(i, false);
-
-    // TODO(wchen): Perform upgrade on Shadow DOM when implemented.
-    // Bug 806506.
-    nsCOMPtr<nsINode> newNode;
-    rv = nsNodeUtils::Clone(oldNode, true, getter_AddRefs(newNode));
-    if (rv.Failed()) {
-      return nullptr;
-    }
-
-    nsINode* parentNode = oldNode->GetParentNode();
-    MOZ_ASSERT(parentNode, "Node obtained by GetElementsByTagName.");
-    nsCOMPtr<Element> newElement = do_QueryInterface(newNode);
-    MOZ_ASSERT(newElement, "Cloned of node obtained by GetElementsByTagName.");
-
-    parentNode->ReplaceChild(*newNode, *oldNode, rv);
-    if (rv.Failed()) {
-      return nullptr;
-    }
-
-    // Dispatch elementreplaced to replaced elements.
-    nsCOMPtr<nsIDOMEvent> event;
-    rv = CreateEvent(NS_LITERAL_STRING("elementreplace"), getter_AddRefs(event));
-    if (rv.Failed()) {
-      return nullptr;
-    }
-
-    if (aOptions.mLifecycle.mCreated) {
-      // Don't abort the upgrade algorithm if the callback throws an
-      // exception.
-      ErrorResult dummy;
-      aOptions.mLifecycle.mCreated->Call(newElement, dummy);
-    }
-
-    nsCOMPtr<nsIDOMElementReplaceEvent> ptEvent = do_QueryInterface(event);
-    MOZ_ASSERT(ptEvent);
-
-    nsCOMPtr<nsIDOMElement> element = do_QueryInterface(newElement);
-    rv = ptEvent->InitElementReplaceEvent(NS_LITERAL_STRING("elementreplace"),
-                                          false, false, element);
-    if (rv.Failed()) {
-      return nullptr;
-    }
-
-    event->SetTrusted(true);
-    event->SetTarget(oldNode);
-    nsEventDispatcher::DispatchDOMEvent(oldNode, nullptr, event,
-                                        nullptr, nullptr);
-  }
-
-  nsContentUtils::DispatchTrustedEvent(this, static_cast<nsIDocument*>(this),
-                                       NS_LITERAL_STRING("elementupgrade"),
-                                       true, true);
+  nsAutoPtr<nsTArray<nsRefPtr<Element>>> candidates;
+  mRegistry->mCandidatesMap.RemoveAndForget(&key, candidates);
+  if (candidates) {
+    for (size_t i = 0; i < candidates->Length(); ++i) {
+      Element *elem = candidates->ElementAt(i);
+
+      // 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;
+      }
+
+      nsWrapperCache* cache;
+      CallQueryInterface(elem, &cache);
+      MOZ_ASSERT(cache, "Element doesn't support wrapper cache?");
+
+      JS::RootedObject wrapper(aCx);
+      if ((wrapper = cache->GetWrapper())) {
+        if (!JS_SetPrototype(aCx, wrapper, protoObject)) {
+          continue;
+        }
+      }
+
+      EnqueueLifecycleCallback(nsIDocument::eCreated, elem, nullptr, definition);
+      if (elem->GetCurrentDoc()) {
+        // Normally callbacks can not be enqueued until the created
+        // callback has been invoked, however, the entered view callback
+        // in element upgrade is an exception so pretend the created
+        // callback has been invoked.
+        elem->GetCustomElementData()->mCreatedCallbackInvoked = true;
+
+        EnqueueLifecycleCallback(nsIDocument::eEnteredView, elem, nullptr, definition);
+      }
+    }
+  }
 
   // Create constructor to return. Store the name of the custom element as the
   // name of the function.
-  JSFunction* constructor = JS_NewFunction(aCx, CustomElementConstructor, 0,
+  JSFunction* constructor = JS_NewFunction(aCx, nsDocument::CustomElementConstructor, 0,
                                            JSFUN_CONSTRUCTOR, JS::NullPtr(),
-                                           NS_ConvertUTF16toUTF8(lcName).get());
+                                           NS_ConvertUTF16toUTF8(lcType).get());
   JSObject* constructorObject = JS_GetFunctionObject(constructor);
   return constructorObject;
 }
 
 NS_IMETHODIMP
 nsDocument::GetElementsByTagName(const nsAString& aTagname,
                                  nsIDOMNodeList** aReturn)
 {
@@ -7846,17 +8452,19 @@ nsDocument::Destroy()
 
   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();
 
-  mCustomPrototypes.Clear();
+  if (mRegistry) {
+    mRegistry->Clear();
+  }
 
   // XXX We really should let cycle collection do this, but that currently still
   //     leaks (see https://bugzilla.mozilla.org/show_bug.cgi?id=406684).
   ReleaseWrapper(static_cast<nsINode*>(this));
 }
 
 void
 nsDocument::RemovedFromDocShell()
@@ -11102,16 +11710,17 @@ nsIDocument::GetMozPointerLockElement()
 
   return pointerLockedElement;
 }
 
 void
 nsDocument::XPCOMShutdown()
 {
   gPendingPointerLockRequest = nullptr;
+  sProcessingStack.destroyIfConstructed();
 }
 
 void
 nsDocument::UpdateVisibilityState()
 {
   dom::VisibilityState oldState = mVisibilityState;
   mVisibilityState = GetVisibilityState();
   if (oldState != mVisibilityState) {
--- a/content/base/src/nsDocument.h
+++ b/content/base/src/nsDocument.h
@@ -1,9 +1,10 @@
 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=2 sw=2 et tw=80: */
 /* 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/. */
 
 /*
  * Base class for all our document implementations.
  */
 
@@ -94,16 +95,18 @@ class nsWindowSizes;
 class nsHtml5TreeOpExecutor;
 class nsDocumentOnStack;
 class nsPointerLockPermissionRequest;
 class nsISecurityConsoleMessage;
 
 namespace mozilla {
 namespace dom {
 class UndoManager;
+class LifecycleCallbacks;
+class CallbackFunction;
 }
 }
 
 /**
  * Right now our identifier map entries contain information for 'name'
  * and 'id' mappings of a given string. This is so that
  * nsHTMLDocument::ResolveName only has to do one hash lookup instead
  * of two. It's not clear whether this still matters for performance.
@@ -232,16 +235,163 @@ private:
   // empty if there are no elements with this ID.
   // The elements are stored as weak pointers.
   nsSmallVoidArray mIdContentList;
   nsRefPtr<nsBaseContentList> mNameContentList;
   nsAutoPtr<nsTHashtable<ChangeCallbackEntry> > mChangeCallbacks;
   nsRefPtr<Element> mImageElement;
 };
 
+namespace mozilla {
+namespace dom {
+
+class CustomElementHashKey : public PLDHashEntryHdr
+{
+public:
+  typedef CustomElementHashKey *KeyType;
+  typedef const CustomElementHashKey *KeyTypePointer;
+
+  CustomElementHashKey(int32_t aNamespaceID, nsIAtom *aAtom)
+    : mNamespaceID(aNamespaceID),
+      mAtom(aAtom)
+  {}
+  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_None,
+               "This equals method is not transitive, nor symmetric. "
+               "A key with a namespace of kNamespaceID_None should "
+               "not be stored in a hashtable.");
+    return (kNameSpaceID_None == 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;
+};
+
+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.
+  nsRefPtr<mozilla::dom::Element> mThisObject;
+  nsRefPtr<mozilla::dom::CallbackFunction> mCallback;
+  // The type of callback (eCreated, eEnteredView, 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
+{
+  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();
+};
+
+// The required information for a custom element as defined in:
+// https://dvcs.w3.org/hg/webcomponents/raw-file/tip/spec/custom/index.html
+struct CustomElementDefinition
+{
+  CustomElementDefinition(JSObject* aPrototype,
+                          nsIAtom* aType,
+                          nsIAtom* aLocalName,
+                          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 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;
+
+  // The document custom element order.
+  uint32_t mDocOrder;
+};
+
+} // namespace dom
+} // namespace mozilla
+
 class nsDocHeaderData
 {
 public:
   nsDocHeaderData(nsIAtom* aField, const nsAString& aData)
     : mField(aField), mData(aData), mNext(nullptr)
   {
   }
 
@@ -991,35 +1141,67 @@ public:
   // Posts an event to call UpdateVisibilityState
   virtual void PostVisibilityUpdateEvent() MOZ_OVERRIDE;
 
   virtual void DocAddSizeOfExcludingThis(nsWindowSizes* aWindowSizes) const MOZ_OVERRIDE;
   // DocAddSizeOfIncludingThis is inherited from nsIDocument.
 
   virtual nsIDOMNode* AsDOMNode() MOZ_OVERRIDE { return this; }
 
-  void GetCustomPrototype(const nsAString& aElementName, JS::MutableHandle<JSObject*> prototype)
+  virtual void EnqueueLifecycleCallback(nsIDocument::ElementCallbackType aType,
+                                        Element* aCustomElement,
+                                        mozilla::dom::LifecycleCallbackArgs* aArgs = nullptr,
+                                        mozilla::dom::CustomElementDefinition* aDefinition = nullptr) MOZ_OVERRIDE;
+
+  static void ProcessTopElementQueue(bool aIsBaseQueue = false);
+
+  void GetCustomPrototype(int32_t aNamespaceID,
+                          nsIAtom* aAtom,
+                          JS::MutableHandle<JSObject*> prototype)
   {
-    mCustomPrototypes.Get(aElementName, prototype.address());
+    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) MOZ_OVERRIDE;
+
   // WebIDL bits
   virtual mozilla::dom::DOMImplementation*
     GetImplementation(mozilla::ErrorResult& rv) MOZ_OVERRIDE;
   virtual JSObject*
-  Register(JSContext* aCx, const nsAString& aName,
-           const mozilla::dom::ElementRegistrationOptions& aOptions,
-           mozilla::ErrorResult& rv) MOZ_OVERRIDE;
+    RegisterElement(JSContext* aCx, const nsAString& aName,
+                    const mozilla::dom::ElementRegistrationOptions& aOptions,
+                    mozilla::ErrorResult& rv) MOZ_OVERRIDE;
   virtual nsIDOMStyleSheetList* StyleSheets() MOZ_OVERRIDE;
   virtual void SetSelectedStyleSheetSet(const nsAString& aSheetSet) MOZ_OVERRIDE;
   virtual void GetLastStyleSheetSet(nsString& aSheetSet) MOZ_OVERRIDE;
   virtual nsIDOMDOMStringList* StyleSheetSets() MOZ_OVERRIDE;
   virtual void EnableStyleSheetsForSet(const nsAString& aSheetSet) MOZ_OVERRIDE;
+  using nsIDocument::CreateElement;
+  using nsIDocument::CreateElementNS;
+  virtual already_AddRefed<Element> CreateElement(const nsAString& aTagName,
+                                                  const nsAString& aTypeExtension,
+                                                  mozilla::ErrorResult& rv) MOZ_OVERRIDE;
+  virtual already_AddRefed<Element> CreateElementNS(const nsAString& aNamespaceURI,
+                                                    const nsAString& aQualifiedName,
+                                                    const nsAString& aTypeExtension,
+                                                    mozilla::ErrorResult& rv) MOZ_OVERRIDE;
 
 protected:
   friend class nsNodeUtils;
   friend class nsDocumentOnStack;
 
   void IncreaseStackRefCnt()
   {
     ++mStackRefCnt;
@@ -1170,19 +1352,69 @@ protected:
   // full-screen element onto this stack, and when we cancel full-screen we
   // 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;
 
-  // Hashtable for custom element prototypes in web components.
-  // Custom prototypes are in the document's compartment.
-  nsJSThingHashtable<nsStringHashKey, JSObject*> mCustomPrototypes;
+private:
+  struct Registry
+  {
+    NS_INLINE_DECL_REFCOUNTING(Registry)
+
+    typedef nsClassHashtable<mozilla::dom::CustomElementHashKey,
+                             mozilla::dom::CustomElementDefinition>
+      DefinitionMap;
+    typedef nsClassHashtable<mozilla::dom::CustomElementHashKey,
+                             nsTArray<nsRefPtr<mozilla::dom::Element>>>
+      CandidateMap;
+
+    // Hashtable for custom element definitions in web components.
+    // Custom prototypes are in the document's compartment.
+    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;
+
+    void Clear()
+    {
+      mCustomDefinitions.Clear();
+      mCandidatesMap.Clear();
+    }
+  };
+
+  // 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<mozilla::dom::CustomElementData*>> sProcessingStack;
+
+  // Flag to prevent re-entrance into base element queue as described in the
+  // custom elements speicification.
+  static bool sProcessingBaseElementQueue;
+
+  static bool CustomElementConstructor(JSContext* aCx, unsigned aArgc, JS::Value* aVp);
+
+public:
+  static void ProcessBaseElementQueue();
+
+  // Modify the prototype and "is" attribute of newly created custom elements.
+  virtual void SwizzleCustomElement(Element* aElement,
+                                    const nsAString& aTypeExtension,
+                                    uint32_t aNamespaceID,
+                                    mozilla::ErrorResult& rv);
+
+  // The "registry" from the web components spec.
+  nsRefPtr<Registry> mRegistry;
 
   nsRefPtr<nsEventListenerManager> mListenerManager;
   nsCOMPtr<nsIDOMStyleSheetList> mDOMStyleSheets;
   nsRefPtr<nsDOMStyleSheetSetList> mStyleSheetSetList;
   nsRefPtr<nsScriptLoader> mScriptLoader;
   nsDocHeaderData* mHeaderData;
   /* mIdentifierMap works as follows for IDs:
    * 1) Attribute changes affect the table immediately (removing and adding
--- a/content/base/src/nsGenericDOMDataNode.cpp
+++ b/content/base/src/nsGenericDOMDataNode.cpp
@@ -708,16 +708,27 @@ nsGenericDOMDataNode::SetXBLInsertionPar
 {
   nsDataSlots *slots = DataSlots();
   if (aContent) {
     SetFlags(NODE_MAY_BE_IN_BINDING_MNGR);
   }
   slots->mXBLInsertionParent = aContent;
 }
 
+CustomElementData *
+nsGenericDOMDataNode::GetCustomElementData() const
+{
+  return nullptr;
+}
+
+void
+nsGenericDOMDataNode::SetCustomElementData(CustomElementData* aData)
+{
+}
+
 bool
 nsGenericDOMDataNode::IsNodeOfType(uint32_t aFlags) const
 {
   return !(aFlags & ~(eCONTENT | eDATA_NODE));
 }
 
 void
 nsGenericDOMDataNode::SaveSubtreeState()
--- a/content/base/src/nsGenericDOMDataNode.h
+++ b/content/base/src/nsGenericDOMDataNode.h
@@ -156,21 +156,24 @@ public:
 
   virtual nsIContent *GetBindingParent() const MOZ_OVERRIDE;
   virtual nsXBLBinding *GetXBLBinding() const MOZ_OVERRIDE;
   virtual void SetXBLBinding(nsXBLBinding* aBinding,
                              nsBindingManager* aOldBindingManager = nullptr) MOZ_OVERRIDE;
   virtual mozilla::dom::ShadowRoot *GetContainingShadow() const MOZ_OVERRIDE;
   virtual mozilla::dom::ShadowRoot *GetShadowRoot() const MOZ_OVERRIDE;
   virtual void SetShadowRoot(mozilla::dom::ShadowRoot* aShadowRoot) MOZ_OVERRIDE;
-  virtual nsIContent *GetXBLInsertionParent() const;
-  virtual void SetXBLInsertionParent(nsIContent* aContent);
+  virtual nsIContent *GetXBLInsertionParent() const MOZ_OVERRIDE;
+  virtual void SetXBLInsertionParent(nsIContent* aContent) MOZ_OVERRIDE;
   virtual bool IsNodeOfType(uint32_t aFlags) const MOZ_OVERRIDE;
   virtual bool IsLink(nsIURI** aURI) const MOZ_OVERRIDE;
 
+  virtual mozilla::dom::CustomElementData* GetCustomElementData() const MOZ_OVERRIDE;
+  virtual void SetCustomElementData(mozilla::dom::CustomElementData* aData) MOZ_OVERRIDE;
+
   virtual nsIAtom* DoGetID() const MOZ_OVERRIDE;
   virtual const nsAttrValue* DoGetClasses() const MOZ_OVERRIDE;
   NS_IMETHOD WalkContentStyleRules(nsRuleWalker* aRuleWalker) MOZ_OVERRIDE;
   NS_IMETHOD_(bool) IsAttributeMapped(const nsIAtom* aAttribute) const;
   virtual nsChangeHint GetAttributeChangeHint(const nsIAtom* aAttribute,
                                               int32_t aModType) const;
   virtual nsIAtom *GetClassAttributeName() const;
 
--- a/content/base/src/nsGkAtomList.h
+++ b/content/base/src/nsGkAtomList.h
@@ -470,16 +470,17 @@ GK_ATOM(inputmode, "inputmode")
 GK_ATOM(ins, "ins")
 GK_ATOM(insertafter, "insertafter")
 GK_ATOM(insertbefore, "insertbefore")
 GK_ATOM(instanceOf, "instanceOf")
 GK_ATOM(int32, "int32")
 GK_ATOM(int64, "int64")
 GK_ATOM(integer, "integer")
 GK_ATOM(intersection, "intersection")
+GK_ATOM(is, "is")
 GK_ATOM(iscontainer, "iscontainer")
 GK_ATOM(isempty, "isempty")
 GK_ATOM(ismap, "ismap")
 GK_ATOM(itemid, "itemid")
 GK_ATOM(itemprop, "itemprop")
 GK_ATOM(itemref, "itemref")
 GK_ATOM(itemscope, "itemscope")
 GK_ATOM(itemtype, "itemtype")
--- a/content/base/test/chrome/test_document_register.xul
+++ b/content/base/test/chrome/test_document_register.xul
@@ -19,17 +19,17 @@ https://bugzilla.mozilla.org/show_bug.cg
 
   <!-- test code goes here -->
   <script type="application/javascript"><![CDATA[
 
   /** Test for Bug 783129 **/
   SimpleTest.waitForExplicitFinish();
 
   function startTests() {
-    var c = $("fooframe").contentDocument.register("x-foo");
+    var c = $("fooframe").contentDocument.registerElement("x-foo");
     var elem = new c();
     is(elem.tagName, "X-FOO", "Constructor should create an x-foo element.");
 
     var anotherElem = $("fooframe").contentDocument.createElement("x-foo");
     is(anotherElem.tagName, "X-FOO", "createElement should create an x-foo element.");
     SimpleTest.finish();
   }
 
--- a/content/html/content/src/HTMLUnknownElement.cpp
+++ b/content/html/content/src/HTMLUnknownElement.cpp
@@ -11,28 +11,15 @@
 NS_IMPL_NS_NEW_HTML_ELEMENT(Unknown)
 
 namespace mozilla {
 namespace dom {
 
 JSObject*
 HTMLUnknownElement::WrapNode(JSContext *aCx, JS::Handle<JSObject*> aScope)
 {
-  JS::Rooted<JSObject*> obj(aCx,
-    HTMLUnknownElementBinding::Wrap(aCx, aScope, this));
-  if (obj && Substring(NodeName(), 0, 2).LowerCaseEqualsLiteral("x-")) {
-    // If we have a registered x-tag then we fix the prototype.
-    JSAutoCompartment ac(aCx, obj);
-    nsDocument* document = static_cast<nsDocument*>(OwnerDoc());
-    JS::Rooted<JSObject*> prototype(aCx);
-    document->GetCustomPrototype(LocalName(), &prototype);
-    if (prototype) {
-      NS_ENSURE_TRUE(JS_WrapObject(aCx, &prototype), nullptr);
-      NS_ENSURE_TRUE(JS_SetPrototype(aCx, obj, prototype), nullptr);
-    }
-  }
-  return obj;
+  return HTMLUnknownElementBinding::Wrap(aCx, aScope, this);
 }
 
 NS_IMPL_ELEMENT_CLONE(HTMLUnknownElement)
 
 } // namespace dom
 } // namespace mozilla
--- a/content/html/document/src/nsHTMLContentSink.cpp
+++ b/content/html/document/src/nsHTMLContentSink.cpp
@@ -252,19 +252,41 @@ NS_NewHTMLElement(Element** aResult, alr
   nsIParserService* parserService = nsContentUtils::GetParserService();
   if (!parserService)
     return NS_ERROR_OUT_OF_MEMORY;
 
   nsIAtom *name = nodeInfo->NameAtom();
 
   NS_ASSERTION(nodeInfo->NamespaceEquals(kNameSpaceID_XHTML), 
                "Trying to HTML elements that don't have the XHTML namespace");
-  
-  *aResult = CreateHTMLElement(parserService->
-                                 HTMLCaseSensitiveAtomTagToId(name),
+
+  // 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)) {
+    NS_IF_ADDREF(*aResult = NS_NewHTMLElement(nodeInfo.forget(), aFromParser));
+    if (!*aResult) {
+      return NS_ERROR_OUT_OF_MEMORY;
+    }
+
+    nsIDocument* doc = aNodeInfo.get()->GetDocument();
+
+    // Element may be unresolved at this point.
+    doc->RegisterUnresolvedElement(*aResult);
+
+    // Try to enqueue a created callback. The custom element data will be set
+    // and created callback will be enqueued if the custom element type
+    // has already been registered.
+    doc->EnqueueLifecycleCallback(nsIDocument::eCreated, *aResult);
+
+    return NS_OK;
+  }
+
+  *aResult = CreateHTMLElement(tag,
                                nodeInfo.forget(), aFromParser).get();
   return *aResult ? NS_OK : NS_ERROR_OUT_OF_MEMORY;
 }
 
 already_AddRefed<nsGenericHTMLElement>
 CreateHTMLElement(uint32_t aNodeType, already_AddRefed<nsINodeInfo> aNodeInfo,
                   FromParser aFromParser)
 {
--- a/content/html/document/src/nsHTMLDocument.h
+++ b/content/html/document/src/nsHTMLDocument.h
@@ -81,16 +81,18 @@ public:
     return mWriteLevel != uint32_t(0);
   }
 
   virtual NS_HIDDEN_(nsContentList*) GetForms();
  
   virtual NS_HIDDEN_(nsContentList*) GetFormControls();
  
   // nsIDOMDocument interface
+  using nsDocument::CreateElement;
+  using nsDocument::CreateElementNS;
   NS_FORWARD_NSIDOMDOCUMENT(nsDocument::)
 
   // And explicitly import the things from nsDocument that we just shadowed
   using nsDocument::GetImplementation;
   using nsDocument::GetTitle;
   using nsDocument::SetTitle;
   using nsDocument::GetLastStyleSheetSet;
   using nsDocument::MozSetImageElement;
--- a/content/svg/content/src/SVGElementFactory.cpp
+++ b/content/svg/content/src/SVGElementFactory.cpp
@@ -98,16 +98,24 @@ void
 SVGElementFactory::Shutdown()
 {
   if (sTagAtomTable) {
     PL_HashTableDestroy(sTagAtomTable);
     sTagAtomTable = nullptr;
   }
 }
 
+bool
+SVGElementFactory::Exists(nsIAtom *aTag)
+{
+  MOZ_ASSERT(sTagAtomTable, "no lookup table, needs SVGElementFactory::Init");
+  void* tag = PL_HashTableLookupConst(sTagAtomTable, aTag);
+  return tag != nullptr;
+}
+
 nsresult
 NS_NewSVGElement(Element** aResult, already_AddRefed<nsINodeInfo> aNodeInfo,
                  FromParser aFromParser)
 {
   NS_ASSERTION(sTagAtomTable, "no lookup table, needs SVGElementFactory::Init");
 
   nsIAtom* name = aNodeInfo.get()->NameAtom();
 
--- a/content/svg/content/src/SVGElementFactory.h
+++ b/content/svg/content/src/SVGElementFactory.h
@@ -1,21 +1,25 @@
 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* 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/. */
 
 #ifndef mozilla_dom_SVGElementFactory_h
 #define mozilla_dom_SVGElementFactory_h
 
+class nsIAtom;
+
 namespace mozilla {
 namespace dom {
 
 class SVGElementFactory {
 public:
   static void Init();
   static void Shutdown();
+
+  static bool Exists(nsIAtom *aTag);
 };
 
 } // namespace dom
 } // namespace mozilla
 
 #endif /* mozilla_dom_SVGElementFactory_h */
--- a/content/xul/document/src/XULDocument.h
+++ b/content/xul/document/src/XULDocument.h
@@ -139,16 +139,18 @@ public:
 
     // nsINode interface overrides
     virtual nsresult Clone(nsINodeInfo *aNodeInfo, nsINode **aResult) const MOZ_OVERRIDE;
 
     // nsIDOMNode interface
     NS_FORWARD_NSIDOMNODE_TO_NSINODE
 
     // nsIDOMDocument interface
+    using nsDocument::CreateElement;
+    using nsDocument::CreateElementNS;
     NS_FORWARD_NSIDOMDOCUMENT(XMLDocument::)
     // And explicitly import the things from nsDocument that we just shadowed
     using nsDocument::GetImplementation;
     using nsDocument::GetTitle;
     using nsDocument::SetTitle;
     using nsDocument::GetLastStyleSheetSet;
     using nsDocument::MozSetImageElement;
     using nsDocument::GetMozFullScreenElement;
--- a/dom/tests/mochitest/webcomponents/mochitest.ini
+++ b/dom/tests/mochitest/webcomponents/mochitest.ini
@@ -1,16 +1,20 @@
 [DEFAULT]
+support-files =
+  inert_style.css
 
 [test_bug900724.html]
 [test_content_element.html]
 [test_nested_content_element.html]
 [test_dyanmic_content_element_matching.html]
 [test_document_register.html]
+[test_document_register_base_queue.html]
 [test_document_register_lifecycle.html]
+[test_document_register_parser.html]
+[test_document_register_stack.html]
 [test_template.html]
 [test_shadow_root.html]
 [test_shadow_root_inert_element.html]
-[inert_style.css]
 [test_shadow_root_style.html]
 [test_shadow_root_style_multiple_shadow.html]
 [test_shadow_root_style_order.html]
 [test_style_fallback_content.html]
--- a/dom/tests/mochitest/webcomponents/test_document_register.html
+++ b/dom/tests/mochitest/webcomponents/test_document_register.html
@@ -1,90 +1,171 @@
 <!DOCTYPE HTML>
 <html>
 <!--
 https://bugzilla.mozilla.org/show_bug.cgi?id=783129
 -->
 <head>
-  <title>Test for document.register using custom prototype</title>
+  <title>Test for document.registerElement using custom prototype</title>
   <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
-<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+  <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=783129">Bug 783129</a>
+<div>
+<x-unresolved id="unresolved"></x-unresolved>
+</div>
 
 <script>
-var gElementUpgraded = false;
-var gElementReplaced = false;
 
-function elementUpgrade() {
-  gElementUpgraded = true;
-
-  // Check for prototype on upgraded element.
-  var documentElement = document.getElementById("grabme");
-  ok(documentElement.hello, "Upgraded element should inherit 'hello' method from prototype.");
-  documentElement.hello();
-
-  var customChild = document.getElementById("kid");
-  ok(customChild, "Upgrade should preserve children.");
-  ok(customChild.parentNode == documentElement, "Parent should be updated to new custom element.");
-
-  // Try creating new element and checking for prototype.
-  var constructedElement = document.createElement("x-hello");
-  ok(constructedElement.hello, "Created element should inherit 'hello' method from prototype.");
-  constructedElement.hello();
+function testRegisterExtend(tag, extend, proto, expectException) {
+  try {
+    document.registerElement(tag, { prototype: proto, extends: extend });
+    ok(!expectException, "Registered " + tag + " extending " + extend + " containing " + proto + " in proto chain.");
+  } catch (ex) {
+    ok(expectException, "Did not register " + tag + " extending " + extend + " containing " + proto + " in proto chain.");
+  }
 }
 
-function elementReplace(e) {
-  gElementReplaced = true;
-
-  ok(e.upgrade != e.target, "Upgraded element should be different from the target.");
-  ok(e.upgrade.firstElementChild.id == "kid", "Upgrade element should have a child.");
-  ok(e.target.firstElementChild.id == "kid", "Replacement element should have a child.");
+function testRegisterSimple(tag, proto, expectException) {
+  try {
+    document.registerElement(tag, { prototype: proto });
+    ok(!expectException, "Registered " + tag + " containing " + proto + " in proto chain.");
+  } catch (ex) {
+    ok(expectException, "Did not register " + tag + " containing " + proto + " in proto chain.");
+  }
 }
 
 function startTest() {
-  // Create a prototype that inheits from HTMLElement.
-  var customProto = {};
-  customProto.__proto__ = HTMLElement.prototype;
-  customProto.hello = function() {
-    ok(true, "Custom element should use provided prototype.");
-  };
+  // Test registering some simple prototypes.
+  testRegisterSimple("x-html-obj-elem", Object.create(HTMLElement.prototype), false);
+  testRegisterSimple("x-html-obj-p", Object.create(HTMLParagraphElement.prototype), false);
+
+  // If prototype is an interface prototype object for any interface object,
+  // registration will throw.
+  testRegisterSimple("x-html-elem", HTMLElement.prototype, true);
+  testRegisterSimple("x-html-select", HTMLSelectElement.prototype, true);
+  testRegisterSimple("some-elem", HTMLElement.prototype, true);
+  testRegisterSimple("x-html-p", HTMLParagraphElement.prototype, true);
+  testRegisterSimple("x-html-span", HTMLSpanElement.prototype, true);
+  testRegisterSimple("x-svg-proto", SVGElement.prototype, true);
+
+  // Make sure the prototype on unresolved elements is HTMLElement not HTMLUnknownElement.
+  var unresolved = document.getElementById("unresolved");
+  is(unresolved.__proto__, HTMLElement.prototype, "Unresolved custom elements should have HTMLElement as prototype.");
+
+  var anotherUnresolved = document.createElement("maybe-custom-element");
+  is(anotherUnresolved.__proto__, HTMLElement.prototype, "Unresolved custom elements should have HTMLElement as prototype.");
+
+  // Registering without a prototype should automatically create one inheriting from HTMLElement.
+  testRegisterSimple("x-elem-no-proto", null, false);
+  var simpleElem = document.createElement("x-elem-no-proto");
+  is(simpleElem.__proto__.__proto__, HTMLElement.prototype, "Default prototype should inherit from HTMLElement");
 
-  var oldElem = document.getElementById("grabme");
-  oldElem.addEventListener("elementreplace", elementReplace);
+  var simpleProto = Object.create(HTMLElement.prototype);
+  testRegisterSimple("x-elem-simple-proto", simpleProto, false);
+  var simpleProtoElem = document.createElement("x-elem-simple-proto");
+  is(simpleProtoElem.__proto__, simpleProto, "Custom element should use registered prototype.");
+  var anotherSimpleElem = document.createElementNS("http://www.w3.org/1999/xhtml", "x-elem-simple-proto");
+  is(anotherSimpleElem.__proto__, simpleProto, "Custom element should use registered prototype.");
+
+  // Test registering some invalid prototypes.
+  testRegisterSimple("x-invalid-number", 42, true);
+  testRegisterSimple("x-invalid-boolean", false, true);
+  testRegisterSimple("x-invalid-float", 1.0, true);
+  // Can not register with a prototype that inherits from SVGElement
+  // without extending an existing element type.
+  testRegisterSimple("x-html-obj-svg", Object.create(SVGElement.prototype), true);
+  // A prototype with a non-configurable "constructor" property must throw.
+  var nonConfigProto = Object.create(HTMLElement.prototype,
+    { constructor: { configurable: false, value: function() {} } });
+  testRegisterSimple("x-non-config-proto", nonConfigProto, true);
 
-  document.addEventListener("elementupgrade", elementUpgrade);
-  var elementConstructor = document.register("x-hello", { prototype: customProto });
+  // Test invalid custom element names.
+  testRegisterSimple("invalid", Object.create(HTMLElement.prototype), true);
+  testRegisterSimple("annotation-xml", Object.create(HTMLElement.prototype), true);
+  testRegisterSimple("color-profile", Object.create(HTMLElement.prototype), true);
+  testRegisterSimple("font-face", Object.create(HTMLElement.prototype), true);
+  testRegisterSimple("font-face-src", Object.create(HTMLElement.prototype), true);
+  testRegisterSimple("font-face-uri", Object.create(HTMLElement.prototype), true);
+  testRegisterSimple("font-face-format", Object.create(HTMLElement.prototype), true);
+  testRegisterSimple("font-face-name", Object.create(HTMLElement.prototype), true);
+  testRegisterSimple("missing-glyph", Object.create(HTMLElement.prototype), true);
 
-  // Try creating new element and checking for prototype.
-  var constructedElement = new elementConstructor();
-  ok(constructedElement.hello, "Created element should inherit 'hello' method from prototype.");
-  constructedElement.hello();
+  // Test registering elements that extend from an existing element.
+  testRegisterExtend("x-extend-span", "span", Object.create(HTMLElement.prototype), false);
+  testRegisterExtend("x-extend-span-caps", "SPAN", Object.create(HTMLElement.prototype), false);
+
+  // Test registering elements that extend from a non-existing element.
+  testRegisterExtend("x-extend-span-nonexist", "nonexisting", Object.create(HTMLElement.prototype), true);
+  testRegisterExtend("x-extend-svg-nonexist", "nonexisting", Object.create(SVGElement.prototype), true);
 
-  ok(!oldElem.hello, "Element obtained prior to registration should not have inherited prototype.");
+  // Test registration with duplicate type.
+  testRegisterSimple("x-dupe-me", Object.create(HTMLElement.prototype), false);
+  testRegisterSimple("x-dupe-me", Object.create(HTMLElement.prototype), true);
+  testRegisterSimple("X-DUPE-ME", Object.create(HTMLElement.prototype), true);
+  testRegisterSimple("x-dupe-me", null, true);
+  testRegisterExtend("x-dupe-me", "span", Object.create(HTMLElement.prototype), true);
+  testRegisterExtend("x-dupe-me", "shape", Object.create(SVGElement.prototype), true);
 
-  ok(gElementUpgraded && gElementReplaced, "Upgrade and replace events should have been fired.");
+  testRegisterExtend("x-svg-dupe-me", "circle", Object.create(SVGElement.prototype), false);
+  testRegisterSimple("x-svg-dupe-me", Object.create(HTMLElement.prototype), true);
+  testRegisterSimple("X-SVG-DUPE-ME", Object.create(HTMLElement.prototype), true);
+  testRegisterSimple("x-svg-dupe-me", null, true);
+  testRegisterExtend("x-svg-dupe-me", "span", Object.create(HTMLElement.prototype), true);
+  testRegisterExtend("x-svg-dupe-me", "shape", Object.create(SVGElement.prototype), true);
 
-  // Check that custom elements registered without a prototype inherit from HTMLElement.
-  document.register("x-bye");
-  var byeElement = document.createElement("x-bye");
-  ok(byeElement.__proto__.__proto__, HTMLElement.prototype, "Element registered without prototype should inherit from HTMLElement.");
+  // document.createElement with extended type.
+  var extendedProto = Object.create(HTMLButtonElement.prototype);
+  var buttonConstructor = document.registerElement("x-extended-button", { prototype: extendedProto, extends: "button" });
+  var extendedButton = document.createElement("button", "x-extended-button");
+  is(extendedButton.tagName, "BUTTON", "Created element should have local name of BUTTON");
+  is(extendedButton.__proto__, extendedProto, "Created element should have the prototype of the extended type.");
+  is(extendedButton.getAttribute("is"), "x-extended-button", "The |is| attribute of the created element should be the extended type.");
+
+  // document.createElementNS with different namespace than definition.
+  var svgButton = document.createElementNS("http://www.w3.org/2000/svg", "button", "x-extended-button");
+  isnot(svgButton.__proto__, extendedProto, "Definition for element is in html namespace, registration should not apply for SVG elements.");
+
+  // document.createElement with non-existant extended type.
+  var normalButton = document.createElement("button", "x-non-existant");
+  is(normalButton.__proto__, HTMLButtonElement.prototype, "When the extended type doesn't exist, prototype should not change.");
 
-  // Check that element registration with a prototype that does not inherit from HTMLElement results in exception.
-  try {
-    document.register("x-foo", { "prototype": {} });
-    ok(false, "Prototype that does not inherit from HTMLElement should throw exception");
-  } catch (ex) {
-    ok(true, "Prototype that does not inherit from HTMLElement should throw exception");
-  }
+  // document.createElement with exteneded type that does not match with local name of element.
+  var normalDiv = document.createElement("div", "x-extended-button");
+  is(normalDiv.__proto__, HTMLDivElement.prototype, "Prototype should not change when local name of extended type defintion does not match.");
+
+  // Custom element constructor.
+  var constructedButton = new buttonConstructor();
+  is(constructedButton.tagName, "BUTTON", "Created element should have local name of BUTTON");
+  is(constructedButton.__proto__, extendedProto, "Created element should have the prototype of the extended type.");
+  is(constructedButton.getAttribute("is"), "x-extended-button", "The |is| attribute of the created element should be the extended type.");
 
-  SimpleTest.finish();
+  // document.createElementNS with extended type.
+  var svgExtendedProto = Object.create(SVGTextElement.prototype);
+  var svgConstructor = document.registerElement("x-extended-text", { prototype: svgExtendedProto, extends: "text"});
+  var extendedText = document.createElementNS("http://www.w3.org/2000/svg", "text", "x-extended-text");
+  is(extendedText.tagName, "text", "Created element should have a local name of |text|.");
+  is(extendedText.__proto__, svgExtendedProto, "Created element have the registered prototype.");
+  is(extendedText.getAttribute("is"), "x-extended-text", "The |is| attribute of the created element should be the extended type.");
+
+  // document.createElement with different namespace than definition for extended element.
+  var htmlText = document.createElement("text", "x-extended-text");
+  isnot(htmlText.__proto__, svgExtendedProto, "Definition for element in SVG namespace should not apply to HTML elements.");
+
+  // Custom element constructor for a SVG element.
+  var constructedText = new svgConstructor();
+  is(constructedText.tagName, "text", "Created element should have a local name of |text|.");
+  is(constructedText.__proto__, svgExtendedProto, "Created element have the registered prototype.");
+  is(constructedText.getAttribute("is"), "x-extended-text", "The |is| attribute of the created element should be the extended type.");
+
+  // Try creating an element with a custom element name, but not in the html namespace.
+  var htmlNamespaceProto = Object.create(HTMLElement.prototype);
+  document.registerElement("x-in-html-namespace", { prototype: htmlNamespaceProto });
+  var wrongNamespaceElem = document.createElementNS("http://www.w3.org/2000/svg", "x-in-html-namespace");
+  isnot(wrongNamespaceElem.__proto__, htmlNamespaceProto, "Definition for element in html namespace should not apply to SVG elements.");
 }
 
-SimpleTest.waitForExplicitFinish();
-</script>
+startTest();
 
-</head>
-<body onload="startTest()">
-<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=783129">Bug 783129</a>
-<x-hello id="grabme">
-<div id="kid"></div>
-</x-hello>
+</script>
 </body>
 </html>
new file mode 100644
--- /dev/null
+++ b/dom/tests/mochitest/webcomponents/test_document_register_base_queue.html
@@ -0,0 +1,48 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=783129
+-->
+<head>
+  <title>Test for document.registerElement lifecycle callback</title>
+  <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+<script>
+var p = Object.create(HTMLElement.prototype);
+
+var createdCallbackCallCount = 0;
+
+// By the time the base element queue is processed via the microtask,
+// both x-hello elements should be in the document.
+p.createdCallback = function() {
+  var one = document.getElementById("one");
+  var two = document.getElementById("two");
+  isnot(one, null, "First x-hello element should be in the tree.");
+  isnot(two, null, "Second x-hello element should be in the tree.");
+  createdCallbackCallCount++;
+  if (createdCallbackCallCount == 2) {
+    SimpleTest.finish();
+  }
+
+  if (createdCallbackCallCount > 2) {
+    ok(false, "Created callback called too much.");
+  }
+};
+
+p.attributeChangedCallback = function(name, oldValue, newValue) {
+  ok(false, "Attribute changed callback should not be called because callbacks should not be queued until created callback invoked.");
+};
+
+document.registerElement("x-hello", { prototype: p });
+
+SimpleTest.waitForExplicitFinish();
+</script>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=783129">Bug 783129</a>
+<x-hello id="one"></x-hello>
+<x-hello id="two"></x-hello>
+<script>
+</script>
+</body>
+</html>
--- a/dom/tests/mochitest/webcomponents/test_document_register_lifecycle.html
+++ b/dom/tests/mochitest/webcomponents/test_document_register_lifecycle.html
@@ -1,49 +1,402 @@
 <!DOCTYPE HTML>
 <html>
 <!--
 https://bugzilla.mozilla.org/show_bug.cgi?id=783129
 -->
 <head>
-  <title>Test for document.register lifecycle callback</title>
+  <title>Test for document.registerElement lifecycle callback</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=783129">Bug 783129</a>
+<div id="container">
+  <x-hello id="hello"></x-hello>
+  <button id="extbutton" is="x-button"></button>
+</div>
 <script>
 
-var gLifecycleCallbackCalled = false;
+var container = document.getElementById("container");
+
+// Tests callbacks after registering element type that is already in the document.
+// create element in document -> register -> remove from document
+function testRegisterUnresolved() {
+  var helloElem = document.getElementById("hello");
+
+  var createdCallbackCalled = false;
+  var enteredViewCallbackCalled = false;
+  var leftViewCallbackCalled = false;
+
+  var p = Object.create(HTMLElement.prototype);
+  p.createdCallback = function() {
+    is(helloElem.__proto__, p, "Prototype should be adjusted just prior to invoking the created callback.");
+    is(createdCallbackCalled, false, "Created callback should only be called once in this tests.");
+    is(this, helloElem, "The 'this' value should be the custom element.");
+    createdCallbackCalled = true;
+  };
+
+  p.enteredViewCallback = function() {
+    is(createdCallbackCalled, true, "Created callback should be called before enteredView");
+    is(enteredViewCallbackCalled, false, "enteredView callback should only be called once in this test.");
+    is(this, helloElem, "The 'this' value should be the custom element.");
+    enteredViewCallbackCalled = true;
+  };
+
+  p.leftViewCallback = function() {
+    is(enteredViewCallbackCalled, true, "enteredView callback should be called before leftView");
+    is(leftViewCallbackCalled, false, "leftView callback should only be called once in this test.");
+    leftViewCallbackCalled = true;
+    is(this, helloElem, "The 'this' value should be the custom element.");
+    runNextTest();
+  };
+
+  p.attributeChangedCallback = function(name, oldValue, newValue) {
+    ok(false, "attributeChanged callback should never be called in this test.");
+  };
+
+  document.registerElement("x-hello", { prototype: p });
+  is(createdCallbackCalled, true, "created callback should be called when control returns to script from user agent code");
+
+  // Remove element from document to trigger leftView callback.
+  container.removeChild(helloElem);
+}
 
-var lifecycleCallbacks = {
-  created: function() {
-    is(this.getAttribute("id"), "grabme", "|this| value should be the upgrade element");
-    gLifecycleCallbackCalled = true;
-  }
-};
+// Tests callbacks after registering an extended element type that is already in the document.
+// create element in document -> register -> remove from document
+function testRegisterUnresolvedExtended() {
+  var buttonElem = document.getElementById("extbutton");
+
+  var createdCallbackCalled = false;
+  var enteredViewCallbackCalled = false;
+  var leftViewCallbackCalled = false;
+
+  var p = Object.create(HTMLButtonElement.prototype);
+  p.createdCallback = function() {
+    is(buttonElem.__proto__, p, "Prototype should be adjusted just prior to invoking the created callback.");
+    is(createdCallbackCalled, false, "Created callback should only be called once in this tests.");
+    is(this, buttonElem, "The 'this' value should be the custom element.");
+    createdCallbackCalled = true;
+  };
+
+  p.enteredViewCallback = function() {
+    is(createdCallbackCalled, true, "Created callback should be called before enteredView");
+    is(enteredViewCallbackCalled, false, "enteredView callback should only be called once in this test.");
+    is(this, buttonElem, "The 'this' value should be the custom element.");
+    enteredViewCallbackCalled = true;
+  };
+
+  p.leftViewCallback = function() {
+    is(enteredViewCallbackCalled, true, "enteredView callback should be called before leftView");
+    is(leftViewCallbackCalled, false, "leftView callback should only be called once in this test.");
+    leftViewCallbackCalled = true;
+    is(this, buttonElem, "The 'this' value should be the custom element.");
+    runNextTest();
+  };
+
+  p.attributeChangedCallback = function(name, oldValue, newValue) {
+    ok(false, "attributeChanged callback should never be called in this test.");
+  };
+
+  document.registerElement("x-button", { prototype: p, extends: "button" });
+  is(createdCallbackCalled, true, "created callback should be called when control returns to script from user agent code");
+
+  // Remove element from document to trigger leftView callback.
+  container.removeChild(buttonElem);
+}
+
+function testInnerHTML() {
+  var createdCallbackCalled = false;
 
-function startTest() {
-  var HtmlProto = function() {};
-  HtmlProto.prototype = HTMLElement.prototype;
+  var p = Object.create(HTMLElement.prototype);
+  p.createdCallback = function() {
+    is(createdCallbackCalled, false, "created callback should only be called once in this test.");
+    createdCallbackCalled = true;
+  };
+
+  document.registerElement("x-inner-html", { prototype: p });
+  var div = document.createElement(div);
+  div.innerHTML = '<x-inner-html></x-inner-html>';
+  is(createdCallbackCalled, true, "created callback should be called after setting innerHTML.");
+  runNextTest();
+}
+
+function testInnerHTMLExtended() {
+  var createdCallbackCalled = false;
+
+  var p = Object.create(HTMLButtonElement.prototype);
+  p.createdCallback = function() {
+    is(createdCallbackCalled, false, "created callback should only be called once in this test.");
+    createdCallbackCalled = true;
+  };
+
+  document.registerElement("x-inner-html-extended", { prototype: p, extends: "button" });
+  var div = document.createElement(div);
+  div.innerHTML = '<button is="x-inner-html-extended"></button>';
+  is(createdCallbackCalled, true, "created callback should be called after setting innerHTML.");
+  runNextTest();
+}
+
+function testInnerHTMLUpgrade() {
+  var createdCallbackCalled = false;
+
+  var div = document.createElement(div);
+  div.innerHTML = '<x-inner-html-upgrade></x-inner-html-upgrade>';
+
+  var p = Object.create(HTMLElement.prototype);
+  p.createdCallback = function() {
+    is(createdCallbackCalled, false, "created callback should only be called once in this test.");
+    createdCallbackCalled = true;
+  };
+
+  document.registerElement("x-inner-html-upgrade", { prototype: p });
+  is(createdCallbackCalled, true, "created callback should be called after registering.");
+  runNextTest();
+}
 
-  // Create a prototype that inheits from HTMLElement.
-  var customProto = new HtmlProto();
-  customProto.hello = function() {
-    ok(true, "Custom element should use provided prototype.");
+function testInnerHTMLExtendedUpgrade() {
+  var createdCallbackCalled = false;
+
+  var div = document.createElement(div);
+  div.innerHTML = '<button is="x-inner-html-extended-upgrade"></button>';
+
+  var p = Object.create(HTMLButtonElement.prototype);
+  p.createdCallback = function() {
+    is(createdCallbackCalled, false, "created callback should only be called once in this test.");
+    createdCallbackCalled = true;
+  };
+
+  document.registerElement("x-inner-html-extended-upgrade", { prototype: p, extends: "button" });
+  is(createdCallbackCalled, true, "created callback should be called after registering.");
+  runNextTest();
+}
+
+// Test callback when creating element after registering an element type.
+// register -> create element -> insert into document -> remove from document
+function testRegisterResolved() {
+  var createdCallbackCalled = false;
+  var enteredViewCallbackCalled = false;
+  var leftViewCallbackCalled = false;
+
+  var createdCallbackThis;
+
+  var p = Object.create(HTMLElement.prototype);
+  p.createdCallback = function() {
+    is(createdCallbackCalled, false, "Created callback should only be called once in this test.");
+    createdCallbackThis = this;
+    createdCallbackCalled = true;
+  };
+
+  p.enteredViewCallback = function() {
+    is(createdCallbackCalled, true, "created callback should be called before enteredView callback.");
+    is(enteredViewCallbackCalled, false, "enteredView callback should only be called on in this test.");
+    is(this, createdElement, "The 'this' value should be the custom element.");
+    enteredViewCallbackCalled = true;
+  };
+
+  p.leftViewCallback = function() {
+    is(enteredViewCallbackCalled, true, "enteredView callback should be called before leftView");
+    is(leftViewCallbackCalled, false, "leftView callback should only be called once in this test.");
+    is(this, createdElement, "The 'this' value should be the custom element.");
+    leftViewCallbackCalled = true;
+    runNextTest();
   };
 
-  var elementConstructor = document.register("x-hello", { prototype: customProto, lifecycle: lifecycleCallbacks });
+  p.attributeChangedCallback = function() {
+    ok(false, "attributeChanged callback should never be called in this test.");
+  };
+
+  document.registerElement("x-resolved", { prototype: p });
+  is(createdCallbackCalled, false, "Created callback should not be called when custom element instance has not been created.");
+
+  var createdElement = document.createElement("x-resolved");
+  is(createdCallbackThis, createdElement, "The 'this' value in the created callback should be the custom element.");
+  is(createdElement.__proto__, p, "Prototype of custom element should be the registered prototype.");
+
+  // Insert element into document to trigger enteredView callback.
+  container.appendChild(createdElement);
+
+  // Remove element from document to trigger leftView callback.
+  container.removeChild(createdElement);
+}
+
+// Callbacks should always be the same ones when registered.
+function testChangingCallback() {
+  var p = Object.create(HTMLElement.prototype);
+  var callbackCalled = false;
+  p.attributeChangedCallback = function(name, oldValue, newValue) {
+    is(callbackCalled, false, "Callback should only be called once in this test.");
+    callbackCalled = true;
+    runNextTest();
+  };
+
+  document.registerElement("x-test-callback", { prototype: p });
+
+  p.attributeChangedCallback = function(name, oldValue, newValue) {
+    ok(false, "Only callbacks at registration should be called.");
+  };
+
+  var elem = document.createElement("x-test-callback");
+  elem.setAttribute("foo", "bar");
+}
+
+function testAttributeChanged() {
+  var createdCallbackCalled = false;
+
+  var createdElement;
+  var createdCallbackThis;
+
+  var p = Object.create(HTMLElement.prototype);
+  p.createdCallback = function() {
+    is(createdCallbackCalled, false, "Created callback should only be called once in this test.");
+    createdCallbackThis = this;
+    createdCallbackCalled = true;
+  };
+
+  // Sequence of callback arguments that we expect from attribute changed callback
+  // after changing attributes values in a specific order.
+  var expectedCallbackArguments = [
+    // [oldValue, newValue]
+    [null, "newvalue"], // Setting the attribute value to "newvalue"
+    ["newvalue", "nextvalue"], // Changing the attribute value from "newvalue" to "nextvalue"
+    ["nextvalue", ""], // Changing the attribute value from "nextvalue" to empty string
+    ["", null], // Removing the attribute.
+  ];
+
+  p.attributeChangedCallback = function(name, oldValue, newValue) {
+    is(createdCallbackCalled, true, "created callback should be called before attribute changed.");
+    is(this, createdElement, "The 'this' value should be the custom element.");
+    ok(expectedCallbackArguments.length > 0, "Attribute changed callback should not be called more than expected.");
+
+    is(name, "changeme", "name arugment in attribute changed callback should be the name of the changed attribute.");
+
+    var expectedArgs = expectedCallbackArguments.shift();
+    is(oldValue, expectedArgs[0], "The old value argument should match the expected value.");
+    is(newValue, expectedArgs[1], "The new value argument should match the expected value.");
+
+    if (expectedCallbackArguments.length === 0) {
+      // Done with the attribute changed callback test.
+      runNextTest();
+    }
+  };
+
+  document.registerElement("x-attrchange", { prototype: p });
+
+  var createdElement = document.createElement("x-attrchange");
+  is(createdCallbackThis, createdElement, "The 'this' value in the created callback should be the custom element.");
+  createdElement.setAttribute("changeme", "newvalue");
+  createdElement.setAttribute("changeme", "nextvalue");
+  createdElement.setAttribute("changeme", "");
+  createdElement.removeAttribute("changeme");
+}
 
-  ok(gLifecycleCallbackCalled, "Lifecycle callback should be called.");
+function testAttributeChangedExtended() {
+  var p = Object.create(HTMLButtonElement.prototype);
+  var callbackCalled = false;
+  p.attributeChangedCallback = function(name, oldValue, newValue) {
+    is(callbackCalled, false, "Callback should only be called once in this test.");
+    callbackCalled = true;
+    runNextTest();
+  };
+
+  document.registerElement("x-extended-attribute-change", { prototype: p, extends: "button" });
+
+  var elem = document.createElement("button", "x-extended-attribute-change");
+  elem.setAttribute("foo", "bar");
+}
+
+// Creates a custom element that is an upgrade candidate (no registration)
+// and mutate the element in ways that would call callbacks for registered
+// elements.
+function testUpgradeCandidate() {
+  var createdElement = document.createElement("x-upgrade-candidate");
+  container.appendChild(createdElement);
+  createdElement.setAttribute("foo", "bar");
+  container.removeChild(createdElement);
+  ok(true, "Nothing bad should happen when trying to mutating upgrade candidates.");
+  runNextTest();
+}
+
+function testNotInDocEnterLeave() {
+  var p = Object.create(HTMLElement.prototype);
+
+  p.enteredView = function() {
+    ok(false, "enteredView should not be called when not entering the document.");
+  };
+
+  p.leftView = function() {
+    ok(false, "leaveView should not be called when not leaving the document.");
+  };
+
+  var createdElement = document.createElement("x-destined-for-fragment");
+
+  document.registerElement("x-destined-for-fragment", { prototype: p });
+
+  var fragment = new DocumentFragment();
+  fragment.appendChild(createdElement);
+  fragment.removeChild(createdElement);
+
+  var divNotInDoc = document.createElement("div");
+  divNotInDoc.appendChild(createdElement);
+  divNotInDoc.removeChild(createdElement);
 
-  SimpleTest.finish();
+  runNextTest();
+}
+
+function testEnterLeaveView() {
+  var enteredViewCallbackCalled = false;
+  var leftViewCallbackCalled = false;
+
+  var p = Object.create(HTMLElement.prototype);
+  p.enteredViewCallback = function() {
+    is(enteredViewCallbackCalled, false, "enteredView callback should only be called on in this test.");
+    enteredViewCallbackCalled = true;
+  };
+
+  p.leftViewCallback = function() {
+    is(enteredViewCallbackCalled, true, "enteredView callback should be called before leftView");
+    is(leftViewCallbackCalled, false, "leftView callback should only be called once in this test.");
+    leftViewCallbackCalled = true;
+    runNextTest();
+  };
+
+  var div = document.createElement("div");
+  document.registerElement("x-element-in-div", { prototype: p });
+  var customElement = document.createElement("x-element-in-div");
+  div.appendChild(customElement);
+  is(enteredViewCallbackCalled, false, "Appending a custom element to a node that is not in the document should not call the enteredView callback.");
+
+  container.appendChild(div);
+  container.removeChild(div);
+}
+
+var testFunctions = [
+  testRegisterUnresolved,
+  testRegisterUnresolvedExtended,
+  testInnerHTML,
+  testInnerHTMLExtended,
+  testInnerHTMLUpgrade,
+  testInnerHTMLExtendedUpgrade,
+  testRegisterResolved,
+  testAttributeChanged,
+  testAttributeChangedExtended,
+  testUpgradeCandidate,
+  testChangingCallback,
+  testNotInDocEnterLeave,
+  testEnterLeaveView,
+  SimpleTest.finish
+];
+
+function runNextTest() {
+  if (testFunctions.length > 0) {
+    var nextTestFunction = testFunctions.shift();
+    nextTestFunction();
+  }
 }
 
 SimpleTest.waitForExplicitFinish();
-</script>
 
-</head>
-<body onload="startTest()">
-<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=783129">Bug 783129</a>
-<x-hello id="grabme">
-<div id="kid"></div>
-</x-hello>
+runNextTest();
+
+</script>
 </body>
 </html>
new file mode 100644
--- /dev/null
+++ b/dom/tests/mochitest/webcomponents/test_document_register_parser.html
@@ -0,0 +1,47 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=783129
+-->
+<head>
+  <title>Test for document.registerElement for elements created by the parser</title>
+  <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+<script>
+
+var extendedButtonProto = Object.create(HTMLButtonElement.prototype);
+var buttonCallbackCalled = false;
+extendedButtonProto.createdCallback = function() {
+  is(buttonCallbackCalled, false, "created callback for x-button should only be called once.");
+  is(this.tagName, "BUTTON", "Only the <button> element should be upgraded.");
+  buttonCallbackCalled = true;
+};
+
+document.registerElement("x-button", { prototype: extendedButtonProto, extends: "button" });
+
+var divProto = Object.create(HTMLDivElement.prototype);
+var divCallbackCalled = false;
+divProto.createdCallback = function() {
+  is(divCallbackCalled, false, "created callback for x-div should only be called once.");
+  is(buttonCallbackCalled, true, "crated callback should be called for x-button before x-div.");
+  is(this.tagName, "X-DIV", "Only the <x-div> element should be upgraded.");
+  divCallbackCalled = true;
+  SimpleTest.finish();
+};
+
+document.registerElement("x-div", { prototype: divProto });
+
+SimpleTest.waitForExplicitFinish();
+</script>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=783129">Bug 783129</a>
+<button is="x-button"></button><!-- should be upgraded -->
+<x-button></x-button><!-- should not be upgraded -->
+<span is="x-button"></span><!-- should not be upgraded -->
+<div is="x-div"></div><!-- should not be upgraded -->
+<x-div></x-div><!-- should be upgraded -->
+<script>
+</script>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/dom/tests/mochitest/webcomponents/test_document_register_stack.html
@@ -0,0 +1,152 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=783129
+-->
+<head>
+  <title>Test for document.registerElement lifecycle callback</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=783129">Bug 783129</a>
+<div id="container">
+</div>
+<script>
+
+var container = document.getElementById("container");
+
+// Test changing attributes in the created callback on an element
+// created after registration.
+function testChangeAttributeInCreatedCallback() {
+  var createdCallbackCalled = false;
+  var attributeChangedCallbackCalled = false;
+
+  var p = Object.create(HTMLElement.prototype);
+  p.createdCallback = function() {
+    is(createdCallbackCalled, false, "Created callback should be called before enteredView callback.");
+    createdCallbackCalled = true;
+    is(attributeChangedCallbackCalled, false, "Attribute changed callback should not have been called prior to setting the attribute.");
+    this.setAttribute("foo", "bar");
+    is(attributeChangedCallbackCalled, false, "While element is being created, element should not be added to the current element callback queue.");
+  };
+
+  p.attributeChangedCallback = function(name, oldValue, newValue) {
+    is(createdCallbackCalled, true, "attributeChanged callback should be called after the created callback because it was enqueued during created callback.");
+    is(attributeChangedCallbackCalled, false, "attributeChanged callback should only be called once in this tests.");
+    is(newValue, "bar", "The new value should be 'bar'");
+    attributeChangedCallbackCalled = true;
+    runNextTest();
+  };
+
+  document.registerElement("x-one", { prototype: p });
+  document.createElement("x-one");
+}
+
+function testChangeAttributeInEnteredViewCallback() {
+  var p = Object.create(HTMLElement.prototype);
+  var attributeChangedCallbackCalled = false;
+  var enteredViewCallbackCalled = false;
+
+  p.enteredViewCallback = function() {
+    is(enteredViewCallbackCalled, false, "enteredView callback should be called only once in this test.");
+    enteredViewCallbackCalled = true;
+    is(attributeChangedCallbackCalled, false, "Attribute changed callback should not be called before changing attribute.");
+    this.setAttribute("foo", "bar");
+    is(attributeChangedCallbackCalled, true, "Transition from user-agent implementation to script should result in attribute changed callback being called.");
+    runNextTest();
+  };
+
+  p.attributeChangedCallback = function() {
+    is(enteredViewCallbackCalled, true, "enteredView callback should have been called prior to attribute changed callback.");
+    is(attributeChangedCallbackCalled, false, "attributeChanged callback should only be called once in this tests.");
+    attributeChangedCallbackCalled = true;
+  };
+
+  document.registerElement("x-two", { prototype: p });
+  var elem = document.createElement("x-two");
+
+  var container = document.getElementById("container");
+  container.appendChild(elem);
+}
+
+function testLeaveViewInEnteredViewCallback() {
+  var p = Object.create(HTMLElement.prototype);
+  var enteredViewCallbackCalled = false;
+  var leftViewCallbackCalled = false;
+  var container = document.getElementById("container");
+
+  p.enteredViewCallback = function() {
+    is(this.parentNode, container, "Parent node should the container in which the node was appended.");
+    is(enteredViewCallbackCalled, false, "enteredView callback should be called only once in this test.");
+    enteredViewCallbackCalled = true;
+    is(leftViewCallbackCalled, false, "leftView callback should not be called prior to removing element from document.");
+    container.removeChild(this);
+    is(leftViewCallbackCalled, true, "Transition from user-agent implementation to script should run left view callback.");
+    runNextTest();
+  };
+
+  p.leftViewCallback = function() {
+    is(leftViewCallbackCalled, false, "The left view callback should only be called once in this test.");
+    is(enteredViewCallbackCalled, true, "The entered view callback should be called prior to left view callback.");
+    leftViewCallbackCalled = true;
+  };
+
+  document.registerElement("x-three", { prototype: p });
+  var elem = document.createElement("x-three");
+
+  container.appendChild(elem);
+}
+
+function testStackedAttributeChangedCallback() {
+  var p = Object.create(HTMLElement.prototype);
+  var attributeChangedCallbackCount = 0;
+
+  var attributeSequence = ["foo", "bar", "baz"];
+
+  p.attributeChangedCallback = function(attrName, oldValue, newValue) {
+    if (newValue == "baz") {
+      return;
+    }
+
+    var nextAttribute = attributeSequence.shift();
+    ok(true, nextAttribute);
+    // Setting this attribute will call this function again, when
+    // control returns to the script, the last attribute in the sequence should
+    // be set on the element.
+    this.setAttribute("foo", nextAttribute);
+    is(this.getAttribute("foo"), "baz", "The last value in the sequence should be the value of the attribute.");
+
+    attributeChangedCallbackCount++;
+    if (attributeChangedCallbackCount == 3) {
+      runNextTest();
+    }
+  };
+
+  document.registerElement("x-four", { prototype: p });
+  var elem = document.createElement("x-four");
+  elem.setAttribute("foo", "changeme");
+}
+
+var testFunctions = [
+  testChangeAttributeInCreatedCallback,
+  testChangeAttributeInEnteredViewCallback,
+  testLeaveViewInEnteredViewCallback,
+  testStackedAttributeChangedCallback,
+  SimpleTest.finish
+];
+
+function runNextTest() {
+  if (testFunctions.length > 0) {
+    var nextTestFunction = testFunctions.shift();
+    nextTestFunction();
+  }
+}
+
+SimpleTest.waitForExplicitFinish();
+
+runNextTest();
+
+</script>
+</body>
+</html>
--- a/dom/webidl/Document.webidl
+++ b/dom/webidl/Document.webidl
@@ -235,17 +235,25 @@ partial interface Document {
 partial interface Document {
     readonly attribute Element? mozPointerLockElement;
     void mozExitPointerLock ();
 };
 
 //http://dvcs.w3.org/hg/webcomponents/raw-file/tip/spec/custom/index.html#dfn-document-register
 partial interface Document {
     [Throws, Pref="dom.webcomponents.enabled"]
-    object register(DOMString name, optional ElementRegistrationOptions options);
+    object registerElement(DOMString name, optional ElementRegistrationOptions options);
+};
+
+//http://dvcs.w3.org/hg/webcomponents/raw-file/tip/spec/custom/index.html#dfn-document-register
+partial interface Document {
+    [NewObject, Throws]
+    Element createElement(DOMString localName, DOMString typeExtension);
+    [NewObject, Throws]
+    Element createElementNS(DOMString? namespace, DOMString qualifiedName, DOMString typeExtension);
 };
 
 // http://dvcs.w3.org/hg/webperf/raw-file/tip/specs/PageVisibility/Overview.html#sec-document-interface
 partial interface Document {
   readonly attribute boolean hidden;
   readonly attribute boolean mozHidden;
   readonly attribute VisibilityState visibilityState;
   readonly attribute VisibilityState mozVisibilityState;
--- a/dom/webidl/DummyBinding.webidl
+++ b/dom/webidl/DummyBinding.webidl
@@ -3,12 +3,13 @@
  * 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/.
  */
 
 // Dummy bindings that we need to force generation of things that
 // aren't actually referenced anywhere in IDL yet but are used in C++.
 
 interface DummyInterface {
+  void lifecycleCallbacks(optional LifecycleCallbacks arg);
 };
 
 interface DummyInterfaceWorkers {
 };
--- a/dom/webidl/WebComponents.webidl
+++ b/dom/webidl/WebComponents.webidl
@@ -6,18 +6,23 @@
  * The origin of this IDL file is
  * http://dvcs.w3.org/hg/webcomponents/raw-file/tip/spec/custom/index.html
  *
  * Copyright © 2012 W3C® (MIT, ERCIM, Keio), All Rights Reserved. W3C
  * liability, trademark and document use rules apply.
  */
 
 callback LifecycleCreatedCallback = void();
+callback LifecycleEnteredViewCallback = void();
+callback LifecycleLeftViewCallback = void();
+callback LifecycleAttributeChangedCallback = void(DOMString attrName, DOMString? oldValue, DOMString? newValue);
 
 dictionary LifecycleCallbacks {
-  LifecycleCreatedCallback? created = null;
+  LifecycleCreatedCallback? createdCallback;
+  LifecycleEnteredViewCallback? enteredViewCallback;
+  LifecycleLeftViewCallback? leftViewCallback;
+  LifecycleAttributeChangedCallback? attributeChangedCallback;
 };
 
 dictionary ElementRegistrationOptions {
   object? prototype = null;
-  LifecycleCallbacks lifecycle;
+  DOMString? extends = null;
 };
-
--- a/parser/html/nsHtml5TreeOperation.cpp
+++ b/parser/html/nsHtml5TreeOperation.cpp
@@ -430,34 +430,50 @@ nsHtml5TreeOperation::Perform(nsHtml5Tre
 
       int32_t len = attributes->getLength();
       for (int32_t i = len; i > 0;) {
         --i;
         // prefix doesn't need regetting. it is always null or a static atom
         // local name is never null
         nsCOMPtr<nsIAtom> localName =
           Reget(attributes->getLocalNameNoBoundsCheck(i));
+        nsCOMPtr<nsIAtom> prefix = attributes->getPrefixNoBoundsCheck(i);
+        int32_t nsuri = attributes->getURINoBoundsCheck(i);
+
         if (ns == kNameSpaceID_XHTML &&
             nsHtml5Atoms::a == name &&
             nsHtml5Atoms::name == localName) {
           // This is an HTML5-incompliant Geckoism.
           // Remove when fixing bug 582361
           NS_ConvertUTF16toUTF8 cname(*(attributes->getValueNoBoundsCheck(i)));
           NS_ConvertUTF8toUTF16 uv(nsUnescape(cname.BeginWriting()));
-          newContent->SetAttr(attributes->getURINoBoundsCheck(i),
+          newContent->SetAttr(nsuri,
                               localName,
-                              attributes->getPrefixNoBoundsCheck(i),
+                              prefix,
                               uv,
                               false);
         } else {
-          newContent->SetAttr(attributes->getURINoBoundsCheck(i),
+          nsString& value = *(attributes->getValueNoBoundsCheck(i));
+
+          newContent->SetAttr(nsuri,
                               localName,
-                              attributes->getPrefixNoBoundsCheck(i),
-                              *(attributes->getValueNoBoundsCheck(i)),
+                              prefix,
+                              value,
                               false);
+
+          // Custom element prototype swizzling may be needed if there is an
+          // "is" attribute.
+          if (kNameSpaceID_None == nsuri && !prefix && nsGkAtoms::is == localName) {
+            ErrorResult errorResult;
+            newContent->OwnerDoc()->SwizzleCustomElement(newContent,
+                                                         value,
+                                                         newContent->GetNameSpaceID(),
+                                                         errorResult);
+
+          }
         }
       }
 
       return rv;
     }
     case eTreeOpSetFormElement: {
       nsIContent* node = *(mOne.node);
       nsIContent* parent = *(mTwo.node);