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 170127 174ec7df74aec1229fcb0df50ee08c42175ccd67
parent 170126 1ba35735fe928757f6a5ee61b835d13cb800643d
child 170128 02959a932fb7886d002f86cb29bc4ce57ac5c146
push id40140
push userwchen@mozilla.com
push dateMon, 24 Feb 2014 00:01:26 +0000
treeherdermozilla-inbound@174ec7df74ae [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmrbkap
bugs856140
milestone30.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
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);