Merge mozilla-central to b2g-inbound
authorCarsten "Tomcat" Book <cbook@mozilla.com>
Mon, 24 Feb 2014 13:26:33 +0100
changeset 170590 6f4d3623806e46129a0b93e20b986c1dab693bef
parent 170589 f8b03bda8c7b08388e5eef0e7d126bc8f0c71a21 (current diff)
parent 170524 d9c58306bfbc74c920aa5c995b86b5f8a398352b (diff)
child 170591 0d33e28e036e3bf5c40a44f7337853a68df9e97e
push id270
push userpvanderbeken@mozilla.com
push dateThu, 06 Mar 2014 09:24:21 +0000
milestone30.0a1
Merge mozilla-central to b2g-inbound
--- a/browser/base/content/browser-charsetmenu.inc
+++ b/browser/base/content/browser-charsetmenu.inc
@@ -1,21 +1,19 @@
 # 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/.
 
-#filter substitution
-
-#expand <menu id="__ID_PREFIX__charsetMenu"
+<menu id="charsetMenu"
     label="&charsetMenu.label;"
 #ifndef OMIT_ACCESSKEYS
     accesskey="&charsetMenu.accesskey;"
 #endif
-    oncommand="MultiplexHandler(event)"
+    oncommand="BrowserSetForcedCharacterSet(event.target.getAttribute('charset'));"
 #ifdef OMIT_ACCESSKEYS
-#expand    onpopupshowing="CharsetMenu.build(event.target, '__ID_PREFIX__', false);"
+    onpopupshowing="CharsetMenu.build(event.target, false);"
 #else
-#expand    onpopupshowing="CharsetMenu.build(event.target, '__ID_PREFIX__');"
+    onpopupshowing="CharsetMenu.build(event.target);"
 #endif
-    onpopupshown="UpdateMenus(event);">
+    onpopupshown="UpdateCurrentCharset(this);">
   <menupopup>
   </menupopup>
 </menu>
--- a/browser/base/content/browser.js
+++ b/browser/base/content/browser.js
@@ -5134,132 +5134,57 @@ function handleDroppedLink(event, url, n
       loadURI(data.url, null, data.postData, false);
   });
 
   // Keep the event from being handled by the dragDrop listeners
   // built-in to gecko if they happen to be above us.
   event.preventDefault();
 };
 
-function MultiplexHandler(event)
-{ try {
-    var node = event.target;
-    var name = node.getAttribute('name');
-
-    if (name == 'detectorGroup') {
-        BrowserCharsetReload();
-        SelectDetector(event, false);
-    } else if (name == 'charsetGroup') {
-        var charset = node.getAttribute('id');
-        charset = charset.substring(charset.indexOf('charset.') + 'charset.'.length);
-        BrowserSetForcedCharacterSet(charset);
-    } else if (name == 'charsetCustomize') {
-        //do nothing - please remove this else statement, once the charset prefs moves to the pref window
-    } else {
-        BrowserSetForcedCharacterSet(node.getAttribute('id'));
-    }
-    } catch(ex) { alert(ex); }
-}
-
-function SelectDetector(event, doReload)
-{
-    var uri =  event.target.getAttribute("id");
-    var prefvalue = uri.substring(uri.indexOf('chardet.') + 'chardet.'.length);
-    if ("off" == prefvalue) { // "off" is special value to turn off the detectors
-        prefvalue = "";
-    }
-
-    try {
-        var str =  Cc["@mozilla.org/supports-string;1"].
-                   createInstance(Ci.nsISupportsString);
-
-        str.data = prefvalue;
-        gPrefService.setComplexValue("intl.charset.detector", Ci.nsISupportsString, str);
-        if (doReload)
-          window.content.location.reload();
-    }
-    catch (ex) {
-        dump("Failed to set the intl.charset.detector preference.\n");
-    }
-}
-
 function BrowserSetForcedCharacterSet(aCharset)
 {
-  gBrowser.docShell.gatherCharsetMenuTelemetry();
-  gBrowser.docShell.charset = aCharset;
-  // Save the forced character-set
-  if (!PrivateBrowsingUtils.isWindowPrivate(window))
-    PlacesUtils.setCharsetForURI(getWebNavigation().currentURI, aCharset);
+  if (aCharset) {
+    gBrowser.docShell.gatherCharsetMenuTelemetry();
+    gBrowser.docShell.charset = aCharset;
+    // Save the forced character-set
+    if (!PrivateBrowsingUtils.isWindowPrivate(window))
+      PlacesUtils.setCharsetForURI(getWebNavigation().currentURI, aCharset);
+  }
   BrowserCharsetReload();
 }
 
 function BrowserCharsetReload()
 {
   BrowserReloadWithFlags(nsIWebNavigation.LOAD_FLAGS_CHARSET_CHANGE);
 }
 
-function charsetMenuGetElement(parent, id) {
-  return parent.getElementsByAttribute("id", id)[0];
+function charsetMenuGetElement(parent, charset) {
+  return parent.getElementsByAttribute("charset", charset)[0];
 }
 
 function UpdateCurrentCharset(target) {
     // extract the charset from DOM
     var wnd = document.commandDispatcher.focusedWindow;
     if ((window == wnd) || (wnd == null)) wnd = window.content;
 
     // Uncheck previous item
     if (gPrevCharset) {
-        var pref_item = charsetMenuGetElement(target, "charset." + gPrevCharset);
+        var pref_item = charsetMenuGetElement(target, gPrevCharset);
         if (pref_item)
           pref_item.setAttribute('checked', 'false');
     }
 
-    var menuitem = charsetMenuGetElement(target, "charset." + FoldCharset(wnd.document.characterSet));
+    var menuitem = charsetMenuGetElement(target, CharsetMenu.foldCharset(wnd.document.characterSet));
     if (menuitem) {
         menuitem.setAttribute('checked', 'true');
     }
 }
 
-function FoldCharset(charset) {
-  // For substantially similar encodings, treat two encodings as the same
-  // for the purpose of the check mark.
-  if (charset == "ISO-8859-8-I") {
-    return "windows-1255";
-  }
-
-  if (charset == "gb18030") {
-    return "gbk";
-  }
-
-  return charset;
-}
-
-function UpdateCharsetDetector(target) {
-  var prefvalue;
-
-  try {
-    prefvalue = gPrefService.getComplexValue("intl.charset.detector", Ci.nsIPrefLocalizedString).data;
-  }
-  catch (ex) {}
-
-  if (!prefvalue)
-    prefvalue = "off";
-
-  var menuitem = charsetMenuGetElement(target, "chardet." + prefvalue);
-  if (menuitem)
-    menuitem.setAttribute("checked", "true");
-}
-
-function UpdateMenus(event) {
-  UpdateCurrentCharset(event.target);
-  UpdateCharsetDetector(event.target);
-}
-
 function charsetLoadListener() {
-  var charset = FoldCharset(window.content.document.characterSet);
+  var charset = CharsetMenu.foldCharset(window.content.document.characterSet);
 
   if (charset.length > 0 && (charset != gLastBrowserCharset)) {
     gPrevCharset = gLastBrowserCharset;
     gLastBrowserCharset = charset;
   }
 }
 
 var gPageStyleMenu = {
--- a/browser/locales/en-US/chrome/browser/preferences/sync.dtd
+++ b/browser/locales/en-US/chrome/browser/preferences/sync.dtd
@@ -44,19 +44,30 @@
 
 <!-- Footer stuff -->
 <!ENTITY prefs.tosLink.label        "Terms of Service">
 <!ENTITY prefs.ppLink.label         "Privacy Policy">
 
 <!-- Firefox Accounts stuff -->
 <!ENTITY fxaPrivacyNotice.link.label "Privacy Notice">
 <!ENTITY determiningAcctStatus.label     "Determining your account status…">
+
+<!-- LOCALIZATION NOTE (signedInUnverified.beforename.label,
+signedInUnverified.aftername.label): these two string are used respectively
+before and after the account email address. Localizers can use one of them, or
+both, to better adapt this sentence to their language.
+-->
 <!ENTITY signedInUnverified.beforename.label "">
 <!ENTITY signedInUnverified.aftername.label "is not verified.">
 
+<!-- LOCALIZATION NOTE (signedInLoginFailure.beforename.label,
+signedInLoginFailure.aftername.label): these two string are used respectively
+before and after the account email address. Localizers can use one of them, or
+both, to better adapt this sentence to their language.
+-->
 <!ENTITY signedInLoginFailure.beforename.label "Please sign in to reconnect">
 <!ENTITY signedInLoginFailure.aftername.label "">
 
 <!ENTITY notSignedIn.label           "You are not signed in.">
 <!ENTITY signIn.label                "Sign in">
 <!ENTITY manage.label                "Manage">
 <!ENTITY disconnect.label            "Disconnect…">
 <!ENTITY verify.label                "Verify Email">
--- 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;
new file mode 100644
--- /dev/null
+++ b/dom/ipc/ColorPickerParent.cpp
@@ -0,0 +1,87 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * vim: set sw=4 ts=8 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/. */
+
+#include "ColorPickerParent.h"
+#include "nsComponentManagerUtils.h"
+#include "nsIDocument.h"
+#include "nsIDOMWindow.h"
+#include "mozilla/unused.h"
+#include "mozilla/dom/Element.h"
+#include "mozilla/dom/TabParent.h"
+
+using mozilla::unused;
+using namespace mozilla::dom;
+
+NS_IMPL_ISUPPORTS1(ColorPickerParent::ColorPickerShownCallback,
+                   nsIColorPickerShownCallback);
+
+NS_IMETHODIMP
+ColorPickerParent::ColorPickerShownCallback::Update(const nsAString& aColor)
+{
+  if (mColorPickerParent) {
+    unused << mColorPickerParent->SendUpdate(nsString(aColor));
+  }
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+ColorPickerParent::ColorPickerShownCallback::Done(const nsAString& aColor)
+{
+  if (mColorPickerParent) {
+    unused << mColorPickerParent->Send__delete__(mColorPickerParent,
+                                                 nsString(aColor));
+  }
+  return NS_OK;
+}
+
+void
+ColorPickerParent::ColorPickerShownCallback::Destroy()
+{
+  mColorPickerParent = nullptr;
+}
+
+bool
+ColorPickerParent::CreateColorPicker()
+{
+  mPicker = do_CreateInstance("@mozilla.org/colorpicker;1");
+  if (!mPicker) {
+    return false;
+  }
+
+  Element* ownerElement = static_cast<TabParent*>(Manager())->GetOwnerElement();
+  if (!ownerElement) {
+    return false;
+  }
+
+  nsCOMPtr<nsIDOMWindow> window = do_QueryInterface(ownerElement->OwnerDoc()->GetWindow());
+  if (!window) {
+    return false;
+  }
+
+  return NS_SUCCEEDED(mPicker->Init(window, mTitle, mInitialColor));
+}
+
+bool
+ColorPickerParent::RecvOpen()
+{
+  if (!CreateColorPicker()) {
+    unused << Send__delete__(this, mInitialColor);
+    return true;
+  }
+
+  mCallback = new ColorPickerShownCallback(this);
+
+  mPicker->Open(mCallback);
+  return true;
+};
+
+void
+ColorPickerParent::ActorDestroy(ActorDestroyReason aWhy)
+{
+  if (mCallback) {
+    mCallback->Destroy();
+  }
+}
new file mode 100644
--- /dev/null
+++ b/dom/ipc/ColorPickerParent.h
@@ -0,0 +1,60 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: set sw=4 ts=8 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/. */
+
+#ifndef mozilla_dom_ColorPickerParent_h
+#define mozilla_dom_ColorPickerParent_h
+
+#include "mozilla/dom/PColorPickerParent.h"
+#include "nsIColorPicker.h"
+
+namespace mozilla {
+namespace dom {
+
+class ColorPickerParent : public PColorPickerParent
+{
+ public:
+  ColorPickerParent(const nsString& aTitle,
+                    const nsString& aInitialColor)
+  : mTitle(aTitle)
+  , mInitialColor(aInitialColor)
+  {}
+
+  virtual ~ColorPickerParent() {}
+
+  virtual bool RecvOpen() MOZ_OVERRIDE;
+  virtual void ActorDestroy(ActorDestroyReason aWhy) MOZ_OVERRIDE;
+
+  class ColorPickerShownCallback MOZ_FINAL
+    : public nsIColorPickerShownCallback
+  {
+  public:
+    ColorPickerShownCallback(ColorPickerParent* aColorPickerParnet)
+      : mColorPickerParent(aColorPickerParnet)
+    {}
+
+    NS_DECL_ISUPPORTS
+    NS_DECL_NSICOLORPICKERSHOWNCALLBACK
+
+    void Destroy();
+
+  private:
+    ColorPickerParent* mColorPickerParent;
+  };
+
+ private:
+  bool CreateColorPicker();
+
+  nsRefPtr<ColorPickerShownCallback> mCallback;
+  nsCOMPtr<nsIColorPicker> mPicker;
+
+  nsString mTitle;
+  nsString mInitialColor;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_ColorPickerParent_h
\ No newline at end of file
--- a/dom/ipc/PBrowser.ipdl
+++ b/dom/ipc/PBrowser.ipdl
@@ -1,16 +1,17 @@
 /* -*- Mode: C++; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 8 -*- */
 /* vim: set sw=4 ts=8 et tw=80 ft=cpp : */
 
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 include protocol PBlob;
+include protocol PColorPicker;
 include protocol PContent;
 include protocol PContentDialog;
 include protocol PDocumentRenderer;
 include protocol PContentPermissionRequest;
 include protocol PFilePicker;
 include protocol PRenderFrame;
 include protocol POfflineCacheUpdate;
 include protocol PIndexedDB;
@@ -52,16 +53,17 @@ using mozilla::CSSToScreenScale from "Un
 
 namespace mozilla {
 namespace dom {
 
 intr protocol PBrowser
 {
     manager PContent;
 
+    manages PColorPicker;
     manages PContentDialog;
     manages PDocumentRenderer;
     manages PContentPermissionRequest;
     manages PFilePicker;
     manages PRenderFrame;
     manages POfflineCacheUpdate;
     manages PIndexedDB;
 
@@ -217,16 +219,22 @@ parent:
     /**
      * Show/hide a tooltip when the mouse hovers over an element in the content
      * document.
      */
     ShowTooltip(uint32_t x, uint32_t y, nsString tooltip);
     HideTooltip();
 
     /**
+     * Create an asynchronous color picker on the parent side,
+     * but don't open it yet.
+     */
+    PColorPicker(nsString title, nsString initialColor);
+
+    /**
      * Initiates an asynchronous request for permission for the
      * provided principal.
      *
      * @param aRequests
      *   The array of permissions to request.
      * @param aPrincipal
      *   The principal of the request.
      *
new file mode 100644
--- /dev/null
+++ b/dom/ipc/PColorPicker.ipdl
@@ -0,0 +1,27 @@
+/* -*- Mode: C++; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 8 -*- */
+/* vim: set sw=4 ts=8 et tw=80 ft=cpp : */
+
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+include protocol PBrowser;
+
+namespace mozilla {
+namespace dom {
+
+protocol PColorPicker
+{
+    manager PBrowser;
+
+parent:
+    Open();
+
+child:
+    Update(nsString color);
+
+    __delete__(nsString color);
+};
+
+} // namespace dom
+} // namespace mozilla
--- a/dom/ipc/TabChild.cpp
+++ b/dom/ipc/TabChild.cpp
@@ -70,16 +70,18 @@
 #include "StructuredCloneUtils.h"
 #include "nsViewportInfo.h"
 #include "JavaScriptChild.h"
 #include "APZCCallbackHelper.h"
 #include "nsILoadContext.h"
 #include "ipc/nsGUIEventIPC.h"
 #include "mozilla/gfx/Matrix.h"
 
+#include "nsColorPickerProxy.h"
+
 #ifdef DEBUG
 #include "PCOMContentPermissionRequestChild.h"
 #endif /* DEBUG */
 
 #define BROWSER_ELEMENT_CHILD_SCRIPT \
     NS_LITERAL_STRING("chrome://global/content/BrowserElementChild.js")
 
 using namespace mozilla;
@@ -2088,16 +2090,31 @@ TabChild::RecvPDocumentRendererConstruct
                                       renderFlags, flushLayout,
                                       renderSize, data);
     if (!ret)
         return true; // silently ignore
 
     return PDocumentRendererChild::Send__delete__(actor, renderSize, data);
 }
 
+PColorPickerChild*
+TabChild::AllocPColorPickerChild(const nsString&, const nsString&)
+{
+  NS_RUNTIMEABORT("unused");
+  return nullptr;
+}
+
+bool
+TabChild::DeallocPColorPickerChild(PColorPickerChild* aColorPicker)
+{
+  nsColorPickerProxy* picker = static_cast<nsColorPickerProxy*>(aColorPicker);
+  NS_RELEASE(picker);
+  return true;
+}
+
 PContentDialogChild*
 TabChild::AllocPContentDialogChild(const uint32_t&,
                                    const nsCString&,
                                    const nsCString&,
                                    const InfallibleTArray<int>&,
                                    const InfallibleTArray<nsString>&)
 {
   return new ContentDialogChild();
--- a/dom/ipc/TabChild.h
+++ b/dom/ipc/TabChild.h
@@ -273,23 +273,29 @@ public:
     virtual bool RecvPDocumentRendererConstructor(PDocumentRendererChild* actor,
                                                   const nsRect& documentRect,
                                                   const gfx::Matrix& transform,
                                                   const nsString& bgcolor,
                                                   const uint32_t& renderFlags,
                                                   const bool& flushLayout,
                                                   const nsIntSize& renderSize) MOZ_OVERRIDE;
 
+    virtual PColorPickerChild*
+    AllocPColorPickerChild(const nsString& title, const nsString& initialColor) MOZ_OVERRIDE;
+    virtual bool DeallocPColorPickerChild(PColorPickerChild* actor) MOZ_OVERRIDE;
+
+
     virtual PContentDialogChild* AllocPContentDialogChild(const uint32_t&,
                                                           const nsCString&,
                                                           const nsCString&,
                                                           const InfallibleTArray<int>&,
                                                           const InfallibleTArray<nsString>&)
                                                           MOZ_OVERRIDE;
     virtual bool DeallocPContentDialogChild(PContentDialogChild* aDialog) MOZ_OVERRIDE;
+
     static void ParamsToArrays(nsIDialogParamBlock* aParams,
                                InfallibleTArray<int>& aIntParams,
                                InfallibleTArray<nsString>& aStringParams);
     static void ArraysToParams(const InfallibleTArray<int>& aIntParams,
                                const InfallibleTArray<nsString>& aStringParams,
                                nsIDialogParamBlock* aParams);
 
 #ifdef DEBUG
--- a/dom/ipc/TabParent.cpp
+++ b/dom/ipc/TabParent.cpp
@@ -50,16 +50,17 @@
 #include "nsIWindowWatcher.h"
 #include "nsPIDOMWindow.h"
 #include "nsPrintfCString.h"
 #include "nsServiceManagerUtils.h"
 #include "nsThreadUtils.h"
 #include "private/pprio.h"
 #include "PermissionMessageUtils.h"
 #include "StructuredCloneUtils.h"
+#include "ColorPickerParent.h"
 #include "JavaScriptParent.h"
 #include "FilePickerParent.h"
 #include "TabChild.h"
 #include "LoadContext.h"
 #include "nsNetCID.h"
 #include <algorithm>
 
 using namespace mozilla::dom;
@@ -1620,16 +1621,30 @@ TabParent::GetAuthPrompt(uint32_t aPromp
     window = do_QueryInterface(frame->OwnerDoc()->GetWindow());
 
   // Get an auth prompter for our window so that the parenting
   // of the dialogs works as it should when using tabs.
   return wwatch->GetPrompt(window, iid,
                            reinterpret_cast<void**>(aResult));
 }
 
+PColorPickerParent*
+TabParent::AllocPColorPickerParent(const nsString& aTitle,
+                                   const nsString& aInitialColor)
+{
+  return new ColorPickerParent(aTitle, aInitialColor);
+}
+
+bool
+TabParent::DeallocPColorPickerParent(PColorPickerParent* actor)
+{
+  delete actor;
+  return true;
+}
+
 PContentDialogParent*
 TabParent::AllocPContentDialogParent(const uint32_t& aType,
                                      const nsCString& aName,
                                      const nsCString& aFeatures,
                                      const InfallibleTArray<int>& aIntParams,
                                      const InfallibleTArray<nsString>& aStringParams)
 {
   ContentDialogParent* parent = new ContentDialogParent();
--- a/dom/ipc/TabParent.h
+++ b/dom/ipc/TabParent.h
@@ -182,16 +182,21 @@ public:
                                 const ViewID& aViewId,
                                 const CSSRect& aRect) MOZ_OVERRIDE;
     virtual bool RecvUpdateZoomConstraints(const uint32_t& aPresShellId,
                                            const ViewID& aViewId,
                                            const bool& aIsRoot,
                                            const ZoomConstraints& aConstraints) MOZ_OVERRIDE;
     virtual bool RecvContentReceivedTouch(const ScrollableLayerGuid& aGuid,
                                           const bool& aPreventDefault) MOZ_OVERRIDE;
+
+    virtual PColorPickerParent*
+    AllocPColorPickerParent(const nsString& aTitle, const nsString& aInitialColor) MOZ_OVERRIDE;
+    virtual bool DeallocPColorPickerParent(PColorPickerParent* aColorPicker) MOZ_OVERRIDE;
+
     virtual PContentDialogParent*
     AllocPContentDialogParent(const uint32_t& aType,
                               const nsCString& aName,
                               const nsCString& aFeatures,
                               const InfallibleTArray<int>& aIntParams,
                               const InfallibleTArray<nsString>& aStringParams) MOZ_OVERRIDE;
     virtual bool DeallocPContentDialogParent(PContentDialogParent* aDialog) MOZ_OVERRIDE
     {
--- a/dom/ipc/moz.build
+++ b/dom/ipc/moz.build
@@ -35,16 +35,17 @@ EXPORTS.mozilla.dom += [
 EXPORTS.mozilla += [
     'AppProcessChecker.h',
     'PreallocatedProcessManager.h',
     'ProcessPriorityManager.h',
 ]
 
 UNIFIED_SOURCES += [
     'AppProcessChecker.cpp',
+    'ColorPickerParent.cpp',
     'ContentChild.cpp',
     'ContentParent.cpp',
     'ContentProcess.cpp',
     'CrashReporterParent.cpp',
     'FilePickerParent.cpp',
     'PermissionMessageUtils.cpp',
     'PreallocatedProcessManager.cpp',
     'ProcessPriorityManager.cpp',
@@ -63,16 +64,17 @@ SOURCES += [
     'CrashReporterChild.cpp',
 ]
 
 IPDL_SOURCES += [
     'DOMTypes.ipdlh',
     'PBlob.ipdl',
     'PBlobStream.ipdl',
     'PBrowser.ipdl',
+    'PColorPicker.ipdl',
     'PContent.ipdl',
     'PContentDialog.ipdl',
     'PContentPermission.ipdlh',
     'PContentPermissionRequest.ipdl',
     'PCrashReporter.ipdl',
     'PDocumentRenderer.ipdl',
     'PFilePicker.ipdl',
     'PMemoryReportRequest.ipdl',
--- 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/gfx/tests/gtest/TestAsyncPanZoomController.cpp
+++ b/gfx/tests/gtest/TestAsyncPanZoomController.cpp
@@ -708,26 +708,32 @@ TEST(AsyncPanZoomController, MediumPress
   mcc->CheckHasDelayedTask();
 
   EXPECT_CALL(*mcc, HandleSingleTap(CSSIntPoint(10, 10), 0, apzc->GetGuid())).Times(1);
   mcc->RunDelayedTask();
 
   apzc->Destroy();
 }
 
-TEST(AsyncPanZoomController, LongPress) {
+void
+DoLongPressTest(bool aShouldUseTouchAction, uint32_t aBehavior) {
   nsRefPtr<MockContentControllerDelayed> mcc = new MockContentControllerDelayed();
   nsRefPtr<TestAPZCTreeManager> tm = new TestAPZCTreeManager();
   nsRefPtr<TestAsyncPanZoomController> apzc = new TestAsyncPanZoomController(
     0, mcc, tm, AsyncPanZoomController::USE_GESTURE_DETECTOR);
 
   apzc->SetFrameMetrics(TestFrameMetrics());
   apzc->NotifyLayersUpdated(TestFrameMetrics(), true);
   apzc->UpdateZoomConstraints(ZoomConstraints(false, CSSToScreenScale(1.0), CSSToScreenScale(1.0)));
 
+  nsTArray<uint32_t> values;
+  values.AppendElement(aBehavior);
+  apzc->SetTouchActionEnabled(aShouldUseTouchAction);
+  apzc->SetAllowedTouchBehavior(values);
+
   int time = 0;
 
   nsEventStatus status = ApzcDown(apzc, 10, 10, time);
   EXPECT_EQ(nsEventStatus_eConsumeNoDefault, status);
 
   MockFunction<void(std::string checkPointName)> check;
 
   {
@@ -834,16 +840,27 @@ TEST(AsyncPanZoomController, LongPressPr
   apzc->SampleContentTransformForFrame(testStartTime, &viewTransformOut, pointOut);
 
   EXPECT_EQ(pointOut, ScreenPoint());
   EXPECT_EQ(viewTransformOut, ViewTransform());
 
   apzc->Destroy();
 }
 
+TEST(AsyncPanZoomController, LongPress) {
+  DoLongPressTest(false, mozilla::layers::AllowedTouchBehavior::NONE);
+}
+
+TEST(AsyncPanZoomController, LongPressPanAndZoom) {
+  DoLongPressTest(true, mozilla::layers::AllowedTouchBehavior::HORIZONTAL_PAN
+                      | mozilla::layers::AllowedTouchBehavior::VERTICAL_PAN
+                      | mozilla::layers::AllowedTouchBehavior::ZOOM);
+}
+
+
 // Layer tree for HitTesting1
 static already_AddRefed<mozilla::layers::Layer>
 CreateTestLayerTree1(nsRefPtr<LayerManager>& aLayerManager, nsTArray<nsRefPtr<Layer> >& aLayers) {
   const char* layerTreeSyntax = "c(ttcc)";
   // LayerID                     0 1234
   nsIntRegion layerVisibleRegion[] = {
     nsIntRegion(nsIntRect(0,0,100,100)),
     nsIntRegion(nsIntRect(0,0,100,100)),
--- a/mobile/android/base/tests/testBrowserProvider.java
+++ b/mobile/android/base/tests/testBrowserProvider.java
@@ -60,27 +60,33 @@ public class testBrowserProvider extends
                                         BrowserContract.Bookmarks.MOBILE_FOLDER_GUID,
                                         BrowserContract.Bookmarks.MENU_FOLDER_GUID,
                                         BrowserContract.Bookmarks.TAGS_FOLDER_GUID,
                                         BrowserContract.Bookmarks.TOOLBAR_FOLDER_GUID,
                                         BrowserContract.Bookmarks.UNFILED_FOLDER_GUID,
                                         BrowserContract.Bookmarks.READING_LIST_FOLDER_GUID });
 
         c = mProvider.query(appendUriParam(BrowserContract.Bookmarks.CONTENT_URI, BrowserContract.PARAM_SHOW_DELETED, "1"), null, null, null, null);
-        mAsserter.is(c.getCount(), 7, "All non-special bookmarks and folders were deleted");
+        assertCountIsAndClose(c, 7, "All non-special bookmarks and folders were deleted");
 
         mProvider.delete(appendUriParam(BrowserContract.History.CONTENT_URI, BrowserContract.PARAM_IS_SYNC, "1"), null, null);
         c = mProvider.query(appendUriParam(BrowserContract.History.CONTENT_URI, BrowserContract.PARAM_SHOW_DELETED, "1"), null, null, null, null);
-        mAsserter.is(c.getCount(), 0, "All history entries were deleted");
+        assertCountIsAndClose(c, 0, "All history entries were deleted");
 
-        mProvider.delete(BrowserContract.Favicons.CONTENT_URI, null, null);
-        mAsserter.is(c.getCount(), 0, "All favicons were deleted");
+        mProvider.delete(appendUriParam(BrowserContract.Favicons.CONTENT_URI, BrowserContract.PARAM_IS_SYNC, "1"), null, null);
+        c = mProvider.query(appendUriParam(BrowserContract.Favicons.CONTENT_URI,
+                                           BrowserContract.PARAM_SHOW_DELETED, "1"),
+                                           null, null, null, null);
+        assertCountIsAndClose(c, 0, "All favicons were deleted");
 
-        mProvider.delete(BrowserContract.Thumbnails.CONTENT_URI, null, null);
-        mAsserter.is(c.getCount(), 0, "All thumbnails were deleted");
+        mProvider.delete(appendUriParam(BrowserContract.Thumbnails.CONTENT_URI, BrowserContract.PARAM_IS_SYNC, "1"), null, null);
+        c = mProvider.query(appendUriParam(BrowserContract.Thumbnails.CONTENT_URI,
+                                           BrowserContract.PARAM_SHOW_DELETED, "1"),
+                                           null, null, null, null);
+        assertCountIsAndClose(c, 0, "All thumbnails were deleted");
     }
 
     private ContentValues createBookmark(String title, String url, long parentId,
             int type, int position, String tags, String description, String keyword) throws Exception {
         ContentValues bookmark = new ContentValues();
 
         bookmark.put(BrowserContract.Bookmarks.TITLE, title);
         bookmark.put(BrowserContract.Bookmarks.URL, url);
@@ -321,31 +327,31 @@ public class testBrowserProvider extends
             try {
                 applyResult = mProvider.applyBatch(mOperations);
             } catch (OperationApplicationException ex) {
                 seenException = true;
             }
             mAsserter.is(seenException, false, "Batch updating succeded");
             mOperations.clear();
 
-            // Delte all visits
+            // Delete all visits
             for (int i = 0; i < TESTCOUNT; i++) {
                 builder = ContentProviderOperation.newDelete(BrowserContract.History.CONTENT_URI);
                 builder.withSelection(BrowserContract.History.URL  + " = ?",
                                       new String[] {"http://www.test.org/" + i});
                 builder.withExpectedCount(1);
                 // Queue the operation
                 mOperations.add(builder.build());
             }
             try {
                 applyResult = mProvider.applyBatch(mOperations);
             } catch (OperationApplicationException ex) {
                 seenException = true;
             }
-            mAsserter.is(seenException, false, "Batch deletion succeded");
+            mAsserter.is(seenException, false, "Batch deletion succeeded");
         }
 
         // Force a Constraint error, see if later operations still apply correctly
         public void testApplyBatchErrors() throws Exception {
             ArrayList<ContentProviderOperation> mOperations
                 = new ArrayList<ContentProviderOperation>();
 
             // Test a bunch of inserts with applyBatch
@@ -470,16 +476,18 @@ public class testBrowserProvider extends
                     mAsserter.is(new Integer(id), new Integer(rootId), "The id of places folder is correct");
                 } else if (guid.equals(BrowserContract.Bookmarks.READING_LIST_FOLDER_GUID)) {
                     mAsserter.is(new Integer(id), new Integer(readingListId), "The id of reading list folder is correct");
                 }
 
                 mAsserter.is(new Integer(parentId), new Integer(rootId),
                              "The PARENT of the " + guid + " special folder is correct");
             }
+
+            c.close();
         }
     }
 
     class TestInsertBookmarks extends Test {
         private long insertWithNullCol(String colName) throws Exception {
             ContentValues b = createOneBookmark();
             b.putNull(colName);
             long id = -1;
@@ -544,16 +552,17 @@ public class testBrowserProvider extends
             b.remove(BrowserContract.Bookmarks.TYPE);
             id = ContentUris.parseId(mProvider.insert(BrowserContract.Bookmarks.CONTENT_URI, b));
             c = getBookmarkById(id);
 
             mAsserter.is(c.moveToFirst(), true, "Inserted bookmark found");
 
             mAsserter.is(c.getString(c.getColumnIndex(BrowserContract.Bookmarks.TYPE)), String.valueOf(BrowserContract.Bookmarks.TYPE_BOOKMARK),
                          "Inserted bookmark has correct default type");
+            c.close();
         }
     }
 
     class TestInsertBookmarksFavicons extends Test {
         @Override
         public void test() throws Exception {
             ContentValues b = createOneBookmark();
 
@@ -566,83 +575,91 @@ public class testBrowserProvider extends
             mProvider.insert(BrowserContract.Favicons.CONTENT_URI, createFaviconEntry(pageUrl, favicon));
 
             Cursor c = getBookmarkById(id, new String[] { BrowserContract.Bookmarks.FAVICON });
 
             mAsserter.is(c.moveToFirst(), true, "Inserted bookmark found");
 
             mAsserter.is(new String(c.getBlob(c.getColumnIndex(BrowserContract.Bookmarks.FAVICON)), "UTF8"),
                          favicon, "Inserted bookmark has corresponding favicon image");
+            c.close();
 
             c = getFaviconsByUrl(pageUrl);
             mAsserter.is(c.moveToFirst(), true, "Inserted favicon found");
 
             mAsserter.is(new String(c.getBlob(c.getColumnIndex(BrowserContract.Combined.FAVICON)), "UTF8"),
                          favicon, "Inserted favicon has corresponding favicon image");
+            c.close();
         }
     }
 
     class TestDeleteBookmarks extends Test {
         private long insertOneBookmark() throws Exception {
             ContentValues b = createOneBookmark();
             long id = ContentUris.parseId(mProvider.insert(BrowserContract.Bookmarks.CONTENT_URI, b));
 
             Cursor c = getBookmarkById(id);
             mAsserter.is(c.moveToFirst(), true, "Inserted bookmark found");
+            c.close();
 
             return id;
         }
 
         @Override
         public void test() throws Exception {
             long id = insertOneBookmark();
 
             int deleted = mProvider.delete(BrowserContract.Bookmarks.CONTENT_URI,
                                            BrowserContract.Bookmarks._ID + " = ?",
                                            new String[] { String.valueOf(id) });
 
             mAsserter.is((deleted == 1), true, "Inserted bookmark was deleted");
 
             Cursor c = getBookmarkById(appendUriParam(BrowserContract.Bookmarks.CONTENT_URI, BrowserContract.PARAM_SHOW_DELETED, "1"), id);
             mAsserter.is(c.moveToFirst(), true, "Deleted bookmark was only marked as deleted");
+            c.close();
 
             deleted = mProvider.delete(appendUriParam(BrowserContract.Bookmarks.CONTENT_URI, BrowserContract.PARAM_IS_SYNC, "1"),
                                        BrowserContract.Bookmarks._ID + " = ?",
                                        new String[] { String.valueOf(id) });
 
             mAsserter.is((deleted == 1), true, "Inserted bookmark was deleted");
 
             c = getBookmarkById(appendUriParam(BrowserContract.Bookmarks.CONTENT_URI, BrowserContract.PARAM_SHOW_DELETED, "1"), id);
             mAsserter.is(c.moveToFirst(), false, "Inserted bookmark is now actually deleted");
+            c.close();
 
             id = insertOneBookmark();
 
             deleted = mProvider.delete(ContentUris.withAppendedId(BrowserContract.Bookmarks.CONTENT_URI, id), null, null);
             mAsserter.is((deleted == 1), true,
                          "Inserted bookmark was deleted using URI with id");
 
             c = getBookmarkById(id);
             mAsserter.is(c.moveToFirst(), false,
                          "Inserted bookmark can't be found after deletion using URI with ID");
+            c.close();
 
             if (Build.VERSION.SDK_INT >= 8 &&
                 Build.VERSION.SDK_INT < 16) {
                 ContentValues b = createBookmark("Folder", null, mMobileFolderId,
                         BrowserContract.Bookmarks.TYPE_FOLDER, 0, "folderTags", "folderDescription", "folderKeyword");
 
                 long parentId = ContentUris.parseId(mProvider.insert(BrowserContract.Bookmarks.CONTENT_URI, b));
                 c = getBookmarkById(parentId);
                 mAsserter.is(c.moveToFirst(), true, "Inserted bookmarks folder found");
+                c.close();
 
                 b = createBookmark("Example", "http://example.com", parentId,
                         BrowserContract.Bookmarks.TYPE_BOOKMARK, 0, "tags", "description", "keyword");
 
                 id = ContentUris.parseId(mProvider.insert(BrowserContract.Bookmarks.CONTENT_URI, b));
                 c = getBookmarkById(id);
                 mAsserter.is(c.moveToFirst(), true, "Inserted bookmark found");
+                c.close();
 
                 deleted = 0;
                 try {
                     Uri uri = ContentUris.withAppendedId(BrowserContract.Bookmarks.CONTENT_URI, parentId);
                     deleted = mProvider.delete(appendUriParam(uri, BrowserContract.PARAM_IS_SYNC, "1"), null, null);
                 } catch(Exception e) {}
 
                 mAsserter.is((deleted == 0), true,
@@ -659,21 +676,23 @@ public class testBrowserProvider extends
             final String pageUrl = b.getAsString(BrowserContract.Bookmarks.URL);
             long id = ContentUris.parseId(mProvider.insert(BrowserContract.Bookmarks.CONTENT_URI, b));
 
             // Insert the favicon into the favicons table
             mProvider.insert(BrowserContract.Favicons.CONTENT_URI, createFaviconEntry(pageUrl, "FAVICON"));
 
             Cursor c = getFaviconsByUrl(pageUrl);
             mAsserter.is(c.moveToFirst(), true, "Inserted favicon found");
+            c.close();
 
             mProvider.delete(ContentUris.withAppendedId(BrowserContract.Bookmarks.CONTENT_URI, id), null, null);
 
             c = getFaviconsByUrl(pageUrl);
             mAsserter.is(c.moveToFirst(), false, "Favicon is deleted with last reference to it");
+            c.close();
         }
     }
 
     class TestUpdateBookmarks extends Test {
         private int updateWithNullCol(long id, String colName) throws Exception {
             ContentValues u = new ContentValues();
             u.putNull(colName);
 
@@ -708,16 +727,17 @@ public class testBrowserProvider extends
             u.put(BrowserContract.Bookmarks.TYPE, BrowserContract.Bookmarks.TYPE_FOLDER);
             u.put(BrowserContract.Bookmarks.POSITION, 10);
 
             int updated = mProvider.update(BrowserContract.Bookmarks.CONTENT_URI, u,
                                            BrowserContract.Bookmarks._ID + " = ?",
                                            new String[] { String.valueOf(id) });
 
             mAsserter.is((updated == 1), true, "Inserted bookmark was updated");
+            c.close();
 
             c = getBookmarkById(id);
             mAsserter.is(c.moveToFirst(), true, "Updated bookmark found");
 
             mAsserter.is(c.getString(c.getColumnIndex(BrowserContract.Bookmarks.TITLE)), u.getAsString(BrowserContract.Bookmarks.TITLE),
                          "Inserted bookmark has correct title");
             mAsserter.is(c.getString(c.getColumnIndex(BrowserContract.Bookmarks.URL)), u.getAsString(BrowserContract.Bookmarks.URL),
                          "Inserted bookmark has correct URL");
@@ -747,22 +767,24 @@ public class testBrowserProvider extends
             updated = updateWithNullCol(id, BrowserContract.Bookmarks.TYPE);
             mAsserter.is((updated > 0), false,
                          "Should not be able to update bookmark with null type");
 
             u = new ContentValues();
             u.put(BrowserContract.Bookmarks.URL, "http://examples2.com");
 
             updated = mProvider.update(ContentUris.withAppendedId(BrowserContract.Bookmarks.CONTENT_URI, id), u, null, null);
+            c.close();
 
             c = getBookmarkById(id);
             mAsserter.is(c.moveToFirst(), true, "Updated bookmark found");
 
             mAsserter.is(c.getString(c.getColumnIndex(BrowserContract.Bookmarks.URL)), u.getAsString(BrowserContract.Bookmarks.URL),
                          "Updated bookmark has correct URL using URI with id");
+            c.close();
         }
     }
 
     class TestUpdateBookmarksFavicons extends Test {
         @Override
         public void test() throws Exception {
             ContentValues b = createOneBookmark();
 
@@ -779,22 +801,24 @@ public class testBrowserProvider extends
             Cursor c = getFaviconsByUrl(pageUrl);
             mAsserter.is(c.moveToFirst(), true, "Inserted favicon found");
 
             mAsserter.is(new String(c.getBlob(c.getColumnIndex(BrowserContract.Combined.FAVICON)), "UTF8"),
                          favicon, "Inserted favicon has corresponding favicon image");
 
             ContentValues u = createFaviconEntry(pageUrl, newFavicon);
             mProvider.update(BrowserContract.Favicons.CONTENT_URI, u, null, null);
+            c.close();
 
             c = getFaviconsByUrl(pageUrl);
             mAsserter.is(c.moveToFirst(), true, "Updated favicon found");
 
             mAsserter.is(new String(c.getBlob(c.getColumnIndex(BrowserContract.Combined.FAVICON)), "UTF8"),
                          newFavicon, "Updated favicon has corresponding favicon image");
+            c.close();
         }
     }
 
     /**
      * Create a folder of one thousand and one bookmarks, then impose an order
      * on them.
      *
      * Verify that the reordering worked by querying.
@@ -820,16 +844,17 @@ public class testBrowserProvider extends
                     mAsserter.is(pos, (long) i, "Position matches sequence.");
                     mAsserter.is(guid, items[i], "GUID matches sequence.");
                 }
                 ++i;
                 c.moveToNext();
             }
 
             mAsserter.is(i, count, "Folder has the right number of children.");
+            c.close();
         }
 
         public static final int NUMBER_OF_CHILDREN = 1001;
         @Override
         public void test() throws Exception {
             // Create the containing folder.
             ContentValues folder = createBookmark("FolderFolder", "", mMobileFolderId,
                                                   BrowserContract.Bookmarks.TYPE_FOLDER, 0, "",
@@ -916,16 +941,17 @@ public class testBrowserProvider extends
 
             id = insertWithNullCol(BrowserContract.History.URL);
             mAsserter.is(new Long(id), new Long(-1),
                          "Should not be able to insert history with null URL");
 
             id = insertWithNullCol(BrowserContract.History.VISITS);
             mAsserter.is(new Long(id), new Long(-1),
                          "Should not be able to insert history with null number of visits");
+            c.close();
         }
     }
 
     class TestInsertHistoryFavicons extends Test {
         @Override
         public void test() throws Exception {
             ContentValues h = createOneHistoryEntry();
 
@@ -938,32 +964,35 @@ public class testBrowserProvider extends
             mProvider.insert(BrowserContract.Favicons.CONTENT_URI, createFaviconEntry(pageUrl, favicon));
 
             Cursor c = getHistoryEntryById(id, new String[] { BrowserContract.History.FAVICON });
 
             mAsserter.is(c.moveToFirst(), true, "Inserted history entry found");
 
             mAsserter.is(new String(c.getBlob(c.getColumnIndex(BrowserContract.History.FAVICON)), "UTF8"),
                          favicon, "Inserted history entry has corresponding favicon image");
+            c.close();
 
             c = getFaviconsByUrl(pageUrl);
             mAsserter.is(c.moveToFirst(), true, "Inserted favicon found");
 
             mAsserter.is(new String(c.getBlob(c.getColumnIndex(BrowserContract.Combined.FAVICON)), "UTF8"),
                          favicon, "Inserted favicon has corresponding favicon image");
+            c.close();
         }
     }
 
     class TestDeleteHistory extends Test {
         private long insertOneHistoryEntry() throws Exception {
             ContentValues h = createOneHistoryEntry();
             long id = ContentUris.parseId(mProvider.insert(BrowserContract.History.CONTENT_URI, h));
 
             Cursor c = getHistoryEntryById(id);
             mAsserter.is(c.moveToFirst(), true, "Inserted history entry found");
+            c.close();
 
             return id;
         }
 
         @Override
         public void test() throws Exception {
             long id = insertOneHistoryEntry();
 
@@ -976,29 +1005,32 @@ public class testBrowserProvider extends
             Cursor c = getHistoryEntryById(appendUriParam(BrowserContract.History.CONTENT_URI, BrowserContract.PARAM_SHOW_DELETED, "1"), id);
             mAsserter.is(c.moveToFirst(), true, "Deleted history entry was only marked as deleted");
 
             deleted = mProvider.delete(appendUriParam(BrowserContract.History.CONTENT_URI, BrowserContract.PARAM_IS_SYNC, "1"),
                                        BrowserContract.History._ID + " = ?",
                                        new String[] { String.valueOf(id) });
 
             mAsserter.is((deleted == 1), true, "Inserted history entry was deleted");
+            c.close();
 
             c = getHistoryEntryById(appendUriParam(BrowserContract.History.CONTENT_URI, BrowserContract.PARAM_SHOW_DELETED, "1"), id);
             mAsserter.is(c.moveToFirst(), false, "Inserted history is now actually deleted");
 
             id = insertOneHistoryEntry();
 
             deleted = mProvider.delete(ContentUris.withAppendedId(BrowserContract.History.CONTENT_URI, id), null, null);
             mAsserter.is((deleted == 1), true,
                          "Inserted history entry was deleted using URI with id");
+            c.close();
 
             c = getHistoryEntryById(id);
             mAsserter.is(c.moveToFirst(), false,
                          "Inserted history entry can't be found after deletion using URI with ID");
+            c.close();
         }
     }
 
     class TestDeleteHistoryFavicons extends Test {
         @Override
         public void test() throws Exception {
             ContentValues h = createOneHistoryEntry();
 
@@ -1007,19 +1039,21 @@ public class testBrowserProvider extends
 
             // Insert the favicon into the favicons table
             mProvider.insert(BrowserContract.Favicons.CONTENT_URI, createFaviconEntry(pageUrl, "FAVICON"));
 
             Cursor c = getFaviconsByUrl(pageUrl);
             mAsserter.is(c.moveToFirst(), true, "Inserted favicon found");
 
             mProvider.delete(ContentUris.withAppendedId(BrowserContract.History.CONTENT_URI, id), null, null);
+            c.close();
 
             c = getFaviconsByUrl(pageUrl);
             mAsserter.is(c.moveToFirst(), false, "Favicon is deleted with last reference to it");
+            c.close();
         }
     }
 
     class TestUpdateHistory extends Test {
         private int updateWithNullCol(long id, String colName) throws Exception {
             ContentValues u = new ContentValues();
             u.putNull(colName);
 
@@ -1051,16 +1085,17 @@ public class testBrowserProvider extends
             u.put(BrowserContract.History.TITLE, h.getAsString(BrowserContract.History.TITLE) + "CHANGED");
             u.put(BrowserContract.History.URL, h.getAsString(BrowserContract.History.URL) + "/more/stuff");
 
             int updated = mProvider.update(BrowserContract.History.CONTENT_URI, u,
                                            BrowserContract.History._ID + " = ?",
                                            new String[] { String.valueOf(id) });
 
             mAsserter.is((updated == 1), true, "Inserted history entry was updated");
+            c.close();
 
             c = getHistoryEntryById(id);
             mAsserter.is(c.moveToFirst(), true, "Updated history entry found");
 
             mAsserter.is(c.getString(c.getColumnIndex(BrowserContract.History.TITLE)), u.getAsString(BrowserContract.History.TITLE),
                          "Updated history entry has correct title");
             mAsserter.is(c.getString(c.getColumnIndex(BrowserContract.History.URL)), u.getAsString(BrowserContract.History.URL),
                          "Updated history entry has correct URL");
@@ -1084,22 +1119,24 @@ public class testBrowserProvider extends
             updated = updateWithNullCol(id, BrowserContract.History.VISITS);
             mAsserter.is((updated > 0), false,
                          "Should not be able to update history with null number of visits");
 
             u = new ContentValues();
             u.put(BrowserContract.History.URL, "http://examples2.com");
 
             updated = mProvider.update(ContentUris.withAppendedId(BrowserContract.History.CONTENT_URI, id), u, null, null);
+            c.close();
 
             c = getHistoryEntryById(id);
             mAsserter.is(c.moveToFirst(), true, "Updated history entry found");
 
             mAsserter.is(c.getString(c.getColumnIndex(BrowserContract.History.URL)), u.getAsString(BrowserContract.History.URL),
                          "Updated history entry has correct URL using URI with id");
+            c.close();
         }
     }
 
     class TestUpdateHistoryFavicons extends Test {
         @Override
         public void test() throws Exception {
             ContentValues h = createOneHistoryEntry();
 
@@ -1116,22 +1153,24 @@ public class testBrowserProvider extends
             mAsserter.is(c.moveToFirst(), true, "Inserted favicon found");
 
             mAsserter.is(new String(c.getBlob(c.getColumnIndex(BrowserContract.Combined.FAVICON)), "UTF8"),
                          favicon, "Inserted favicon has corresponding favicon image");
 
             ContentValues u = createFaviconEntry(pageUrl, newFavicon);
 
             mProvider.update(BrowserContract.Favicons.CONTENT_URI, u, null, null);
+            c.close();
 
             c = getFaviconsByUrl(pageUrl);
             mAsserter.is(c.moveToFirst(), true, "Updated favicon found");
 
             mAsserter.is(new String(c.getBlob(c.getColumnIndex(BrowserContract.Combined.FAVICON)), "UTF8"),
                          newFavicon, "Updated favicon has corresponding favicon image");
+            c.close();
         }
     }
 
     class TestUpdateOrInsertHistory extends Test {
         private final String TEST_URL_1 = "http://example.com";
         private final String TEST_URL_2 = "http://example.org";
         private final String TEST_TITLE = "Example";
 
@@ -1190,16 +1229,17 @@ public class testBrowserProvider extends
             values = new ContentValues();
             values.put(BrowserContract.History.DATE_LAST_VISITED, System.currentTimeMillis());
             values.put(BrowserContract.History.TITLE, TEST_TITLE);
 
             updated = mProvider.update(updateOrInsertHistoryUri, values,
                                        BrowserContract.History._ID + " = ?",
                                        new String[] { String.valueOf(id) });
             mAsserter.is((updated == 1), true, "Inserted history entry was updated");
+            c.close();
 
             c = getHistoryEntryById(id);
             mAsserter.is(c.moveToFirst(), true, "Updated history entry found");
 
             mAsserter.is(c.getString(c.getColumnIndex(BrowserContract.History.TITLE)), TEST_TITLE,
                          "Updated history entry has correct title");
             mAsserter.is(new Long(c.getLong(c.getColumnIndex(BrowserContract.History.VISITS))), new Long(2),
                          "Updated history entry has correct number of visits");
@@ -1215,16 +1255,18 @@ public class testBrowserProvider extends
             values.put(BrowserContract.History.VISITS, 10);
 
             updated = mProvider.update(updateOrInsertHistoryUri, values,
                                            BrowserContract.History.URL + " = ?",
                                            new String[] { values.getAsString(BrowserContract.History.URL) });
             mAsserter.is((updated == 1), true, "History entry was inserted");
 
             id = getHistoryEntryIdByUrl(TEST_URL_2);
+            c.close();
+
             c = getHistoryEntryById(id);
             mAsserter.is(c.moveToFirst(), true, "History entry was inserted");
 
             dateCreated = c.getLong(c.getColumnIndex(BrowserContract.History.DATE_CREATED));
             dateModified = c.getLong(c.getColumnIndex(BrowserContract.History.DATE_MODIFIED));
 
             mAsserter.is(new Long(c.getLong(c.getColumnIndex(BrowserContract.History.VISITS))), new Long(10),
                          "Inserted history entry has correct specified number of visits");
@@ -1234,30 +1276,32 @@ public class testBrowserProvider extends
             // Update the history entry, specifying additional visit count
             values = new ContentValues();
             values.put(BrowserContract.History.VISITS, 10);
 
             updated = mProvider.update(updateOrInsertHistoryUri, values,
                                        BrowserContract.History._ID + " = ?",
                                        new String[] { String.valueOf(id) });
             mAsserter.is((updated == 1), true, "Inserted history entry was updated");
+            c.close();
 
             c = getHistoryEntryById(id);
             mAsserter.is(c.moveToFirst(), true, "Updated history entry found");
 
             mAsserter.is(c.getString(c.getColumnIndex(BrowserContract.History.TITLE)), TEST_TITLE,
                          "Updated history entry has correct unchanged title");
             mAsserter.is(c.getString(c.getColumnIndex(BrowserContract.History.URL)), TEST_URL_2,
                          "Updated history entry has correct unchanged URL");
             mAsserter.is(new Long(c.getLong(c.getColumnIndex(BrowserContract.History.VISITS))), new Long(20),
                          "Updated history entry has correct number of visits");
             mAsserter.is(new Long(c.getLong(c.getColumnIndex(BrowserContract.History.DATE_CREATED))), new Long(dateCreated),
                          "Updated history entry has same creation date");
             mAsserter.isnot(new Long(c.getLong(c.getColumnIndex(BrowserContract.History.DATE_MODIFIED))), new Long(dateModified),
                             "Updated history entry has new modification date");
+            c.close();
 
         }
     }
 
     class TestInsertHistoryThumbnails extends Test {
         @Override
         public void test() throws Exception {
             ContentValues h = createOneHistoryEntry();
@@ -1270,16 +1314,17 @@ public class testBrowserProvider extends
             // Insert the thumbnail into the thumbnails table
             mProvider.insert(BrowserContract.Thumbnails.CONTENT_URI, createThumbnailEntry(pageUrl, thumbnail));
 
             Cursor c = getThumbnailByUrl(pageUrl);
             mAsserter.is(c.moveToFirst(), true, "Inserted thumbnail found");
 
             mAsserter.is(new String(c.getBlob(c.getColumnIndex(BrowserContract.Thumbnails.DATA)), "UTF8"),
                          thumbnail, "Inserted thumbnail has corresponding thumbnail image");
+            c.close();
         }
     }
 
     class TestUpdateHistoryThumbnails extends Test {
         @Override
         public void test() throws Exception {
             ContentValues h = createOneHistoryEntry();
 
@@ -1296,22 +1341,24 @@ public class testBrowserProvider extends
             mAsserter.is(c.moveToFirst(), true, "Inserted thumbnail found");
 
             mAsserter.is(new String(c.getBlob(c.getColumnIndex(BrowserContract.Thumbnails.DATA)), "UTF8"),
                          thumbnail, "Inserted thumbnail has corresponding thumbnail image");
 
             ContentValues u = createThumbnailEntry(pageUrl, newThumbnail);
 
             mProvider.update(BrowserContract.Thumbnails.CONTENT_URI, u, null, null);
+            c.close();
 
             c = getThumbnailByUrl(pageUrl);
             mAsserter.is(c.moveToFirst(), true, "Updated thumbnail found");
 
             mAsserter.is(new String(c.getBlob(c.getColumnIndex(BrowserContract.Thumbnails.DATA)), "UTF8"),
                          newThumbnail, "Updated thumbnail has corresponding thumbnail image");
+            c.close();
         }
     }
 
     class TestDeleteHistoryThumbnails extends Test {
         @Override
         public void test() throws Exception {
             ContentValues h = createOneHistoryEntry();
 
@@ -1320,19 +1367,21 @@ public class testBrowserProvider extends
 
             // Insert the thumbnail into the thumbnails table
             mProvider.insert(BrowserContract.Thumbnails.CONTENT_URI, createThumbnailEntry(pageUrl, "THUMBNAIL"));
 
             Cursor c = getThumbnailByUrl(pageUrl);
             mAsserter.is(c.moveToFirst(), true, "Inserted thumbnail found");
 
             mProvider.delete(ContentUris.withAppendedId(BrowserContract.History.CONTENT_URI, id), null, null);
+            c.close();
 
             c = getThumbnailByUrl(pageUrl);
             mAsserter.is(c.moveToFirst(), false, "Thumbnail is deleted with last reference to it");
+            c.close();
         }
     }
 
     class TestCombinedView extends Test {
         @Override
         public void test() throws Exception {
             final String TITLE_1 = "Test Page 1";
             final String TITLE_2 = "Test Page 2";
@@ -1430,16 +1479,17 @@ public class testBrowserProvider extends
             mAsserter.is(new Long(c.getLong(c.getColumnIndex(BrowserContract.Combined.HISTORY_ID))), new Long(combinedHistoryId),
                          "Combined entry has correct history id");
             mAsserter.is(c.getString(c.getColumnIndex(BrowserContract.Combined.URL)), URL_3,
                          "Combined entry has correct url");
             mAsserter.is(c.getInt(c.getColumnIndex(BrowserContract.Combined.VISITS)), VISITS,
                          "Combined entry has correct number of visits");
             mAsserter.is(new Long(c.getLong(c.getColumnIndex(BrowserContract.Combined.DATE_LAST_VISITED))), new Long(LAST_VISITED),
                          "Combined entry has correct last visit time");
+            c.close();
         }
     }
 
     class TestCombinedViewDisplay extends Test {
         @Override
         public void test() throws Exception {
             final String TITLE_1 = "Test Page 1";
             final String TITLE_2 = "Test Page 2";
@@ -1490,16 +1540,17 @@ public class testBrowserProvider extends
                 long id = c.getLong(c.getColumnIndex(BrowserContract.Combined.BOOKMARK_ID));
 
                 int display = c.getInt(c.getColumnIndex(BrowserContract.Combined.DISPLAY));
                 int expectedDisplay = (id == readingListItemId || id == readingListItemId2 ? BrowserContract.Combined.DISPLAY_READER : BrowserContract.Combined.DISPLAY_NORMAL);
 
                 mAsserter.is(new Integer(display), new Integer(expectedDisplay),
                                  "Combined display column should always be DISPLAY_READER for the reading list item");
             }
+            c.close();
         }
     }
 
     class TestCombinedViewWithDeletedBookmark extends Test {
         @Override
         public void test() throws Exception {
             final String TITLE = "Test Page 1";
             final String URL = "http://example.com";
@@ -1522,23 +1573,25 @@ public class testBrowserProvider extends
             mAsserter.is(new Long(c.getLong(c.getColumnIndex(BrowserContract.Combined.BOOKMARK_ID))), new Long(combinedBookmarkId),
                          "Bookmark id should be set correctly on combined entry");
 
             int deleted = mProvider.delete(BrowserContract.Bookmarks.CONTENT_URI,
                                            BrowserContract.Bookmarks._ID + " = ?",
                                            new String[] { String.valueOf(combinedBookmarkId) });
 
             mAsserter.is((deleted == 1), true, "Inserted combined bookmark was deleted");
+            c.close();
 
             c = mProvider.query(BrowserContract.Combined.CONTENT_URI, null, "", null, null);
             mAsserter.is(c.getCount(), 1, "1 combined entry found");
 
             mAsserter.is(c.moveToFirst(), true, "Found combined entry without bookmark id");
             mAsserter.is(new Long(c.getLong(c.getColumnIndex(BrowserContract.Combined.BOOKMARK_ID))), new Long(0),
                          "Bookmark id should not be set to removed bookmark id");
+            c.close();
         }
     }
 
     class TestCombinedViewWithDeletedReadingListItem extends Test {
         @Override
         public void test() throws Exception {
             final String TITLE = "Test Page 1";
             final String URL = "http://example.com";
@@ -1564,25 +1617,27 @@ public class testBrowserProvider extends
             mAsserter.is(new Long(c.getLong(c.getColumnIndex(BrowserContract.Combined.DISPLAY))), new Long(BrowserContract.Combined.DISPLAY_READER),
                          "Combined entry should have reader display type");
 
             int deleted = mProvider.delete(BrowserContract.Bookmarks.CONTENT_URI,
                                            BrowserContract.Bookmarks._ID + " = ?",
                                            new String[] { String.valueOf(combinedReadingListItemId) });
 
             mAsserter.is((deleted == 1), true, "Inserted combined reading list item was deleted");
+            c.close();
 
             c = mProvider.query(BrowserContract.Combined.CONTENT_URI, null, "", null, null);
             mAsserter.is(c.getCount(), 1, "1 combined entry found");
 
             mAsserter.is(c.moveToFirst(), true, "Found combined entry without bookmark id");
             mAsserter.is(new Long(c.getLong(c.getColumnIndex(BrowserContract.Combined.BOOKMARK_ID))), new Long(0),
                          "Bookmark id should not be set to removed bookmark id");
             mAsserter.is(new Long(c.getLong(c.getColumnIndex(BrowserContract.Combined.DISPLAY))), new Long(BrowserContract.Combined.DISPLAY_NORMAL),
                          "Combined entry should have reader display type");
+            c.close();
         }
     }
 
     class TestExpireHistory extends Test {
         private void createFakeHistory(long timeShift, int count) {
             // Insert a bunch of very new entries
             ContentValues[] allVals = new ContentValues[count];
             long time = System.currentTimeMillis() - timeShift;
@@ -1603,96 +1658,101 @@ public class testBrowserProvider extends
                 ContentValues cv = new ContentValues();
                 cv.put(BrowserContract.History.DATE_CREATED, time);
                 cv.put(BrowserContract.History.DATE_MODIFIED, time);
                 mProvider.update(BrowserContract.History.CONTENT_URI, cv, BrowserContract.History.URL + " = ?",
                                  new String[] { "http://www.test.org/" + i });
             }
 
             Cursor c = mProvider.query(BrowserContract.History.CONTENT_URI, null, "", null, null);
-            mAsserter.is(c.getCount(), count, count + " history entries found");
+
+            assertCountIsAndClose(c, count, count + " history entries found");
 
             // add thumbnails for each entry
             allVals = new ContentValues[count];
             for (int i = 0; i < count; i++) {
                 allVals[i] = new ContentValues();
                 allVals[i].put(BrowserContract.Thumbnails.DATA, i);
                 allVals[i].put(BrowserContract.Thumbnails.URL, "http://www.test.org/" + i);
             }
 
             inserts = mProvider.bulkInsert(BrowserContract.Thumbnails.CONTENT_URI, allVals);
             mAsserter.is(inserts, count, "Expected number of inserts matches");
 
             c = mProvider.query(BrowserContract.Thumbnails.CONTENT_URI, null, null, null, null);
-            mAsserter.is(c.getCount(), count, count + " thumbnails entries found");
+            assertCountIsAndClose(c, count, count + " thumbnails entries found");
         }
 
         @Override
         public void test() throws Exception {
             final int count = 3000;
             final int thumbCount = 15;
 
             // insert a bunch of new entries
             createFakeHistory(0, count);
 
             // expiring with a normal priority should not delete new entries
             Uri url = appendUriParam(BrowserContract.History.CONTENT_OLD_URI, BrowserContract.PARAM_EXPIRE_PRIORITY, "NORMAL");
             mProvider.delete(url, null, null);
             Cursor c = mProvider.query(BrowserContract.History.CONTENT_URI, null, "", null, null);
-            mAsserter.is(c.getCount(), count, count + " history entries found");
+            assertCountIsAndClose(c, count, count + " history entries found");
 
             // expiring with a normal priority should delete all but 10 thumbnails
             c = mProvider.query(BrowserContract.Thumbnails.CONTENT_URI, null, null, null, null);
-            mAsserter.is(c.getCount(), thumbCount, thumbCount + " thumbnails found");
+            assertCountIsAndClose(c, thumbCount, thumbCount + " thumbnails found");
 
             ensureEmptyDatabase();
-            // insert a bunch of new entries
+
+            // Insert a bunch of new entries.
             createFakeHistory(0, count);
 
-            // expiring with a aggressive priority should leave 500 entries
+            // Expiring with a aggressive priority should leave 500 entries.
             url = appendUriParam(BrowserContract.History.CONTENT_OLD_URI, BrowserContract.PARAM_EXPIRE_PRIORITY, "AGGRESSIVE");
             mProvider.delete(url, null, null);
+
             c = mProvider.query(BrowserContract.History.CONTENT_URI, null, "", null, null);
-            mAsserter.is(c.getCount(), 500, "500 history entries found");
+            assertCountIsAndClose(c, 500, "500 history entries found");
 
-            // expiring with a aggressive priority should delete all but 10 thumbnails
+            // Expiring with a aggressive priority should delete all but 10 thumbnails.
             c = mProvider.query(BrowserContract.Thumbnails.CONTENT_URI, null, null, null, null);
-            mAsserter.is(c.getCount(), thumbCount, thumbCount + " thumbnails found");
+            assertCountIsAndClose(c, thumbCount, thumbCount + " thumbnails found");
 
             ensureEmptyDatabase();
-            // insert a bunch of entries with an old time created/modified
+
+            // Insert a bunch of entries with an old time created/modified.
             long time = 1000L * 60L * 60L * 24L * 30L * 3L;
             createFakeHistory(time, count);
 
-            // expiring with an normal priority should remove at most 1000 entries
-            // entries leaving at least 2000
+            // Expiring with an normal priority should remove at most 1000 entries,
+            // entries leaving at least 2000.
             url = appendUriParam(BrowserContract.History.CONTENT_OLD_URI, BrowserContract.PARAM_EXPIRE_PRIORITY, "NORMAL");
             mProvider.delete(url, null, null);
+
             c = mProvider.query(BrowserContract.History.CONTENT_URI, null, "", null, null);
-            mAsserter.is(c.getCount(), 2000, "2000 history entries found");
+            assertCountIsAndClose(c, 2000, "2000 history entries found");
 
-            // expiring with a normal priority should delete all but 10 thumbnails
+            // Expiring with a normal priority should delete all but 10 thumbnails.
             c = mProvider.query(BrowserContract.Thumbnails.CONTENT_URI, null, null, null, null);
-            mAsserter.is(c.getCount(), thumbCount, thumbCount + " thumbnails found");
+            assertCountIsAndClose(c, thumbCount, thumbCount + " thumbnails found");
 
             ensureEmptyDatabase();
             // insert a bunch of entries with an old time created/modified
             time = 1000L * 60L * 60L * 24L * 30L * 3L;
             createFakeHistory(time, count);
 
-            // expiring with an agressive priority should remove old
-            // entries leaving at least 500
+            // Expiring with an aggressive priority should remove old
+            // entries, leaving at least 500.
             url = appendUriParam(BrowserContract.History.CONTENT_OLD_URI, BrowserContract.PARAM_EXPIRE_PRIORITY, "AGGRESSIVE");
             mProvider.delete(url, null, null);
             c = mProvider.query(BrowserContract.History.CONTENT_URI, null, "", null, null);
-            mAsserter.is(c.getCount(), 500, "500 history entries found");
+            assertCountIsAndClose(c, 500, "500 history entries found");
 
             // expiring with an aggressive priority should delete all but 10 thumbnails
             c = mProvider.query(BrowserContract.Thumbnails.CONTENT_URI, null, null, null, null);
-            mAsserter.is(c.getCount(), thumbCount, thumbCount + " thumbnails found");
+            assertCountIsAndClose(c, thumbCount, thumbCount + " thumbnails found");
         }
     }
 
     /*
      * Verify that insert, update, delete, and bulkInsert operations
      * notify the ambient content resolver.  Each operation calls the
      * content resolver notifyChange method synchronously, so it is
      * okay to test sequentially.
@@ -1771,9 +1831,21 @@ public class testBrowserProvider extends
 
             mAsserter.is(Long.valueOf(numBulkInserted),
                          Long.valueOf(1),
                          "Correct number of items are bulkInserted");
 
             ensureOnlyChangeNotifiedStartsWith(BrowserContract.History.CONTENT_URI, "bulkInsert");
         }
     }
+
+    /**
+     * Assert that the provided cursor has the expected number of rows,
+     * closing the cursor afterwards.
+     */
+    private void assertCountIsAndClose(Cursor c, int expectedCount, String message) {
+        try {
+            mAsserter.is(c.getCount(), expectedCount, message);
+        } finally {
+            c.close();
+        }
+    }
 }
--- 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);
--- a/toolkit/components/viewsource/content/viewSource.js
+++ b/toolkit/components/viewsource/content/viewSource.js
@@ -652,92 +652,21 @@ function BrowserCharsetReload()
   if (isHistoryEnabled()) {
     gPageLoader.loadPage(gPageLoader.currentDescriptor,
                          gPageLoader.DISPLAY_NORMAL);
   } else {
     gBrowser.reloadWithFlags(Ci.nsIWebNavigation.LOAD_FLAGS_CHARSET_CHANGE);
   }
 }
 
-function BrowserSetForcedCharacterSet(aCharset)
-{
-  gBrowser.docShell.charset = aCharset;
-  BrowserCharsetReload();
-}
-
-function MultiplexHandler(event)
-{
-  var node = event.target;
-  var name = node.getAttribute("name");
-
-  if (name == "detectorGroup") {
-    SelectDetector(event);
-    BrowserCharsetReload();
-  } else if (name == "charsetGroup") {
-    var charset = node.getAttribute("id");
-    charset = charset.substring(charset.indexOf("charset.") + "charset.".length);
-    BrowserSetForcedCharacterSet(charset);
-  }
-}
-
-function SelectDetector(event)
+function BrowserSetCharacterSet(aEvent)
 {
-  var uri =  event.target.getAttribute("id");
-  var prefvalue = uri.substring(uri.indexOf("chardet.") + "chardet.".length);
-  if ("off" == prefvalue) { // "off" is special value to turn off the detectors
-    prefvalue = "";
-  }
-
-  try {
-    var str = Cc["@mozilla.org/supports-string;1"].
-              createInstance(Ci.nsISupportsString);
-    str.data = prefvalue;
-    gPrefService.setComplexValue("intl.charset.detector", Ci.nsISupportsString, str);
-  }
-  catch (ex) {
-    dump("Failed to set the intl.charset.detector preference.\n");
-  }
-}
-
-function FoldCharset(charset) {
-  // For substantially similar encodings, treat two encodings as the same
-  // for the purpose of the check mark.
-  if (charset == "ISO-8859-8-I") {
-    return "windows-1255";
-  } else if (charset == "gb18030") {
-    return "gbk";
-  }
-  return charset;
-}
-
-function UpdateCurrentCharset() {
-  var menuitem = document.getElementById("charset." + FoldCharset(content.document.characterSet));
-  if (menuitem)
-    menuitem.setAttribute("checked", "true");
-}
-
-function UpdateCharsetDetector() {
-  var prefvalue;
-
-  try {
-    prefvalue = gPrefService.getComplexValue("intl.charset.detector", Ci.nsIPrefLocalizedString).data;
-  }
-  catch (ex) {}
-
-  if (!prefvalue)
-    prefvalue = "off";
-
-  var menuitem = document.getElementById("chardet." + prefvalue);
-  if (menuitem)
-    menuitem.setAttribute("checked", "true");
-}
-
-function UpdateMenus() {
-  UpdateCurrentCharset();
-  UpdateCharsetDetector();
+  if (aEvent.target.hasAttribute("charset"))
+    gBrowser.docShell.charset = aEvent.target.getAttribute("charset");
+  BrowserCharsetReload();
 }
 
 function BrowserForward(aEvent) {
   try {
     gBrowser.goForward();
   }
   catch(ex) {
   }
--- a/toolkit/components/viewsource/content/viewSource.xul
+++ b/toolkit/components/viewsource/content/viewSource.xul
@@ -196,21 +196,20 @@
                         key="key_textZoomReset"/>
             </menupopup>
           </menu>
 
           <!-- Charset Menu -->
           <menu id="charsetMenu"
                 label="&charsetMenu.label;"
                 accesskey="&charsetMenu.accesskey;"
-                oncommand="MultiplexHandler(event);"
+                oncommand="BrowserSetCharacterSet(event);"
                 onpopupshowing="CharsetMenu.build(event.target);"
-                onpopupshown="UpdateMenus();">
-            <menupopup>
-            </menupopup>
+                onpopupshown="CharsetMenu.update(event, content.document.characterSet);">
+            <menupopup/>
           </menu>
           <menuseparator/>
           <menuitem id="menu_wrapLongLines" type="checkbox" command="cmd_wrapLongLines"
                     label="&menu_wrapLongLines.title;" accesskey="&menu_wrapLongLines.accesskey;"/>
           <menuitem type="checkbox" id="menu_highlightSyntax" command="cmd_highlightSyntax"
                     label="&menu_highlightSyntax.label;" accesskey="&menu_highlightSyntax.accesskey;"/>
         </menupopup>
       </menu>
--- a/toolkit/modules/CharsetMenu.jsm
+++ b/toolkit/modules/CharsetMenu.jsm
@@ -9,17 +9,17 @@ const { classes: Cc, interfaces: Ci, uti
 Cu.import("resource://gre/modules/Services.jsm");
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 XPCOMUtils.defineLazyGetter(this, "gBundle", function() {
   const kUrl = "chrome://global/locale/charsetMenu.properties";
   return Services.strings.createBundle(kUrl);
 });
 
 const kAutoDetectors = [
-  ["off", "off"],
+  ["off", ""],
   ["ja", "ja_parallel_state_machine"],
   ["ru", "ruprob"],
   ["uk", "ukprob"]
 ];
 
 /**
  * This set contains encodings that are in the Encoding Standard, except:
  *  - XSS-dangerous encodings (except ISO-2022-JP which is assumed to be
@@ -84,56 +84,81 @@ const kEncodings = new Set([
 // Always at the start of the menu, in this order, followed by a separator.
 const kPinned = [
   "UTF-8",
   "windows-1252"
 ];
 
 kPinned.forEach(x => kEncodings.delete(x));
 
+function CharsetComparator(a, b) {
+  // Normal sorting sorts the part in parenthesis in an order that
+  // happens to make the less frequently-used items first.
+  let titleA = a.label.replace(/\(.*/, "") + b.value;
+  let titleB = b.label.replace(/\(.*/, "") + a.value;
+  // Secondarily reverse sort by encoding name to sort "windows" or
+  // "shift_jis" first.
+  return titleA.localeCompare(titleB) || b.value.localeCompare(a.value);
+}
+
+function SetDetector(event) {
+  let str = Cc["@mozilla.org/supports-string;1"].createInstance(Ci.nsISupportsString);
+  str.data = event.target.getAttribute("detector");
+  Services.prefs.setComplexValue("intl.charset.detector", Ci.nsISupportsString, str);
+}
+
+function UpdateDetectorMenu(event) {
+  event.stopPropagation();
+  let detector = Services.prefs.getComplexValue("intl.charset.detector", Ci.nsIPrefLocalizedString);
+  let menuitem = this.getElementsByAttribute("detector", detector).item(0);
+  if (menuitem) {
+    menuitem.setAttribute("checked", "true");
+  }
+}
 
 let gDetectorInfoCache, gCharsetInfoCache, gPinnedInfoCache;
 
 let CharsetMenu = {
-  build: function(parent, idPrefix="", showAccessKeys=true) {
+  build: function(parent, showAccessKeys=true, showDetector=true) {
     function createDOMNode(doc, nodeInfo) {
       let node = doc.createElement("menuitem");
       node.setAttribute("type", "radio");
-      node.setAttribute("name", nodeInfo.name);
+      node.setAttribute("name", nodeInfo.name + "Group");
+      node.setAttribute(nodeInfo.name, nodeInfo.value);
       node.setAttribute("label", nodeInfo.label);
       if (showAccessKeys && nodeInfo.accesskey) {
         node.setAttribute("accesskey", nodeInfo.accesskey);
       }
-      if (idPrefix) {
-        node.id = idPrefix + nodeInfo.id;
-      } else {
-        node.id = nodeInfo.id;
-      }
       return node;
     }
 
-    if (parent.childElementCount > 0) {
+    if (parent.hasChildNodes()) {
       // Detector menu or charset menu already built
       return;
     }
+    this._ensureDataReady();
     let doc = parent.ownerDocument;
 
-    let menuNode = doc.createElement("menu");
-    menuNode.setAttribute("label", gBundle.GetStringFromName("charsetMenuAutodet"));
-    if (showAccessKeys) {
-      menuNode.setAttribute("accesskey", gBundle.GetStringFromName("charsetMenuAutodet.key"));
-    }
-    parent.appendChild(menuNode);
+    if (showDetector) {
+      let menuNode = doc.createElement("menu");
+      menuNode.setAttribute("label", gBundle.GetStringFromName("charsetMenuAutodet"));
+      if (showAccessKeys) {
+        menuNode.setAttribute("accesskey", gBundle.GetStringFromName("charsetMenuAutodet.key"));
+      }
+      parent.appendChild(menuNode);
 
-    let menuPopupNode = doc.createElement("menupopup");
-    menuNode.appendChild(menuPopupNode);
+      let menuPopupNode = doc.createElement("menupopup");
+      menuNode.appendChild(menuPopupNode);
+      menuPopupNode.addEventListener("command", SetDetector);
+      menuPopupNode.addEventListener("popupshown", UpdateDetectorMenu);
 
-    this._ensureDataReady();
-    gDetectorInfoCache.forEach(detectorInfo => menuPopupNode.appendChild(createDOMNode(doc, detectorInfo)));
-    parent.appendChild(doc.createElement("menuseparator"));
+      gDetectorInfoCache.forEach(detectorInfo => menuPopupNode.appendChild(createDOMNode(doc, detectorInfo)));
+      parent.appendChild(doc.createElement("menuseparator"));
+    }
+
     gPinnedInfoCache.forEach(charsetInfo => parent.appendChild(createDOMNode(doc, charsetInfo)));
     parent.appendChild(doc.createElement("menuseparator"));
     gCharsetInfoCache.forEach(charsetInfo => parent.appendChild(createDOMNode(doc, charsetInfo)));
   },
 
   getData: function() {
     this._ensureDataReady();
     return {
@@ -142,68 +167,40 @@ let CharsetMenu = {
       otherCharsets: gCharsetInfoCache
     };
   },
 
   _ensureDataReady: function() {
     if (!gDetectorInfoCache) {
       gDetectorInfoCache = this.getDetectorInfo();
       gPinnedInfoCache = this.getCharsetInfo(kPinned, false);
-      gCharsetInfoCache = this.getCharsetInfo([...kEncodings]);
+      gCharsetInfoCache = this.getCharsetInfo(kEncodings);
     }
   },
 
   getDetectorInfo: function() {
     return kAutoDetectors.map(([detectorName, nodeId]) => ({
-      id: "chardet." + nodeId,
       label: this._getDetectorLabel(detectorName),
       accesskey: this._getDetectorAccesskey(detectorName),
-      name: "detectorGroup",
+      name: "detector",
+      value: nodeId
     }));
   },
 
   getCharsetInfo: function(charsets, sort=true) {
-    let list = charsets.map(charset => ({
-      id: "charset." + charset,
+    let list = [{
       label: this._getCharsetLabel(charset),
       accesskey: this._getCharsetAccessKey(charset),
-      name: "charsetGroup",
-    }));
-
-    if (!sort) {
-      return list;
-    }
+      name: "charset",
+      value: charset
+    } for (charset of charsets)];
 
-    list.sort(function (a, b) {
-      let titleA = a.label;
-      let titleB = b.label;
-      // Normal sorting sorts the part in parenthesis in an order that
-      // happens to make the less frequently-used items first.
-      let index;
-      if ((index = titleA.indexOf("(")) > -1) {
-        titleA = titleA.substring(0, index);
-      }
-      if ((index = titleB.indexOf("(")) > -1) {
-        titleA = titleB.substring(0, index);
-      }
-      let comp = titleA.localeCompare(titleB);
-      if (comp) {
-        return comp;
-      }
-      // secondarily reverse sort by encoding name to sort "windows" or
-      // "shift_jis" first. This works regardless of localization, because
-      // the ids aren't localized.
-      if (a.id < b.id) {
-        return 1;
-      }
-      if (b.id < a.id) {
-        return -1;
-      }
-      return 0;
-    });
+    if (sort) {
+      list.sort(CharsetComparator);
+    }
     return list;
   },
 
   _getDetectorLabel: function(detector) {
     try {
       return gBundle.GetStringFromName("charsetMenuAutodet." + detector);
     } catch (ex) {}
     return detector;
@@ -230,12 +227,36 @@ let CharsetMenu = {
       // Localization key has been revised
       charset = "gbk.bis";
     }
     try {
       return gBundle.GetStringFromName(charset + ".key");
     } catch (ex) {}
     return "";
   },
+
+  /**
+   * For substantially similar encodings, treat two encodings as the same
+   * for the purpose of the check mark.
+   */
+  foldCharset: function(charset) {
+    switch (charset) {
+      case "ISO-8859-8-I":
+        return "windows-1255";
+
+      case "gb18030":
+        return "gbk";
+
+      default:
+        return charset;
+    }
+  },
+
+  update: function(event, charset) {
+    let menuitem = event.target.getElementsByAttribute("charset", this.foldCharset(charset)).item(0);
+    if (menuitem) {
+      menuitem.setAttribute("checked", "true");
+    }
+  },
 };
 
 Object.freeze(CharsetMenu);
 
--- a/toolkit/mozapps/extensions/AddonManager.jsm
+++ b/toolkit/mozapps/extensions/AddonManager.jsm
@@ -78,38 +78,94 @@ this.EXPORTED_SYMBOLS = [ "AddonManager"
 const CATEGORY_PROVIDER_MODULE = "addon-provider-module";
 
 // A list of providers to load by default
 const DEFAULT_PROVIDERS = [
   "resource://gre/modules/addons/XPIProvider.jsm",
   "resource://gre/modules/LightweightThemeManager.jsm"
 ];
 
-["LOG", "WARN", "ERROR"].forEach(function(aName) {
-  this.__defineGetter__(aName, function logFuncGetter() {
-    Components.utils.import("resource://gre/modules/addons/AddonLogging.jsm");
-
-    LogManager.getLogger("addons.manager", this);
-    return this[aName];
-  });
-}, this);
+Cu.import("resource://gre/modules/Log.jsm");
+// Configure a logger at the parent 'addons' level to format
+// messages for all the modules under addons.*
+const PARENT_LOGGER_ID = "addons";
+let parentLogger = Log.repository.getLogger(PARENT_LOGGER_ID);
+parentLogger.level = Log.Level.Warn;
+let formatter = new Log.BasicFormatter();
+// Set parent logger (and its children) to append to
+// the Javascript section of the Browser Console
+parentLogger.addAppender(new Log.ConsoleAppender(formatter));
+// Set parent logger (and its children) to
+// also append to standard out
+parentLogger.addAppender(new Log.DumpAppender(formatter));
+
+// Create a new logger (child of 'addons' logger)
+// for use by the Addons Manager
+const LOGGER_ID = "addons.manager";
+let logger = Log.repository.getLogger(LOGGER_ID);
+
+// Provide the ability to enable/disable logging
+// messages at runtime.
+// If the "extensions.logging.enabled" preference is
+// missing or 'false', messages at the WARNING and higher
+// severity should be logged to the JS console and standard error.
+// If "extensions.logging.enabled" is set to 'true', messages
+// at DEBUG and higher should go to JS console and standard error.
+const PREF_LOGGING_ENABLED = "extensions.logging.enabled";
+const NS_PREFBRANCH_PREFCHANGE_TOPIC_ID = "nsPref:changed";
+
+/**
+ * Preference listener which listens for a change in the
+ * "extensions.logging.enabled" preference and changes the logging level of the
+ * parent 'addons' level logger accordingly.
+ */
+var PrefObserver = {
+    init: function PrefObserver_init() {
+      Services.prefs.addObserver(PREF_LOGGING_ENABLED, this, false);
+      Services.obs.addObserver(this, "xpcom-shutdown", false);
+      this.observe(null, NS_PREFBRANCH_PREFCHANGE_TOPIC_ID, PREF_LOGGING_ENABLED);
+    },
+
+    observe: function PrefObserver_observe(aSubject, aTopic, aData) {
+      if (aTopic == "xpcom-shutdown") {
+        Services.prefs.removeObserver(PREF_LOGGING_ENABLED, this);
+        Services.obs.removeObserver(this, "xpcom-shutdown");
+      }
+      else if (aTopic == NS_PREFBRANCH_PREFCHANGE_TOPIC_ID) {
+        let debugLogEnabled = false;
+        try {
+          debugLogEnabled = Services.prefs.getBoolPref(PREF_LOGGING_ENABLED);
+        }
+        catch (e) {
+        }
+        if (debugLogEnabled) {
+          parentLogger.level = Log.Level.Debug;
+        }
+        else {
+          parentLogger.level = Log.Level.Warn;
+        }
+      }
+    }
+};
+
+PrefObserver.init();
 
 /**
  * Calls a callback method consuming any thrown exception. Any parameters after
  * the callback parameter will be passed to the callback.
  *
  * @param  aCallback
  *         The callback method to call
  */
 function safeCall(aCallback, ...aArgs) {
   try {
     aCallback.apply(null, aArgs);
   }
   catch (e) {
-    WARN("Exception calling callback", e);
+    logger.warn("Exception calling callback", e);
   }
 }
 
 /**
  * Calls a method on a provider if it exists and consumes any thrown exception.
  * Any parameters after the dflt parameter are passed to the provider's method.
  *
  * @param  aProvider
@@ -125,17 +181,17 @@ function safeCall(aCallback, ...aArgs) {
 function callProvider(aProvider, aMethod, aDefault, ...aArgs) {
   if (!(aMethod in aProvider))
     return aDefault;
 
   try {
     return aProvider[aMethod].apply(aProvider, aArgs);
   }
   catch (e) {
-    ERROR("Exception calling provider " + aMethod, e);
+    logger.error("Exception calling provider " + aMethod, e);
     return aDefault;
   }
 }
 
 /**
  * Gets the currently selected locale for display.
  * @return  the selected locale or "en-US" if none is selected
  */
@@ -491,17 +547,17 @@ var AddonManagerInternal = {
 
       let oldPlatformVersion = null;
       try {
         oldPlatformVersion = Services.prefs.getCharPref(PREF_EM_LAST_PLATFORM_VERSION);
       }
       catch (e) { }
 
       if (appChanged !== false) {
-        LOG("Application has been upgraded");
+        logger.debug("Application has been upgraded");
         Services.prefs.setCharPref(PREF_EM_LAST_APP_VERSION,
                                    Services.appinfo.version);
         Services.prefs.setCharPref(PREF_EM_LAST_PLATFORM_VERSION,
                                    Services.appinfo.platformVersion);
         Services.prefs.setIntPref(PREF_BLOCKLIST_PINGCOUNTVERSION,
                                   (appChanged === undefined ? 0 : -1));
       }
 
@@ -553,17 +609,17 @@ var AddonManagerInternal = {
       // Ensure all default providers have had a chance to register themselves
       if (defaultProvidersEnabled) {
         DEFAULT_PROVIDERS.forEach(function(url) {
           try {
             Components.utils.import(url, {});
           }
           catch (e) {
             AddonManagerPrivate.recordException("AMI", "provider " + url + " load failed", e);
-            ERROR("Exception loading default provider \"" + url + "\"", e);
+            logger.error("Exception loading default provider \"" + url + "\"", e);
           }
         });
       }
 
       // Load any providers registered in the category manager
       let catman = Cc["@mozilla.org/categorymanager;1"].
                    getService(Ci.nsICategoryManager);
       let entries = catman.enumerateCategory(CATEGORY_PROVIDER_MODULE);
@@ -571,17 +627,17 @@ var AddonManagerInternal = {
         let entry = entries.getNext().QueryInterface(Ci.nsISupportsCString).data;
         let url = catman.getCategoryEntry(CATEGORY_PROVIDER_MODULE, entry);
 
         try {
           Components.utils.import(url, {});
         }
         catch (e) {
           AddonManagerPrivate.recordException("AMI", "provider " + url + " load failed", e);
-          ERROR("Exception loading provider " + entry + " from category \"" +
+          logger.error("Exception loading provider " + entry + " from category \"" +
                 url + "\"", e);
         }
       }
 
       // Register our shutdown handler with the AsyncShutdown manager
       AsyncShutdown.profileBeforeChange.addBlocker("AddonManager: shutting down providers",
                                                    this.shutdown.bind(this));
 
@@ -596,17 +652,17 @@ var AddonManagerInternal = {
         for (let type in this.startupChanges)
           delete this.startupChanges[type];
       }
 
       gStartupComplete = true;
       this.recordTimestamp("AMI_startup_end");
     }
     catch (e) {
-      ERROR("startup failed", e);
+      logger.error("startup failed", e);
       AddonManagerPrivate.recordException("AMI", "startup failed", e);
     }
   },
 
   /**
    * Registers a new AddonProvider.
    *
    * @param  aProvider
@@ -624,17 +680,17 @@ var AddonManagerInternal = {
                                  Cr.NS_ERROR_INVALID_ARG);
 
     this.providers.push(aProvider);
 
     if (aTypes) {
       aTypes.forEach(function(aType) {
         if (!(aType.id in this.types)) {
           if (!VALID_TYPES_REGEXP.test(aType.id)) {
-            WARN("Ignoring invalid type " + aType.id);
+            logger.warn("Ignoring invalid type " + aType.id);
             return;
           }
 
           this.types[aType.id] = {
             type: aType,
             providers: [aProvider]
           };
 
@@ -711,17 +767,17 @@ var AddonManagerInternal = {
 
     let providers = this.providers.slice(0);
     for (let provider of providers) {
       try {
         if (aMethod in provider)
           provider[aMethod].apply(provider, aArgs);
       }
       catch (e) {
-        ERROR("Exception calling provider " + aMethod, e);
+        logger.error("Exception calling provider " + aMethod, e);
       }
     }
   },
 
   /**
    * Calls a method on all registered providers, if the provider implements
    * the method. The called method is expected to return a promise, and
    * callProvidersAsync returns a promise that resolves when every provider
@@ -746,60 +802,60 @@ var AddonManagerInternal = {
         if (aMethod in provider) {
           // Resolve a new promise with the result of the method, to handle both
           // methods that return values (or nothing) and methods that return promises.
           let providerResult = provider[aMethod].apply(provider, aArgs);
           let nextPromise = Promise.resolve(providerResult);
           // Log and swallow the errors from methods that do return promises.
           nextPromise = nextPromise.then(
               null,
-              e => ERROR("Exception calling provider " + aMethod, e));
+              e => logger.error("Exception calling provider " + aMethod, e));
           allProviders.push(nextPromise);
         }
       }
       catch (e) {
-        ERROR("Exception calling provider " + aMethod, e);
+        logger.error("Exception calling provider " + aMethod, e);
       }
     }
     // Because we use promise.then to catch and log all errors above, Promise.all()
     // will never exit early because of a rejection.
     return Promise.all(allProviders);
   },
 
   /**
    * Shuts down the addon manager and all registered providers, this must clean
    * up everything in order for automated tests to fake restarts.
    * @return Promise{null} that resolves when all providers and dependent modules
    *                       have finished shutting down
    */
   shutdown: function AMI_shutdown() {
-    LOG("shutdown");
+    logger.debug("shutdown");
     // Clean up listeners
     Services.prefs.removeObserver(PREF_EM_CHECK_COMPATIBILITY, this);
     Services.prefs.removeObserver(PREF_EM_STRICT_COMPATIBILITY, this);
     Services.prefs.removeObserver(PREF_EM_CHECK_UPDATE_SECURITY, this);
     Services.prefs.removeObserver(PREF_EM_UPDATE_ENABLED, this);
     Services.prefs.removeObserver(PREF_EM_AUTOUPDATE_DEFAULT, this);
     Services.prefs.removeObserver(PREF_EM_HOTFIX_ID, this);
 
     // Only shut down providers if they've been started. Shut down
     // AddonRepository after providers (if any).
     let shuttingDown = null;
     if (gStarted) {
       shuttingDown = this.callProvidersAsync("shutdown")
         .then(null,
-              err => ERROR("Failure during async provider shutdown", err))
+              err => logger.error("Failure during async provider shutdown", err))
         .then(() => AddonRepository.shutdown());
     }
     else {
       shuttingDown = AddonRepository.shutdown();
     }
 
-    shuttingDown.then(val => LOG("Async provider shutdown done"),
-                      err => ERROR("Failure during AddonRepository shutdown", err))
+      shuttingDown.then(val => logger.debug("Async provider shutdown done"),
+                        err => logger.error("Failure during AddonRepository shutdown", err))
       .then(() => {
         this.managerListeners.splice(0, this.managerListeners.length);
         this.installListeners.splice(0, this.installListeners.length);
         this.addonListeners.splice(0, this.addonListeners.length);
         this.typeListeners.splice(0, this.typeListeners.length);
         for (let type in this.startupChanges)
           delete this.startupChanges[type];
         gStarted = false;
@@ -1083,17 +1139,17 @@ var AddonManagerInternal = {
 
           // If the available version isn't newer than the last installed
           // version then ignore it.
           if (Services.vc.compare(hotfixVersion, update.version) >= 0) {
             notifyComplete();
             return;
           }
 
-          LOG("Downloading hotfix version " + update.version);
+          logger.debug("Downloading hotfix version " + update.version);
           AddonManager.getInstallForURL(update.updateURL,
                                        function BUC_getInstallForURL(aInstall) {
             aInstall.addListener({
               onDownloadEnded: function BUC_onDownloadEnded(aInstall) {
                 try {
                   if (!Services.prefs.getBoolPref(PREF_EM_CERT_CHECKATTRIBUTES))
                     return;
                 }
@@ -1102,17 +1158,17 @@ var AddonManagerInternal = {
                   return;
                 }
 
                 try {
                   CertUtils.validateCert(aInstall.certificate,
                                          CertUtils.readCertPrefs(PREF_EM_HOTFIX_CERTS));
                 }
                 catch (e) {
-                  WARN("The hotfix add-on was not signed by the expected " +
+                  logger.warn("The hotfix add-on was not signed by the expected " +
                        "certificate and so will not be installed.");
                   aInstall.cancel();
                 }
               },
 
               onInstallEnded: function BUC_onInstallEnded(aInstall) {
                 // Remember the last successfully installed version.
                 Services.prefs.setCharPref(PREF_EM_HOTFIX_LASTVERSION,
@@ -1219,17 +1275,17 @@ var AddonManagerInternal = {
 
     let managerListeners = this.managerListeners.slice(0);
     for (let listener of managerListeners) {
       try {
         if (aMethod in listener)
           listener[aMethod].apply(listener, aArgs);
       }
       catch (e) {
-        WARN("AddonManagerListener threw exception when calling " + aMethod, e);
+        logger.warn("AddonManagerListener threw exception when calling " + aMethod, e);
       }
     }
   },
 
   /**
    * Calls all registered InstallListeners with an event. Any parameters after
    * the extraListeners parameter are passed to the listener.
    *
@@ -1263,17 +1319,17 @@ var AddonManagerInternal = {
     for (let listener of listeners) {
       try {
         if (aMethod in listener) {
           if (listener[aMethod].apply(listener, aArgs) === false)
             result = false;
         }
       }
       catch (e) {
-        WARN("InstallListener threw exception when calling " + aMethod, e);
+        logger.warn("InstallListener threw exception when calling " + aMethod, e);
       }
     }
     return result;
   },
 
   /**
    * Calls all registered AddonListeners with an event. Any parameters after
    * the method parameter are passed to the listener.
@@ -1292,17 +1348,17 @@ var AddonManagerInternal = {
 
     let addonListeners = this.addonListeners.slice(0);
     for (let listener of addonListeners) {
       try {
         if (aMethod in listener)
           listener[aMethod].apply(listener, aArgs);
       }
       catch (e) {
-        WARN("AddonListener threw exception when calling " + aMethod, e);
+        logger.warn("AddonListener threw exception when calling " + aMethod, e);
       }
     }
   },
 
   /**
    * Notifies all providers that an add-on has been enabled when that type of
    * add-on only supports a single add-on being enabled at a time. This allows
    * the providers to disable theirs if necessary.
@@ -1671,17 +1727,17 @@ var AddonManagerInternal = {
       throw Components.Exception("aURI must be a nsIURI or null",
                                  Cr.NS_ERROR_INVALID_ARG);
 
     if (!Array.isArray(aInstalls))
       throw Components.Exception("aInstalls must be an array",
                                  Cr.NS_ERROR_INVALID_ARG);
 
     if (!("@mozilla.org/addons/web-install-listener;1" in Cc)) {
-      WARN("No web installer available, cancelling all installs");
+      logger.warn("No web installer available, cancelling all installs");
       aInstalls.forEach(function(aInstall) {
         aInstall.cancel();
       });
       return;
     }
 
     try {
       let weblistener = Cc["@mozilla.org/addons/web-install-listener;1"].
@@ -1705,17 +1761,17 @@ var AddonManagerInternal = {
           aInstall.install();
         });
       }
     }
     catch (e) {
       // In the event that the weblistener throws during instantiation or when
       // calling onWebInstallBlocked or onWebInstallRequested all of the
       // installs should get cancelled.
-      WARN("Failure calling web installer", e);
+      logger.warn("Failure calling web installer", e);
       aInstalls.forEach(function(aInstall) {
         aInstall.cancel();
       });
     }
   },
 
   /**
    * Adds a new InstallListener if the listener is not already registered.
--- a/toolkit/mozapps/extensions/DeferredSave.jsm
+++ b/toolkit/mozapps/extensions/DeferredSave.jsm
@@ -14,16 +14,78 @@ Cu.import("resource://gre/modules/Promis
 // Make it possible to mock out timers for testing
 let MakeTimer = () => Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
 
 this.EXPORTED_SYMBOLS = ["DeferredSave"];
 
 // If delay parameter is not provided, default is 50 milliseconds.
 const DEFAULT_SAVE_DELAY_MS = 50;
 
+Cu.import("resource://gre/modules/Log.jsm");
+//Configure a logger at the parent 'DeferredSave' level to format
+//messages for all the modules under DeferredSave.*
+const DEFERREDSAVE_PARENT_LOGGER_ID = "DeferredSave";
+let parentLogger = Log.repository.getLogger(DEFERREDSAVE_PARENT_LOGGER_ID);
+parentLogger.level = Log.Level.Warn;
+let formatter = new Log.BasicFormatter();
+//Set parent logger (and its children) to append to
+//the Javascript section of the Browser Console
+parentLogger.addAppender(new Log.ConsoleAppender(formatter));
+//Set parent logger (and its children) to
+//also append to standard out
+parentLogger.addAppender(new Log.DumpAppender(formatter));
+
+//Provide the ability to enable/disable logging
+//messages at runtime.
+//If the "extensions.logging.enabled" preference is
+//missing or 'false', messages at the WARNING and higher
+//severity should be logged to the JS console and standard error.
+//If "extensions.logging.enabled" is set to 'true', messages
+//at DEBUG and higher should go to JS console and standard error.
+Cu.import("resource://gre/modules/Services.jsm");
+
+const PREF_LOGGING_ENABLED = "extensions.logging.enabled";
+const NS_PREFBRANCH_PREFCHANGE_TOPIC_ID = "nsPref:changed";
+
+/**
+* Preference listener which listens for a change in the
+* "extensions.logging.enabled" preference and changes the logging level of the
+* parent 'addons' level logger accordingly.
+*/
+var PrefObserver = {
+ init: function PrefObserver_init() {
+   Services.prefs.addObserver(PREF_LOGGING_ENABLED, this, false);
+   Services.obs.addObserver(this, "xpcom-shutdown", false);
+   this.observe(null, NS_PREFBRANCH_PREFCHANGE_TOPIC_ID, PREF_LOGGING_ENABLED);
+ },
+
+ observe: function PrefObserver_observe(aSubject, aTopic, aData) {
+   if (aTopic == "xpcom-shutdown") {
+     Services.prefs.removeObserver(PREF_LOGGING_ENABLED, this);
+     Services.obs.removeObserver(this, "xpcom-shutdown");
+   }
+   else if (aTopic == NS_PREFBRANCH_PREFCHANGE_TOPIC_ID) {
+     let debugLogEnabled = false;
+     try {
+       debugLogEnabled = Services.prefs.getBoolPref(PREF_LOGGING_ENABLED);
+     }
+     catch (e) {
+     }
+     if (debugLogEnabled) {
+       parentLogger.level = Log.Level.Debug;
+     }
+     else {
+       parentLogger.level = Log.Level.Warn;
+     }
+   }
+ }
+};
+
+PrefObserver.init();
+
 /**
  * A module to manage deferred, asynchronous writing of data files
  * to disk. Writing is deferred by waiting for a specified delay after
  * a request to save the data, before beginning to write. If more than
  * one save request is received during the delay, all requests are
  * fulfilled by a single write.
  *
  * @constructor
@@ -37,20 +99,21 @@ const DEFAULT_SAVE_DELAY_MS = 50;
  *        If aDataProvider returns a String the data are UTF-8 encoded
  *        and then written to the file.
  * @param [optional] aDelay
  *        The delay in milliseconds between the first saveChanges() call
  *        that marks the data as needing to be saved, and when the DeferredSave
  *        begins writing the data to disk. Default 50 milliseconds.
  */
 this.DeferredSave = function (aPath, aDataProvider, aDelay) {
-  // Set up loggers for this instance of DeferredSave
+  // Create a new logger (child of 'DeferredSave' logger)
+  // for use by this particular instance of DeferredSave object
   let leafName = OS.Path.basename(aPath);
-  Cu.import("resource://gre/modules/addons/AddonLogging.jsm");
-  LogManager.getLogger("DeferredSave/" + leafName, this);
+  let logger_id = DEFERREDSAVE_PARENT_LOGGER_ID + "." + leafName;
+  this.logger = Log.repository.getLogger(logger_id);
 
   // @type {Deferred|null}, null when no data needs to be written
   // @resolves with the result of OS.File.writeAtomic when all writes complete
   // @rejects with the error from OS.File.writeAtomic if the write fails,
   //          or with the error from aDataProvider() if that throws.
   this._pending = null;
 
   // @type {Promise}, completes when the in-progress write (if any) completes,
@@ -100,33 +163,33 @@ this.DeferredSave.prototype = {
   },
 
   // Start the pending timer if data is dirty
   _startTimer: function() {
     if (!this._pending) {
       return;
     }
 
-    this.LOG("Starting timer");
+      this.logger.debug("Starting timer");
     if (!this._timer)
       this._timer = MakeTimer();
     this._timer.initWithCallback(() => this._deferredSave(),
                                  this._delay, Ci.nsITimer.TYPE_ONE_SHOT);
   },
 
   /**
    * Mark the current stored data dirty, and schedule a flush to disk
    * @return A Promise<integer> that will be resolved after the data is written to disk;
    *         the promise is resolved with the number of bytes written.
    */
   saveChanges: function() {
-    this.LOG("Save changes");
+      this.logger.debug("Save changes");
     if (!this._pending) {
       if (this.writeInProgress) {
-        this.LOG("Data changed while write in progress");
+          this.logger.debug("Data changed while write in progress");
         this.overlappedSaves++;
       }
       this._pending = Promise.defer();
       // Wait until the most recent write completes or fails (if it hasn't already)
       // and then restart our timer
       this._writing.then(count => this._startTimer(), error => this._startTimer());
     }
     return this._pending.promise;
@@ -140,42 +203,42 @@ this.DeferredSave.prototype = {
 
     // In either the success or the exception handling case, we don't need to handle
     // the error from _writing here; it's already being handled in another then()
     let toSave = null;
     try {
       toSave = this._dataProvider();
     }
     catch(e) {
-      this.ERROR("Deferred save dataProvider failed", e);
+        this.logger.error("Deferred save dataProvider failed", e);
       writing.then(null, error => {})
         .then(count => {
           pending.reject(e);
         });
       return;
     }
 
     writing.then(null, error => {return 0;})
     .then(count => {
-      this.LOG("Starting write");
+        this.logger.debug("Starting write");
       this.totalSaves++;
       this.writeInProgress = true;
 
       OS.File.writeAtomic(this._path, toSave, {tmpPath: this._path + ".tmp"})
       .then(
         result => {
           this._lastError = null;
           this.writeInProgress = false;
-          this.LOG("Write succeeded");
+              this.logger.debug("Write succeeded");
           pending.resolve(result);
         },
         error => {
           this._lastError = error;
           this.writeInProgress = false;
-          this.WARN("Write failed", error);
+              this.logger.warn("Write failed", error);
           pending.reject(error);
         });
     });
   },
 
   /**
    * Immediately save the dirty data to disk, skipping
    * the delay of normal operation. Note that the write
@@ -193,19 +256,19 @@ this.DeferredSave.prototype = {
    *         written. If all in-memory data is clean, completes with the
    *         result of the most recent write.
    */
   flush: function() {
     // If we have pending changes, cancel our timer and set up the write
     // immediately (_deferredSave queues the write for after the most
     // recent write completes, if it hasn't already)
     if (this._pending) {
-      this.LOG("Flush called while data is dirty");
+        this.logger.debug("Flush called while data is dirty");
       if (this._timer) {
         this._timer.cancel();
         this._timer = null;
       }
       this._deferredSave();
     }
 
     return this._writing;
   }
-}
+};
--- a/toolkit/mozapps/extensions/amWebInstallListener.js
+++ b/toolkit/mozapps/extensions/amWebInstallListener.js
@@ -9,39 +9,38 @@
  * confirmation when all the add-ons have been downloaded.
  */
 
 "use strict";
 
 const Cc = Components.classes;
 const Ci = Components.interfaces;
 const Cr = Components.results;
+const Cu = Components.utils;
 
-Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
-Components.utils.import("resource://gre/modules/AddonManager.jsm");
-Components.utils.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource://gre/modules/AddonManager.jsm");
+Cu.import("resource://gre/modules/Services.jsm");
 
 const URI_XPINSTALL_DIALOG = "chrome://mozapps/content/xpinstall/xpinstallConfirm.xul";
 
 // Installation can begin from any of these states
 const READY_STATES = [
   AddonManager.STATE_AVAILABLE,
   AddonManager.STATE_DOWNLOAD_FAILED,
   AddonManager.STATE_INSTALL_FAILED,
   AddonManager.STATE_CANCELLED
 ];
 
-["LOG", "WARN", "ERROR"].forEach(function(aName) {
-  this.__defineGetter__(aName, function logFuncGetter() {
-    Components.utils.import("resource://gre/modules/addons/AddonLogging.jsm");
+Cu.import("resource://gre/modules/Log.jsm");
+const LOGGER_ID = "addons.weblistener";
 
-    LogManager.getLogger("addons.weblistener", this);
-    return this[aName];
-  });
-}, this);
+// Create a new logger for use by the Addons Web Listener
+// (Requires AddonManager.jsm)
+let logger = Log.repository.getLogger(LOGGER_ID);
 
 function notifyObservers(aTopic, aWindow, aUri, aInstalls) {
   let info = {
     originatingWindow: aWindow,
     originatingURI: aUri,
     installs: aInstalls,
 
     QueryInterface: XPCOMUtils.generateQI([Ci.amIWebInstallInfo])
@@ -124,17 +123,17 @@ Installer.prototype = {
               installs.push(aInstall);
           }, this);
         }
         break;
       case AddonManager.STATE_CANCELLED:
         // Just ignore cancelled downloads
         break;
       default:
-        WARN("Download of " + install.sourceURI.spec + " in unexpected state " +
+        logger.warn("Download of " + install.sourceURI.spec + " in unexpected state " +
              install.state);
       }
     }
 
     this.isDownloading = false;
     this.downloads = installs;
 
     if (failed.length > 0) {
--- a/toolkit/mozapps/extensions/internal/AddonRepository.jsm
+++ b/toolkit/mozapps/extensions/internal/AddonRepository.jsm
@@ -56,24 +56,22 @@ const BLANK_DB = function() {
   return {
     addons: new Map(),
     schema: DB_SCHEMA
   };
 }
 
 const TOOLKIT_ID     = "toolkit@mozilla.org";
 
-["LOG", "WARN", "ERROR"].forEach(function(aName) {
-  this.__defineGetter__(aName, function logFuncGetter() {
-    Components.utils.import("resource://gre/modules/addons/AddonLogging.jsm");
+Cu.import("resource://gre/modules/Log.jsm");
+const LOGGER_ID = "addons.repository";
 
-    LogManager.getLogger("addons.repository", this);
-    return this[aName];
-  });
-}, this);
+// Create a new logger for use by the Addons Repository
+// (Requires AddonManager.jsm)
+let logger = Log.repository.getLogger(LOGGER_ID);
 
 // A map between XML keys to AddonSearchResult keys for string values
 // that require no extra parsing from XML
 const STRING_KEY_MAP = {
   name:               "name",
   version:            "version",
   homepage:           "homepageURL",
   support:            "supportURL"
@@ -440,17 +438,17 @@ AddonSearchResult.prototype = {
           case "updateDate":
             json.updateDate = value ? value.getTime() : "";
             break;
 
           default:
             json[property] = value;
         }
       } catch (ex) {
-        WARN("Error writing property value for " + property);
+        logger.warn("Error writing property value for " + property);
       }
     }
 
     for (let [property, value] of Iterator(this._unsupportedProperties)) {
       if (!property.startsWith("_"))
         json[property] = value;
     }
 
@@ -479,17 +477,17 @@ this.AddonRepository = {
     if (!AddonDatabase.databaseOk)
       return false;
 
     let preference = PREF_GETADDONS_CACHE_ENABLED;
     let enabled = false;
     try {
       enabled = Services.prefs.getBoolPref(preference);
     } catch(e) {
-      WARN("cacheEnabled: Couldn't get pref: " + preference);
+      logger.warn("cacheEnabled: Couldn't get pref: " + preference);
     }
 
     return enabled;
   },
 
   // A cache of the add-ons stored in the database
   _addons: null,
 
@@ -634,17 +632,17 @@ this.AddonRepository = {
 
       self._beginGetAddons(aAddons, {
         searchSucceeded: function repopulateCacheInternal_searchSucceeded(aAddons) {
           self._addons = {};
           aAddons.forEach(function(aAddon) { self._addons[aAddon.id] = aAddon; });
           AddonDatabase.repopulate(aAddons, aCallback);
         },
         searchFailed: function repopulateCacheInternal_searchFailed() {
-          WARN("Search failed when repopulating cache");
+          logger.warn("Search failed when repopulating cache");
           if (aCallback)
             aCallback();
         }
       }, aSendPerformance, aTimeout);
     });
   },
 
   /**
@@ -674,17 +672,17 @@ this.AddonRepository = {
       }
 
       self.getAddonsByIDs(aAddons, {
         searchSucceeded: function cacheAddons_searchSucceeded(aAddons) {
           aAddons.forEach(function(aAddon) { self._addons[aAddon.id] = aAddon; });
           AddonDatabase.insertAddons(aAddons, aCallback);
         },
         searchFailed: function cacheAddons_searchFailed() {
-          WARN("Search failed when adding add-ons to cache");
+          logger.warn("Search failed when adding add-ons to cache");
           if (aCallback)
             aCallback();
         }
       });
     });
   },
 
   /**
@@ -1062,17 +1060,17 @@ this.AddonRepository = {
               break;
             case 2:
               addon.type = "theme";
               break;
             case 3:
               addon.type = "dictionary";
               break;
             default:
-              WARN("Unknown type id when parsing addon: " + id);
+              logger.warn("Unknown type id when parsing addon: " + id);
           }
           break;
         case "authors":
           let authorNodes = node.getElementsByTagName("author");
           for (let authorNode of authorNodes) {
             let name = self._getDescendantTextContent(authorNode, "name");
             let link = self._getDescendantTextContent(authorNode, "link");
             if (name == null || link == null)
@@ -1307,17 +1305,17 @@ this.AddonRepository = {
       }
     });
   },
 
   // Parses addon_compatibility nodes, that describe compatibility overrides.
   _parseAddonCompatElement: function AddonRepo_parseAddonCompatElement(aResultObj, aElement) {
     let guid = this._getDescendantTextContent(aElement, "guid");
     if (!guid) {
-        LOG("Compatibility override is missing guid.");
+        logger.debug("Compatibility override is missing guid.");
       return;
     }
 
     let compat = {id: guid};
     compat.hosted = aElement.getAttribute("hosted") != "false";
 
     function findMatchingAppRange(aNodes) {
       let toolkitAppRange = null;
@@ -1343,38 +1341,38 @@ this.AddonRepository = {
       }
       return toolkitAppRange;
     }
 
     function parseRangeNode(aNode) {
       let type = aNode.getAttribute("type");
       // Only "incompatible" (blacklisting) is supported for now.
       if (type != "incompatible") {
-        LOG("Compatibility override of unsupported type found.");
+        logger.debug("Compatibility override of unsupported type found.");
         return null;
       }
 
       let override = new AddonManagerPrivate.AddonCompatibilityOverride(type);
 
       override.minVersion = this._getDirectDescendantTextContent(aNode, "min_version");
       override.maxVersion = this._getDirectDescendantTextContent(aNode, "max_version");
 
       if (!override.minVersion) {
-        LOG("Compatibility override is missing min_version.");
+        logger.debug("Compatibility override is missing min_version.");
         return null;
       }
       if (!override.maxVersion) {
-        LOG("Compatibility override is missing max_version.");
+        logger.debug("Compatibility override is missing max_version.");
         return null;
       }
 
       let appRanges = aNode.querySelectorAll("compatible_applications > application");
       let appRange = findMatchingAppRange.bind(this)(appRanges);
       if (!appRange) {
-        LOG("Compatibility override is missing a valid application range.");
+        logger.debug("Compatibility override is missing a valid application range.");
         return null;
       }
 
       override.appID = appRange.appID;
       override.appMinVersion = appRange.appMinVersion;
       override.appMaxVersion = appRange.appMaxVersion;
 
       return override;
@@ -1402,17 +1400,17 @@ this.AddonRepository = {
       aCallback.searchFailed();
       return;
     }
 
     this._searching = true;
     this._callback = aCallback;
     this._maxResults = aMaxResults;
 
-    LOG("Requesting " + aURI);
+    logger.debug("Requesting " + aURI);
 
     this._request = new XHRequest();
     this._request.mozBackgroundRequest = true;
     this._request.open("GET", aURI, true);
     this._request.overrideMimeType("text/xml");
     if (aTimeout) {
       this._request.timeout = aTimeout;
     }
@@ -1470,17 +1468,17 @@ this.AddonRepository = {
   },
 
   // Create url from preference, returning null if preference does not exist
   _formatURLPref: function AddonRepo_formatURLPref(aPreference, aSubstitutions) {
     let url = null;
     try {
       url = Services.prefs.getCharPref(aPreference);
     } catch(e) {
-      WARN("_formatURLPref: Couldn't get pref: " + aPreference);
+      logger.warn("_formatURLPref: Couldn't get pref: " + aPreference);
       return null;
     }
 
     url = url.replace(/%([A-Z_]+)%/g, function urlSubstitution(aMatch, aKey) {
       return (aKey in aSubstitutions) ? aSubstitutions[aKey] : aMatch;
     });
 
     return Services.urlFormatter.formatURL(url);
@@ -1570,17 +1568,17 @@ var AddonDatabase = {
      schema = parseInt(inputDB.schema, 10);
 
      if (!Number.isInteger(schema) ||
          schema < DB_MIN_JSON_SCHEMA) {
        throw new Error("Invalid schema value.");
      }
 
     } catch (e if e.result == Cr.NS_ERROR_FILE_NOT_FOUND) {
-      LOG("No " + FILE_DATABASE + " found.");
+      logger.debug("No " + FILE_DATABASE + " found.");
 
       // Create a blank addons.json file
       this._saveDBToDisk();
 
       let dbSchema = 0;
       try {
         dbSchema = Services.prefs.getIntPref(PREF_GETADDONS_DB_SCHEMA);
       } catch (e) {}
@@ -1599,17 +1597,17 @@ var AddonDatabase = {
         });
 
         Services.prefs.setIntPref(PREF_GETADDONS_DB_SCHEMA, DB_SCHEMA);
       }
 
       return;
 
     } catch (e) {
-      ERROR("Malformed " + FILE_DATABASE + ": " + e);
+      logger.error("Malformed " + FILE_DATABASE + ": " + e);
       this.databaseOk = false;
       return;
 
     } finally {
      cstream.close();
      fstream.close();
     }
 
@@ -1670,17 +1668,17 @@ var AddonDatabase = {
   delete: function AD_delete(aCallback) {
     this.DB = BLANK_DB();
 
     this.Writer.flush()
       .then(null, () => {})
       // shutdown(true) never rejects
       .then(() => this.shutdown(true))
       .then(() => OS.File.remove(this.jsonFile.path, {}))
-      .then(null, error => ERROR("Unable to delete Addon Repository file " +
+      .then(null, error => logger.error("Unable to delete Addon Repository file " +
                                  this.jsonFile.path, error))
       .then(aCallback);
   },
 
   toJSON: function AD_toJSON() {
     let json = {
       schema: this.DB.schema,
       addons: []
@@ -1861,17 +1859,17 @@ var AddonDatabase = {
 
           case "iconURL":
             break;
 
           default:
             addon[expectedProperty] = value;
         }
       } catch (ex) {
-        WARN("Error in parsing property value for " + expectedProperty + " | " + ex);
+        logger.warn("Error in parsing property value for " + expectedProperty + " | " + ex);
       }
 
       // delete property from obj to indicate we've already
       // handled it. The remaining public properties will
       // be stored separately and just passed through to
       // be written back to the DB.
       delete aObj[expectedProperty];
     }
@@ -1905,17 +1903,17 @@ var AddonDatabase = {
    * the DB_BATCH_TIMEOUT_MS timeout.
    *
    * @return Promise A promise that resolves after the
    *                 write to disk has completed.
    */
   _saveDBToDisk: function() {
     return this.Writer.saveChanges().then(
       function() Services.obs.notifyObservers(null, DB_DATA_WRITTEN_TOPIC, null),
-      ERROR);
+      logger.error);
   },
 
   /**
    * Make a developer object from a vanilla
    * JS object from the JSON database
    *
    * @param  aObj
    *         The JS object to use
--- a/toolkit/mozapps/extensions/internal/AddonRepository_SQLiteMigrator.jsm
+++ b/toolkit/mozapps/extensions/internal/AddonRepository_SQLiteMigrator.jsm
@@ -1,44 +1,41 @@
 /* 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/. */
 
 "use strict";
 
 const Cc = Components.classes;
 const Ci = Components.interfaces;
+const Cu = Components.utils;
 
-Components.utils.import("resource://gre/modules/Services.jsm");
-Components.utils.import("resource://gre/modules/AddonManager.jsm");
-Components.utils.import("resource://gre/modules/FileUtils.jsm");
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/AddonManager.jsm");
+Cu.import("resource://gre/modules/FileUtils.jsm");
 
 const KEY_PROFILEDIR  = "ProfD";
 const FILE_DATABASE   = "addons.sqlite";
 const LAST_DB_SCHEMA   = 4;
 
 // Add-on properties present in the columns of the database
 const PROP_SINGLE = ["id", "type", "name", "version", "creator", "description",
                      "fullDescription", "developerComments", "eula",
                      "homepageURL", "supportURL", "contributionURL",
                      "contributionAmount", "averageRating", "reviewCount",
                      "reviewURL", "totalDownloads", "weeklyDownloads",
                      "dailyUsers", "sourceURI", "repositoryStatus", "size",
                      "updateDate"];
 
-
-["LOG", "WARN", "ERROR"].forEach(function(aName) {
-  this.__defineGetter__(aName, function logFuncGetter() {
-    Components.utils.import("resource://gre/modules/addons/AddonLogging.jsm");
+Cu.import("resource://gre/modules/Log.jsm");
+const LOGGER_ID = "addons.repository.sqlmigrator";
 
-    LogManager.getLogger("addons.repository.sqlmigrator", this);
-    return this[aName];
-  });
-}, this);
-
+// Create a new logger for use by the Addons Repository SQL Migrator
+// (Requires AddonManager.jsm)
+let logger = Log.repository.getLogger(LOGGER_ID);
 
 this.EXPORTED_SYMBOLS = ["AddonRepository_SQLiteMigrator"];
 
 
 this.AddonRepository_SQLiteMigrator = {
 
   /**
    * Migrates data from a previous SQLite version of the
@@ -54,22 +51,22 @@ this.AddonRepository_SQLiteMigrator = {
    */
   migrate: function(aCallback) {
     if (!this._openConnection()) {
       this._closeConnection();
       aCallback([]);
       return false;
     }
 
-    LOG("Importing addon repository from previous " + FILE_DATABASE + " storage.");
+    logger.debug("Importing addon repository from previous " + FILE_DATABASE + " storage.");
 
     this._retrieveStoredData((results) => {
       this._closeConnection();
       let resultArray = [addon for ([,addon] of Iterator(results))];
-      LOG(resultArray.length + " addons imported.")
+      logger.debug(resultArray.length + " addons imported.")
       aCallback(resultArray);
     });
 
     return true;
   },
 
   /**
    * Synchronously opens a new connection to the database file.
@@ -95,51 +92,51 @@ this.AddonRepository_SQLiteMigrator = {
     try {
       this.connection.beginTransaction();
 
       switch (this.connection.schemaVersion) {
         case 0:
           return false;
 
         case 1:
-          LOG("Upgrading database schema to version 2");
+          logger.debug("Upgrading database schema to version 2");
           this.connection.executeSimpleSQL("ALTER TABLE screenshot ADD COLUMN width INTEGER");
           this.connection.executeSimpleSQL("ALTER TABLE screenshot ADD COLUMN height INTEGER");
           this.connection.executeSimpleSQL("ALTER TABLE screenshot ADD COLUMN thumbnailWidth INTEGER");
           this.connection.executeSimpleSQL("ALTER TABLE screenshot ADD COLUMN thumbnailHeight INTEGER");
         case 2:
-          LOG("Upgrading database schema to version 3");
+          logger.debug("Upgrading database schema to version 3");
           this.connection.createTable("compatibility_override",
                                       "addon_internal_id INTEGER, " +
                                       "num INTEGER, " +
                                       "type TEXT, " +
                                       "minVersion TEXT, " +
                                       "maxVersion TEXT, " +
                                       "appID TEXT, " +
                                       "appMinVersion TEXT, " +
                                       "appMaxVersion TEXT, " +
                                       "PRIMARY KEY (addon_internal_id, num)");
         case 3:
-          LOG("Upgrading database schema to version 4");
+          logger.debug("Upgrading database schema to version 4");
           this.connection.createTable("icon",
                                       "addon_internal_id INTEGER, " +
                                       "size INTEGER, " +
                                       "url TEXT, " +
                                       "PRIMARY KEY (addon_internal_id, size)");
           this._createIndices();
           this._createTriggers();
           this.connection.schemaVersion = LAST_DB_SCHEMA;
         case LAST_DB_SCHEMA:
           break;
         default:
           return false;
       }
       this.connection.commitTransaction();
     } catch (e) {
-      ERROR("Failed to open " + FILE_DATABASE + ". Data import will not happen.", e);
+      logger.error("Failed to open " + FILE_DATABASE + ". Data import will not happen.", e);
       this.logSQLError(this.connection.lastError, this.connection.lastErrorString);
       this.connection.rollbackTransaction();
       return false;
     }
 
     return true;
   },
 
@@ -175,17 +172,17 @@ this.AddonRepository_SQLiteMigrator = {
             addons[internal_id] = self._makeAddonFromAsyncRow(row);
           }
         },
 
         handleError: self.asyncErrorLogger,
 
         handleCompletion: function getAllAddons_handleCompletion(aReason) {
           if (aReason != Ci.mozIStorageStatementCallback.REASON_FINISHED) {
-            ERROR("Error retrieving add-ons from database. Returning empty results");
+            logger.error("Error retrieving add-ons from database. Returning empty results");
             aCallback({});
             return;
           }
 
           getAllDevelopers();
         }
       });
     }
@@ -193,33 +190,33 @@ this.AddonRepository_SQLiteMigrator = {
     // Retrieve all data from the developer table
     function getAllDevelopers() {
       self.getAsyncStatement("getAllDevelopers").executeAsync({
         handleResult: function getAllDevelopers_handleResult(aResults) {
           let row = null;
           while ((row = aResults.getNextRow())) {
             let addon_internal_id = row.getResultByName("addon_internal_id");
             if (!(addon_internal_id in addons)) {
-              WARN("Found a developer not linked to an add-on in database");
+              logger.warn("Found a developer not linked to an add-on in database");
               continue;
             }
 
             let addon = addons[addon_internal_id];
             if (!addon.developers)
               addon.developers = [];
 
             addon.developers.push(self._makeDeveloperFromAsyncRow(row));
           }
         },
 
         handleError: self.asyncErrorLogger,
 
         handleCompletion: function getAllDevelopers_handleCompletion(aReason) {
           if (aReason != Ci.mozIStorageStatementCallback.REASON_FINISHED) {
-            ERROR("Error retrieving developers from database. Returning empty results");
+            logger.error("Error retrieving developers from database. Returning empty results");
             aCallback({});
             return;
           }
 
           getAllScreenshots();
         }
       });
     }
@@ -227,97 +224,97 @@ this.AddonRepository_SQLiteMigrator = {
     // Retrieve all data from the screenshot table
     function getAllScreenshots() {
       self.getAsyncStatement("getAllScreenshots").executeAsync({
         handleResult: function getAllScreenshots_handleResult(aResults) {
           let row = null;
           while ((row = aResults.getNextRow())) {
             let addon_internal_id = row.getResultByName("addon_internal_id");
             if (!(addon_internal_id in addons)) {
-              WARN("Found a screenshot not linked to an add-on in database");
+              logger.warn("Found a screenshot not linked to an add-on in database");
               continue;
             }
 
             let addon = addons[addon_internal_id];
             if (!addon.screenshots)
               addon.screenshots = [];
             addon.screenshots.push(self._makeScreenshotFromAsyncRow(row));
           }
         },
 
         handleError: self.asyncErrorLogger,
 
         handleCompletion: function getAllScreenshots_handleCompletion(aReason) {
           if (aReason != Ci.mozIStorageStatementCallback.REASON_FINISHED) {
-            ERROR("Error retrieving screenshots from database. Returning empty results");
+            logger.error("Error retrieving screenshots from database. Returning empty results");
             aCallback({});
             return;
           }
 
           getAllCompatOverrides();
         }
       });
     }
 
     function getAllCompatOverrides() {
       self.getAsyncStatement("getAllCompatOverrides").executeAsync({
         handleResult: function getAllCompatOverrides_handleResult(aResults) {
           let row = null;
           while ((row = aResults.getNextRow())) {
             let addon_internal_id = row.getResultByName("addon_internal_id");
             if (!(addon_internal_id in addons)) {
-              WARN("Found a compatibility override not linked to an add-on in database");
+              logger.warn("Found a compatibility override not linked to an add-on in database");
               continue;
             }
 
             let addon = addons[addon_internal_id];
             if (!addon.compatibilityOverrides)
               addon.compatibilityOverrides = [];
             addon.compatibilityOverrides.push(self._makeCompatOverrideFromAsyncRow(row));
           }
         },
 
         handleError: self.asyncErrorLogger,
 
         handleCompletion: function getAllCompatOverrides_handleCompletion(aReason) {
           if (aReason != Ci.mozIStorageStatementCallback.REASON_FINISHED) {
-            ERROR("Error retrieving compatibility overrides from database. Returning empty results");
+            logger.error("Error retrieving compatibility overrides from database. Returning empty results");
             aCallback({});
             return;
           }
 
           getAllIcons();
         }
       });
     }
 
     function getAllIcons() {
       self.getAsyncStatement("getAllIcons").executeAsync({
         handleResult: function getAllIcons_handleResult(aResults) {
           let row = null;
           while ((row = aResults.getNextRow())) {
             let addon_internal_id = row.getResultByName("addon_internal_id");
             if (!(addon_internal_id in addons)) {
-              WARN("Found an icon not linked to an add-on in database");
+              logger.warn("Found an icon not linked to an add-on in database");
               continue;
             }
 
             let addon = addons[addon_internal_id];
             let { size, url } = self._makeIconFromAsyncRow(row);
             addon.icons[size] = url;
             if (size == 32)
               addon.iconURL = url;
           }
         },
 
         handleError: self.asyncErrorLogger,
 
         handleCompletion: function getAllIcons_handleCompletion(aReason) {
           if (aReason != Ci.mozIStorageStatementCallback.REASON_FINISHED) {
-            ERROR("Error retrieving icons from database. Returning empty results");
+            logger.error("Error retrieving icons from database. Returning empty results");
             aCallback({});
             return;
           }
 
           let returnedAddons = {};
           for each (let addon in addons)
             returnedAddons[addon.id] = addon;
           aCallback(returnedAddons);
@@ -344,17 +341,17 @@ this.AddonRepository_SQLiteMigrator = {
   getAsyncStatement: function AD_getAsyncStatement(aKey) {
     if (aKey in this.asyncStatementsCache)
       return this.asyncStatementsCache[aKey];
 
     let sql = this.queries[aKey];
     try {
       return this.asyncStatementsCache[aKey] = this.connection.createAsyncStatement(sql);
     } catch (e) {
-      ERROR("Error creating statement " + aKey + " (" + sql + ")");
+      logger.error("Error creating statement " + aKey + " (" + sql + ")");
       throw Components.Exception("Error creating statement " + aKey + " (" + sql + "): " + e,
                                  e.result);
     }
   },
 
   // The queries used by the database
   queries: {
     getAllAddons: "SELECT internal_id, id, type, name, version, " +
@@ -473,27 +470,27 @@ this.AddonRepository_SQLiteMigrator = {
    * A helper function to log an SQL error.
    *
    * @param  aError
    *         The storage error code associated with the error
    * @param  aErrorString
    *         An error message
    */
   logSQLError: function AD_logSQLError(aError, aErrorString) {
-    ERROR("SQL error " + aError + ": " + aErrorString);
+    logger.error("SQL error " + aError + ": " + aErrorString);
   },
 
   /**
    * A helper function to log any errors that occur during async statements.
    *
    * @param  aError
    *         A mozIStorageError to log
    */
   asyncErrorLogger: function AD_asyncErrorLogger(aError) {
-    ERROR("Async SQL error " + aError.result + ": " + aError.message);
+    logger.error("Async SQL error " + aError.result + ": " + aError.message);
   },
 
   /**
    * Synchronously creates the triggers in the database.
    */
   _createTriggers: function AD__createTriggers() {
     this.connection.executeSimpleSQL("DROP TRIGGER IF EXISTS delete_addon");
     this.connection.executeSimpleSQL("CREATE TRIGGER delete_addon AFTER DELETE " +
--- a/toolkit/mozapps/extensions/internal/AddonUpdateChecker.jsm
+++ b/toolkit/mozapps/extensions/internal/AddonUpdateChecker.jsm
@@ -39,25 +39,22 @@ XPCOMUtils.defineLazyGetter(this, "CertU
   let certUtils = {};
   Components.utils.import("resource://gre/modules/CertUtils.jsm", certUtils);
   return certUtils;
 });
 
 var gRDF = Cc["@mozilla.org/rdf/rdf-service;1"].
            getService(Ci.nsIRDFService);
 
-["LOG", "WARN", "ERROR"].forEach(function(aName) {
-  this.__defineGetter__(aName, function logFuncGetter() {
-    Components.utils.import("resource://gre/modules/addons/AddonLogging.jsm");
+Cu.import("resource://gre/modules/Log.jsm");
+const LOGGER_ID = "addons.updates";
 
-    LogManager.getLogger("addons.updates", this);
-    return this[aName];
-  });
-}, this);
-
+// Create a new logger for use by the Addons Update Checker
+// (Requires AddonManager.jsm)
+let logger = Log.repository.getLogger(LOGGER_ID);
 
 /**
  * A serialisation method for RDF data that produces an identical string
  * for matching RDF graphs.
  * The serialisation is not complete, only assertions stemming from a given
  * resource are included, multiple references to the same resource are not
  * permitted, and the RDF prolog and epilog are not included.
  * RDF Blob and Date literals are not supported.
@@ -304,17 +301,17 @@ function parseRDFManifest(aId, aUpdateKe
       throw Components.Exception("The signature for " + aId + " was not created by the add-on's updateKey");
   }
 
   let updates = ds.GetTarget(addonRes, EM_R("updates"), true);
 
   // A missing updates property doesn't count as a failure, just as no avialable
   // update information
   if (!updates) {
-    WARN("Update manifest for " + aId + " did not contain an updates property");
+    logger.warn("Update manifest for " + aId + " did not contain an updates property");
     return [];
   }
 
   if (!(updates instanceof Ci.nsIRDFResource))
     throw Components.Exception("Missing updates property for " + addonRes.Value);
 
   let cu = Cc["@mozilla.org/rdf/container-utils;1"].
            getService(Ci.nsIRDFContainerUtils);
@@ -325,51 +322,51 @@ function parseRDFManifest(aId, aUpdateKe
   let ctr = Cc["@mozilla.org/rdf/container;1"].
             createInstance(Ci.nsIRDFContainer);
   ctr.Init(ds, updates);
   let items = ctr.GetElements();
   while (items.hasMoreElements()) {
     let item = items.getNext().QueryInterface(Ci.nsIRDFResource);
     let version = getProperty(ds, item, "version");
     if (!version) {
-      WARN("Update manifest is missing a required version property.");
+      logger.warn("Update manifest is missing a required version property.");
       continue;
     }
 
-    LOG("Found an update entry for " + aId + " version " + version);
+    logger.debug("Found an update entry for " + aId + " version " + version);
 
     let targetApps = ds.GetTargets(item, EM_R("targetApplication"), true);
     while (targetApps.hasMoreElements()) {
       let targetApp = targetApps.getNext().QueryInterface(Ci.nsIRDFResource);
 
       let appEntry = {};
       try {
         appEntry.id = getRequiredProperty(ds, targetApp, "id");
         appEntry.minVersion = getRequiredProperty(ds, targetApp, "minVersion");
         appEntry.maxVersion = getRequiredProperty(ds, targetApp, "maxVersion");
       }
       catch (e) {
-        WARN(e);
+        logger.warn(e);
         continue;
       }
 
       let result = {
         id: aId,
         version: version,
         updateURL: getProperty(ds, targetApp, "updateLink"),
         updateHash: getProperty(ds, targetApp, "updateHash"),
         updateInfoURL: getProperty(ds, targetApp, "updateInfoURL"),
         strictCompatibility: getProperty(ds, targetApp, "strictCompatibility") == "true",
         targetApplications: [appEntry]
       };
 
       if (result.updateURL && AddonManager.checkUpdateSecurity &&
           result.updateURL.substring(0, 6) != "https:" &&
           (!result.updateHash || result.updateHash.substring(0, 3) != "sha")) {
-        WARN("updateLink " + result.updateURL + " is not secure and is not verified" +
+        logger.warn("updateLink " + result.updateURL + " is not secure and is not verified" +
              " by a strong enough hash (needs to be sha1 or stronger).");
         delete result.updateURL;
         delete result.updateHash;
       }
       results.push(result);
     }
   }
   return results;
@@ -396,17 +393,17 @@ function UpdateParser(aId, aUpdateKey, a
 
   let requireBuiltIn = true;
   try {
     requireBuiltIn = Services.prefs.getBoolPref(PREF_UPDATE_REQUIREBUILTINCERTS);
   }
   catch (e) {
   }
 
-  LOG("Requesting " + aUrl);
+  logger.debug("Requesting " + aUrl);
   try {
     this.request = Cc["@mozilla.org/xmlextras/xmlhttprequest;1"].
                    createInstance(Ci.nsIXMLHttpRequest);
     this.request.open("GET", this.url, true);
     this.request.channel.notificationCallbacks = new CertUtils.BadCertHandler(!requireBuiltIn);
     this.request.channel.loadFlags |= Ci.nsIRequest.LOAD_BYPASS_CACHE;
     // Prevent the request from writing to cache.
     this.request.channel.loadFlags |= Ci.nsIRequest.INHIBIT_CACHING;
@@ -414,17 +411,17 @@ function UpdateParser(aId, aUpdateKey, a
     this.request.timeout = TIMEOUT;
     var self = this;
     this.request.addEventListener("load", function loadEventListener(event) { self.onLoad() }, false);
     this.request.addEventListener("error", function errorEventListener(event) { self.onError() }, false);
     this.request.addEventListener("timeout", function timeoutEventListener(event) { self.onTimeout() }, false);
     this.request.send(null);
   }
   catch (e) {
-    ERROR("Failed to request update manifest", e);
+    logger.error("Failed to request update manifest", e);
   }
 }
 
 UpdateParser.prototype = {
   id: null,
   updateKey: null,
   observer: null,
   request: null,
@@ -448,110 +445,110 @@ UpdateParser.prototype = {
       CertUtils.checkCert(request.channel, !requireBuiltIn);
     }
     catch (e) {
       this.notifyError(AddonUpdateChecker.ERROR_DOWNLOAD_ERROR);
       return;
     }
 
     if (!Components.isSuccessCode(request.status)) {
-      WARN("Request failed: " + this.url + " - " + request.status);
+      logger.warn("Request failed: " + this.url + " - " + request.status);
       this.notifyError(AddonUpdateChecker.ERROR_DOWNLOAD_ERROR);
       return;
     }
 
     let channel = request.channel;
     if (channel instanceof Ci.nsIHttpChannel && !channel.requestSucceeded) {
-      WARN("Request failed: " + this.url + " - " + channel.responseStatus +
+      logger.warn("Request failed: " + this.url + " - " + channel.responseStatus +
            ": " + channel.responseStatusText);
       this.notifyError(AddonUpdateChecker.ERROR_DOWNLOAD_ERROR);
       return;
     }
 
     let xml = request.responseXML;
     if (!xml || xml.documentElement.namespaceURI == XMLURI_PARSE_ERROR) {
-      WARN("Update manifest was not valid XML");
+      logger.warn("Update manifest was not valid XML");
       this.notifyError(AddonUpdateChecker.ERROR_PARSE_ERROR);
       return;
     }
 
     // We currently only know about RDF update manifests
     if (xml.documentElement.namespaceURI == PREFIX_NS_RDF) {
       let results = null;
 
       try {
         results = parseRDFManifest(this.id, this.updateKey, request);
       }
       catch (e) {
-        WARN(e);
+        logger.warn(e);
         this.notifyError(AddonUpdateChecker.ERROR_PARSE_ERROR);
         return;
       }
       if ("onUpdateCheckComplete" in this.observer) {
         try {
           this.observer.onUpdateCheckComplete(results);
         }
         catch (e) {
-          WARN("onUpdateCheckComplete notification failed", e);
+          logger.warn("onUpdateCheckComplete notification failed", e);
         }
       }
       return;
     }
 
-    WARN("Update manifest had an unrecognised namespace: " + xml.documentElement.namespaceURI);
+    logger.warn("Update manifest had an unrecognised namespace: " + xml.documentElement.namespaceURI);
     this.notifyError(AddonUpdateChecker.ERROR_UNKNOWN_FORMAT);
   },
 
   /**
    * Called when the request times out
    */
   onTimeout: function() {
     this.request = null;
-    WARN("Request for " + this.url + " timed out");
+    logger.warn("Request for " + this.url + " timed out");
     this.notifyError(AddonUpdateChecker.ERROR_TIMEOUT);
   },
 
   /**
    * Called when the manifest failed to load.
    */
   onError: function UP_onError() {
     if (!Components.isSuccessCode(this.request.status)) {
-      WARN("Request failed: " + this.url + " - " + this.request.status);
+      logger.warn("Request failed: " + this.url + " - " + this.request.status);
     }
     else if (this.request.channel instanceof Ci.nsIHttpChannel) {
       try {
         if (this.request.channel.requestSucceeded) {
-          WARN("Request failed: " + this.url + " - " +
+          logger.warn("Request failed: " + this.url + " - " +
                this.request.channel.responseStatus + ": " +
                this.request.channel.responseStatusText);
         }
       }
       catch (e) {
-        WARN("HTTP Request failed for an unknown reason");
+        logger.warn("HTTP Request failed for an unknown reason");
       }
     }
     else {
-      WARN("Request failed for an unknown reason");
+      logger.warn("Request failed for an unknown reason");
     }
 
     this.request = null;
 
     this.notifyError(AddonUpdateChecker.ERROR_DOWNLOAD_ERROR);
   },
 
   /**
    * Helper method to notify the observer that an error occured.
    */
   notifyError: function UP_notifyError(aStatus) {
     if ("onUpdateCheckError" in this.observer) {
       try {
         this.observer.onUpdateCheckError(aStatus);
       }
       catch (e) {
-        WARN("onUpdateCheckError notification failed", e);
+        logger.warn("onUpdateCheckError notification failed", e);
       }
     }
   },
 
   /**
    * Called to cancel an in-progress update check.
    */
   cancel: function UP_cancel() {
--- a/toolkit/mozapps/extensions/internal/PluginProvider.jsm
+++ b/toolkit/mozapps/extensions/internal/PluginProvider.jsm
@@ -1,34 +1,33 @@
 /* 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/. */
 
 "use strict";
 
 const Cc = Components.classes;
 const Ci = Components.interfaces;
+const Cu = Components.utils;
 
 this.EXPORTED_SYMBOLS = [];
 
-Components.utils.import("resource://gre/modules/AddonManager.jsm");
-Components.utils.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/AddonManager.jsm");
+Cu.import("resource://gre/modules/Services.jsm");
 
 const URI_EXTENSION_STRINGS  = "chrome://mozapps/locale/extensions/extensions.properties";
 const STRING_TYPE_NAME       = "type.%ID%.name";
 const LIST_UPDATED_TOPIC     = "plugins-list-updated";
 
-for (let name of ["LOG", "WARN", "ERROR"]) {
-  this.__defineGetter__(name, function() {
-    Components.utils.import("resource://gre/modules/addons/AddonLogging.jsm");
+Cu.import("resource://gre/modules/Log.jsm");
+const LOGGER_ID = "addons.plugins";
 
-    LogManager.getLogger("addons.plugins", this);
-    return this[name];
-  });
-}
+// Create a new logger for use by the Addons Plugin Provider
+// (Requires AddonManager.jsm)
+let logger = Log.repository.getLogger(LOGGER_ID);
 
 function getIDHashForString(aStr) {
   // return the two-digit hexadecimal code for a byte
   function toHexString(charCode)
     ("0" + charCode.toString(16)).slice(-2);
 
   let hasher = Cc["@mozilla.org/security/hash;1"].
                createInstance(Ci.nsICryptoHash);
--- a/toolkit/mozapps/extensions/internal/XPIProvider.jsm
+++ b/toolkit/mozapps/extensions/internal/XPIProvider.jsm
@@ -187,28 +187,22 @@ const MSG_JAR_FLUSH = "AddonJarFlush";
 
 var gGlobalScope = this;
 
 /**
  * Valid IDs fit this pattern.
  */
 var gIDTest = /^(\{[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\}|[a-z0-9-\._]*\@[a-z0-9-\._]+)$/i;
 
-["LOG", "WARN", "ERROR"].forEach(function(aName) {
-  Object.defineProperty(this, aName, {
-    get: function logFuncGetter() {
-      Components.utils.import("resource://gre/modules/addons/AddonLogging.jsm");
-
-      LogManager.getLogger("addons.xpi", this);
-      return this[aName];
-    },
-    configurable: true
-  });
-}, this);
-
+Cu.import("resource://gre/modules/Log.jsm");
+const LOGGER_ID = "addons.xpi";
+
+// Create a new logger for use by all objects in this Addons XPI Provider module
+// (Requires AddonManager.jsm)
+let logger = Log.repository.getLogger(LOGGER_ID);
 
 const LAZY_OBJECTS = ["XPIDatabase"];
 
 var gLazyObjectsLoaded = false;
 
 function loadLazyObjects() {
   let scope = {};
   scope.AddonInternal = AddonInternal;
@@ -256,17 +250,17 @@ function findMatchingStaticBlocklistItem
  * @param  aPermissions
  *         The permisions to set
  */
 function setFilePermissions(aFile, aPermissions) {
   try {
     aFile.permissions = aPermissions;
   }
   catch (e) {
-    WARN("Failed to set permissions " + aPermissions.toString(8) + " on " +
+    logger.warn("Failed to set permissions " + aPermissions.toString(8) + " on " +
          aFile.path, e);
   }
 }
 
 /**
  * A safe way to install a file or the contents of a directory to a new
  * directory. The file or directory is moved or copied recursively and if
  * anything fails an attempt is made to rollback the entire operation. The
@@ -290,63 +284,63 @@ SafeInstallOperation.prototype = {
     let newFile = aFile.clone();
     try {
       if (aCopy)
         newFile.copyTo(aTargetDirectory, null);
       else
         newFile.moveTo(aTargetDirectory, null);
     }
     catch (e) {
-      ERROR("Failed to " + (aCopy ? "copy" : "move") + " file " + aFile.path +
+      logger.error("Failed to " + (aCopy ? "copy" : "move") + " file " + aFile.path +
             " to " + aTargetDirectory.path, e);
       throw e;
     }
     this._installedFiles.push({ oldFile: oldFile, newFile: newFile });
   },
 
   _installDirectory: function SIO_installDirectory(aDirectory, aTargetDirectory, aCopy) {
     let newDir = aTargetDirectory.clone();
     newDir.append(aDirectory.leafName);
     try {
       newDir.create(Ci.nsIFile.DIRECTORY_TYPE, FileUtils.PERMS_DIRECTORY);
     }
     catch (e) {
-      ERROR("Failed to create directory " + newDir.path, e);
+      logger.error("Failed to create directory " + newDir.path, e);
       throw e;
     }
     this._createdDirs.push(newDir);
 
     // Use a snapshot of the directory contents to avoid possible issues with
     // iterating over a directory while removing files from it (the YAFFS2
     // embedded filesystem has this issue, see bug 772238), and to remove
     // normal files before their resource forks on OSX (see bug 733436).
     let entries = getDirectoryEntries(aDirectory, true);
     entries.forEach(function(aEntry) {
       try {
         this._installDirEntry(aEntry, newDir, aCopy);
       }
       catch (e) {
-        ERROR("Failed to " + (aCopy ? "copy" : "move") + " entry " +
+        logger.error("Failed to " + (aCopy ? "copy" : "move") + " entry " +
               aEntry.path, e);
         throw e;
       }
     }, this);
 
     // If this is only a copy operation then there is nothing else to do
     if (aCopy)
       return;
 
     // The directory should be empty by this point. If it isn't this will throw
     // and all of the operations will be rolled back
     try {
       setFilePermissions(aDirectory, FileUtils.PERMS_DIRECTORY);
       aDirectory.remove(false);
     }
     catch (e) {
-      ERROR("Failed to remove directory " + aDirectory.path, e);
+      logger.error("Failed to remove directory " + aDirectory.path, e);
       throw e;
     }
 
     // Note we put the directory move in after all the file moves so the
     // directory is recreated before all the files are moved back
     this._installedFiles.push({ oldFile: aDirectory, newFile: newDir });
   },
 
@@ -358,29 +352,29 @@ SafeInstallOperation.prototype = {
     }
     catch (e) {
       // If the file has already gone away then don't worry about it, this can
       // happen on OSX where the resource fork is automatically moved with the
       // data fork for the file. See bug 733436.
       if (e.result == Cr.NS_ERROR_FILE_TARGET_DOES_NOT_EXIST)
         return;
 
-      ERROR("Failure " + (aCopy ? "copying" : "moving") + " " + aDirEntry.path +
+      logger.error("Failure " + (aCopy ? "copying" : "moving") + " " + aDirEntry.path +
             " to " + aTargetDirectory.path);
       throw e;
     }
 
     try {
       if (isDir)
         this._installDirectory(aDirEntry, aTargetDirectory, aCopy);
       else
         this._installFile(aDirEntry, aTargetDirectory, aCopy);
     }
     catch (e) {
-      ERROR("Failure " + (aCopy ? "copying" : "moving") + " " + aDirEntry.path +
+      logger.error("Failure " + (aCopy ? "copying" : "moving") + " " + aDirEntry.path +
             " to " + aTargetDirectory.path);
       throw e;
     }
   },
 
   /**
    * Moves a file or directory into a new directory. If an error occurs then all
    * files that have been moved will be moved back to their original location.
@@ -710,29 +704,29 @@ function loadManifestFromRDF(aUri, aStre
   function readLocale(aDs, aSource, isDefault, aSeenLocales) {
     let locale = { };
     if (!isDefault) {
       locale.locales = [];
       let targets = ds.GetTargets(aSource, EM_R("locale"), true);
       while (targets.hasMoreElements()) {
         let localeName = getRDFValue(targets.getNext());
         if (!localeName) {
-          WARN("Ignoring empty locale in localized properties");
+          logger.warn("Ignoring empty locale in localized properties");
           continue;
         }
         if (aSeenLocales.indexOf(localeName) != -1) {
-          WARN("Ignoring duplicate locale in localized properties");
+          logger.warn("Ignoring duplicate locale in localized properties");
           continue;
         }
         aSeenLocales.push(localeName);
         locale.locales.push(localeName);
       }
 
       if (locale.locales.length == 0) {
-        WARN("Ignoring localized properties with no listed locales");
+        logger.warn("Ignoring localized properties with no listed locales");
         return null;
       }
     }
 
     PROP_LOCALE_SINGLE.forEach(function(aProp) {
       locale[aProp] = getRDFProperty(aDs, aSource, aProp);
     });
 
@@ -859,21 +853,21 @@ function loadManifestFromRDF(aUri, aStre
   while (targets.hasMoreElements()) {
     let target = targets.getNext().QueryInterface(Ci.nsIRDFResource);
     let targetAppInfo = {};
     PROP_TARGETAPP.forEach(function(aProp) {
       targetAppInfo[aProp] = getRDFProperty(ds, target, aProp);
     });
     if (!targetAppInfo.id || !targetAppInfo.minVersion ||
         !targetAppInfo.maxVersion) {
-      WARN("Ignoring invalid targetApplication entry in install manifest");
+      logger.warn("Ignoring invalid targetApplication entry in install manifest");
       continue;
     }
     if (seenApplications.indexOf(targetAppInfo.id) != -1) {
-      WARN("Ignoring duplicate targetApplication entry for " + targetAppInfo.id +
+      logger.warn("Ignoring duplicate targetApplication entry for " + targetAppInfo.id +
            " in install manifest");
       continue;
     }
     seenApplications.push(targetAppInfo.id);
     addon.targetApplications.push(targetAppInfo);
   }
 
   // Note that we don't need to check for duplicate targetPlatform entries since
@@ -1162,23 +1156,23 @@ function saveStreamAsync(aPath, aStream,
 
   let data = Uint8Array(EXTRACTION_BUFFER);
 
   function readFailed(error) {
     try {
       aStream.close();
     }
     catch (e) {
-      ERROR("Failed to close JAR stream for " + aPath);
+      logger.error("Failed to close JAR stream for " + aPath);
     }
 
     aFile.close().then(function() {
       deferred.reject(error);
     }, function(e) {
-      ERROR("Failed to close file for " + aPath);
+      logger.error("Failed to close file for " + aPath);
       deferred.reject(error);
     });
   }
 
   function readData() {
     try {
       let count = Math.min(source.available(), data.byteLength);
       source.readArrayBuffer(count, data.buffer);
@@ -1234,31 +1228,31 @@ function extractFilesAsync(aZipFile, aDi
       let zipentry = zipReader.getEntry(name);
       let path = OS.Path.join(aDir.path, ...name.split("/"));
 
       if (zipentry.isDirectory) {
         try {
           yield OS.File.makeDir(path);
         }
         catch (e) {
-          ERROR("extractFilesAsync: failed to create directory " + path, e);
+          logger.error("extractFilesAsync: failed to create directory " + path, e);
           throw e;
         }
       }
       else {
         let options = { unixMode: zipentry.permissions | FileUtils.PERMS_FILE };
         try {
           let file = yield OS.File.open(path, { truncate: true }, options);
           if (zipentry.realSize == 0)
             yield file.close();
           else
             yield saveStreamAsync(path, zipReader.getInputStream(entryName), file);
         }
         catch (e) {
-          ERROR("extractFilesAsync: failed to extract file " + path, e);
+          logger.error("extractFilesAsync: failed to extract file " + path, e);
           throw e;
         }
       }
     }
 
     zipReader.close();
   }).then(null, (e) => {
     zipReader.close();
@@ -1294,17 +1288,17 @@ function extractFiles(aZipFile, aDir) {
       var entryName = entries.getNext();
       let target = getTargetFile(aDir, entryName);
       if (!target.exists()) {
         try {
           target.create(Ci.nsIFile.DIRECTORY_TYPE,
                         FileUtils.PERMS_DIRECTORY);
         }
         catch (e) {
-          ERROR("extractFiles: failed to create target directory for " +
+          logger.error("extractFiles: failed to create target directory for " +
                 "extraction file = " + target.path, e);
         }
       }
     }
 
     entries = zipReader.findEntries(null);
     while (entries.hasMore()) {
       let entryName = entries.getNext();
@@ -1312,17 +1306,17 @@ function extractFiles(aZipFile, aDir) {
       if (target.exists())
         continue;
 
       zipReader.extract(entryName, target);
       try {
         target.permissions |= FileUtils.PERMS_FILE;
       }
       catch (e) {
-        WARN("Failed to set permissions " + aPermissions.toString(8) + " on " +
+        logger.warn("Failed to set permissions " + aPermissions.toString(8) + " on " +
              target.path, e);
       }
     }
   }
   finally {
     zipReader.close();
   }
 }
@@ -1445,33 +1439,33 @@ function recursiveRemove(aFile) {
                                   : FileUtils.PERMS_FILE);
 
   try {
     aFile.remove(true);
     return;
   }
   catch (e) {
     if (!aFile.isDirectory()) {
-      ERROR("Failed to remove file " + aFile.path, e);
+      logger.error("Failed to remove file " + aFile.path, e);
       throw e;
     }
   }
 
   // Use a snapshot of the directory contents to avoid possible issues with
   // iterating over a directory while removing files from it (the YAFFS2
   // embedded filesystem has this issue, see bug 772238), and to remove
   // normal files before their resource forks on OSX (see bug 733436).
   let entries = getDirectoryEntries(aFile, true);
   entries.forEach(recursiveRemove);
 
   try {
     aFile.remove(true);
   }
   catch (e) {
-    ERROR("Failed to remove empty directory " + aFile.path, e);
+    logger.error("Failed to remove empty directory " + aFile.path, e);
     throw e;
   }
 }
 
 /**
  * Returns the timestamp and leaf file name of the most recently modified
  * entry in a directory,
  * or simply the file's own timestamp if it is not a directory.
@@ -1500,17 +1494,17 @@ function recursiveLastModifiedTime(aFile
           fileName = subName;
         }
       }
       entries.close();
       return [fileName, modTime, totalItems];
     }
   }
   catch (e) {
-    WARN("Problem getting last modified time for " + aFile.path, e);
+    logger.warn("Problem getting last modified time for " + aFile.path, e);
   }
 
   // If the file is something else, just ignore it.
   return ["", 0, 0];
 }
 
 /**
  * Gets a snapshot of directory entries.
@@ -1705,17 +1699,17 @@ function directoryStateDiffers(aState, a
  *         a warning and returns undefined.
  */
 function makeSafe(aFunction) {
   return function(...aArgs) {
     try {
       return aFunction(...aArgs);
     }
     catch(ex) {
-      WARN("XPIProvider callback failed", ex);
+      logger.warn("XPIProvider callback failed", ex);
     }
     return undefined;
   }
 }
 
 var XPIProvider = {
   // An array of known install locations
   installLocations: null,
@@ -1781,17 +1775,17 @@ var XPIProvider = {
   cancelAll: function XPI_cancelAll() {
     // Cancelling one may alter _inProgress, so restart the iterator after each
     while (this._inProgress.size > 0) {
       for (let c of this._inProgress) {
         try {
           c.cancel();
         }
         catch (e) {
-          WARN("Cancel failed", e);
+          logger.warn("Cancel failed", e);
         }
         this._inProgress.delete(c);
       }
     }
   },
 
   /**
    * Adds or updates a URI mapping for an Addon.id.
@@ -1807,17 +1801,17 @@ var XPIProvider = {
       let uri = this._resolveURIToFile(getURIForResourceInFile(aFile, "."));
       if (!uri) {
         throw new Error("Cannot resolve");
       }
       this._ensureURIMappings();
       this._uriMappings[aID] = uri.spec;
     }
     catch (ex) {
-      WARN("Failed to add URI mapping", ex);
+      logger.warn("Failed to add URI mapping", ex);
     }
   },
 
   /**
    * Ensures that the URI to Addon mappings are available.
    *
    * The function will add mappings for all non-bootstrapped but enabled
    * add-ons.
@@ -1922,17 +1916,17 @@ var XPIProvider = {
    * @param  aOldAppVersion
    *         The version of the application last run with this profile or null
    *         if it is a new profile or the version is unknown
    * @param  aOldPlatformVersion
    *         The version of the platform last run with this profile or null
    *         if it is a new profile or the version is unknown
    */
   startup: function XPI_startup(aAppChanged, aOldAppVersion, aOldPlatformVersion) {
-    LOG("startup");
+    logger.debug("startup");
     this.runPhase = XPI_STARTING;
     this.installs = [];
     this.installLocations = [];
     this.installLocationsByName = {};
     // Hook for tests to detect when saving database at shutdown time fails
     this._shutdownError = null;
     // Clear this at startup for xpcshell test restarts
     this._telemetryDetails = {};
@@ -1943,38 +1937,38 @@ var XPIProvider = {
     AddonManagerPrivate.recordTimestamp("XPI_startup_begin");
 
     function addDirectoryInstallLocation(aName, aKey, aPaths, aScope, aLocked) {
       try {
         var dir = FileUtils.getDir(aKey, aPaths);
       }
       catch (e) {
         // Some directories aren't defined on some platforms, ignore them
-        LOG("Skipping unavailable install location " + aName);
+        logger.debug("Skipping unavailable install location " + aName);
         return;
       }
 
       try {
         var location = new DirectoryInstallLocation(aName, dir, aScope, aLocked);
       }
       catch (e) {
-        WARN("Failed to add directory install location " + aName, e);
+        logger.warn("Failed to add directory install location " + aName, e);
         return;
       }
 
       XPIProvider.installLocations.push(location);
       XPIProvider.installLocationsByName[location.name] = location;
     }
 
     function addRegistryInstallLocation(aName, aRootkey, aScope) {
       try {
         var location = new WinRegInstallLocation(aName, aRootkey, aScope);
       }
       catch (e) {
-        WARN("Failed to add registry install location " + aName, e);
+        logger.warn("Failed to add registry install location " + aName, e);
         return;
       }
 
       XPIProvider.installLocations.push(location);
       XPIProvider.installLocationsByName[location.name] = location;
     }
 
     try {
@@ -2093,24 +2087,24 @@ var XPIProvider = {
             if (AddonManager.getStartupChanges(AddonManager.STARTUP_CHANGE_INSTALLED)
                             .indexOf(id) !== -1)
               reason = BOOTSTRAP_REASONS.ADDON_INSTALL;
             this.callBootstrapMethod(id, this.bootstrappedAddons[id].version,
                                      this.bootstrappedAddons[id].type, file,
                                      "startup", reason);
           }
           catch (e) {
-            ERROR("Failed to load bootstrap addon " + id + " from " +
+            logger.error("Failed to load bootstrap addon " + id + " from " +
                   this.bootstrappedAddons[id].descriptor, e);
           }
         }
         AddonManagerPrivate.recordTimestamp("XPI_bootstrap_addons_end");
       }
       catch (e) {
-        ERROR("bootstrap startup failed", e);
+        logger.error("bootstrap startup failed", e);
         AddonManagerPrivate.recordException("XPI-BOOTSTRAP", "startup failed", e);
       }
 
       // Let these shutdown a little earlier when they still have access to most
       // of XPCOM
       Services.obs.addObserver({
         observe: function shutdownObserver(aSubject, aTopic, aData) {
           for (let id in XPIProvider.bootstrappedAddons) {
@@ -2134,29 +2128,29 @@ var XPIProvider = {
       }, "final-ui-startup", false);
 
       AddonManagerPrivate.recordTimestamp("XPI_startup_end");
 
       this.extensionsActive = true;
       this.runPhase = XPI_BEFORE_UI_STARTUP;
     }
     catch (e) {
-      ERROR("startup failed", e);
+      logger.error("startup failed", e);
       AddonManagerPrivate.recordException("XPI", "startup failed", e);
     }
   },
 
   /**
    * Shuts down the database and releases all references.
    * Return: Promise{integer} resolves / rejects with the result of
    *                          flushing the XPI Database if it was loaded,
    *                          0 otherwise.
    */
   shutdown: function XPI_shutdown() {
-    LOG("shutdown");
+    logger.debug("shutdown");
 
     // Stop anything we were doing asynchronously
     this.cancelAll();
 
     this.bootstrappedAddons = {};
     this.bootstrapScopes = {};
     this.enabledAddons = null;
     this.allAppGlobal = true;
@@ -2180,29 +2174,29 @@ var XPIProvider = {
 
     // Remove URI mappings again
     delete this._uriMappings;
 
     if (gLazyObjectsLoaded) {
       let done = XPIDatabase.shutdown();
       done.then(
         ret => {
-          LOG("Notifying XPI shutdown observers");
+          logger.debug("Notifying XPI shutdown observers");
           Services.obs.notifyObservers(null, "xpi-provider-shutdown", null);
         },
         err => {
-          LOG("Notifying XPI shutdown observers");
+          logger.debug("Notifying XPI shutdown observers");
           this._shutdownError = err;
           Services.obs.notifyObservers(null, "xpi-provider-shutdown", err);
         }
       );
       return done;
     }
     else {
-      LOG("Notifying XPI shutdown observers");
+      logger.debug("Notifying XPI shutdown observers");
       Services.obs.notifyObservers(null, "xpi-provider-shutdown", null);
     }
   },
 
   /**
    * Applies any pending theme change to the preferences.
    */
   applyThemeChange: function XPI_applyThemeChange() {
@@ -2210,21 +2204,21 @@ var XPIProvider = {
       return;
 
     // Tell the Chrome Registry which Skin to select
     try {
       this.selectedSkin = Prefs.getCharPref(PREF_DSS_SKIN_TO_SELECT);
       Services.prefs.setCharPref(PREF_GENERAL_SKINS_SELECTEDSKIN,
                                  this.selectedSkin);
       Services.prefs.clearUserPref(PREF_DSS_SKIN_TO_SELECT);
-      LOG("Changed skin to " + this.selectedSkin);
+      logger.debug("Changed skin to " + this.selectedSkin);
       this.currentSkin = this.selectedSkin;
     }
     catch (e) {
-      ERROR("Error applying theme change", e);
+      logger.error("Error applying theme change", e);
     }
     Services.prefs.clearUserPref(PREF_DSS_SWITCHPENDING);
   },
 
   /**
    * Shows the "Compatibility Updates" UI
    */
   showUpgradeUI: function XPI_showUpgradeUI() {
@@ -2377,17 +2371,17 @@ var XPIProvider = {
 
       if (stagedXPIDir.exists() && stagedXPIDir.isDirectory()) {
         let entries = stagedXPIDir.directoryEntries
                                   .QueryInterface(Ci.nsIDirectoryEnumerator);
         while (entries.hasMoreElements()) {
           let stageDirEntry = entries.nextFile;
 
           if (!stageDirEntry.isDirectory()) {
-            WARN("Ignoring file in XPI staging directory: " + stageDirEntry.path);
+            logger.warn("Ignoring file in XPI staging directory: " + stageDirEntry.path);
             continue;
           }
 
           // Find the last added XPI file in the directory
           let stagedXPI = null;
           var xpiEntries = stageDirEntry.directoryEntries
                                         .QueryInterface(Ci.nsIDirectoryEnumerator);
           while (xpiEntries.hasMoreElements()) {
@@ -2408,70 +2402,70 @@ var XPIProvider = {
           if (!stagedXPI)
             continue;
 
           let addon = null;
           try {
             addon = loadManifestFromZipFile(stagedXPI);
           }
           catch (e) {
-            ERROR("Unable to read add-on manifest from " + stagedXPI.path, e);
+            logger.error("Unable to read add-on manifest from " + stagedXPI.path, e);
             continue;
           }
 
-          LOG("Migrating staged install of " + addon.id + " in " + aLocation.name);
+          logger.debug("Migrating staged install of " + addon.id + " in " + aLocation.name);
 
           if (addon.unpack || Prefs.getBoolPref(PREF_XPI_UNPACK, false)) {
             let targetDir = stagingDir.clone();
             targetDir.append(addon.id);
             try {
               targetDir.create(Ci.nsIFile.DIRECTORY_TYPE, FileUtils.PERMS_DIRECTORY);
             }
             catch (e) {
-              ERROR("Failed to create staging directory for add-on " + addon.id, e);
+              logger.error("Failed to create staging directory for add-on " + addon.id, e);
               continue;
             }
 
             try {
               extractFiles(stagedXPI, targetDir);
             }
             catch (e) {
-              ERROR("Failed to extract staged XPI for add-on " + addon.id + " in " +
+              logger.error("Failed to extract staged XPI for add-on " + addon.id + " in " +
                     aLocation.name, e);
             }
           }
           else {
             try {
               stagedXPI.moveTo(stagingDir, addon.id + ".xpi");
             }
             catch (e) {
-              ERROR("Failed to move staged XPI for add-on " + addon.id + " in " +
+              logger.error("Failed to move staged XPI for add-on " + addon.id + " in " +
                     aLocation.name, e);
             }
           }
         }
         entries.close();
       }
 
       if (stagedXPIDir.exists()) {
         try {
           recursiveRemove(stagedXPIDir);
         }
         catch (e) {
           // Non-critical, just saves some perf on startup if we clean this up.
-          LOG("Error removing XPI staging dir " + stagedXPIDir.path, e);
+          logger.debug("Error removing XPI staging dir " + stagedXPIDir.path, e);
         }
       }
 
       try {
         if (!stagingDir || !stagingDir.exists() || !stagingDir.isDirectory())
           return;
       }
       catch (e) {
-        WARN("Failed to find staging directory", e);
+        logger.warn("Failed to find staging directory", e);
         return;
       }
 
       let seenFiles = [];
       // Use a snapshot of the directory contents to avoid possible issues with
       // iterating over a directory while removing files from it (the YAFFS2
       // embedded filesystem has this issue, see bug 772238), and to remove
       // normal files before their resource forks on OSX (see bug 733436).
@@ -2491,101 +2485,101 @@ var XPIProvider = {
         }
 
         if (!isDir) {
           if (id.substring(id.length - 4).toLowerCase() == ".xpi") {
             id = id.substring(0, id.length - 4);
           }
           else {
             if (id.substring(id.length - 5).toLowerCase() != ".json") {
-              WARN("Ignoring file: " + stageDirEntry.path);
+              logger.warn("Ignoring file: " + stageDirEntry.path);
               seenFiles.push(stageDirEntry.leafName);
             }
             continue;
           }
         }
 
         // Check that the directory's name is a valid ID.
         if (!gIDTest.test(id)) {
-          WARN("Ignoring directory whose name is not a valid add-on ID: " +
+          logger.warn("Ignoring directory whose name is not a valid add-on ID: " +
                stageDirEntry.path);
           seenFiles.push(stageDirEntry.leafName);
           continue;
         }
 
         changed = true;
 
         if (isDir) {
           // Check if the directory contains an install manifest.
           let manifest = stageDirEntry.clone();
           manifest.append(FILE_INSTALL_MANIFEST);
 
           // If the install manifest doesn't exist uninstall this add-on in this
           // install location.
           if (!manifest.exists()) {
-            LOG("Processing uninstall of " + id + " in " + aLocation.name);
+            logger.debug("Processing uninstall of " + id + " in " + aLocation.name);
             try {
               aLocation.uninstallAddon(id);
               seenFiles.push(stageDirEntry.leafName);
             }
             catch (e) {
-              ERROR("Failed to uninstall add-on " + id + " in " + aLocation.name, e);
+              logger.error("Failed to uninstall add-on " + id + " in " + aLocation.name, e);
             }
             // The file check later will spot the removal and cleanup the database
             continue;
           }
         }
 
         aManifests[aLocation.name][id] = null;
         let existingAddonID = id;
 
         let jsonfile = stagingDir.clone();
         jsonfile.append(id + ".json");
 
         try {
           aManifests[aLocation.name][id] = loadManifestFromFile(stageDirEntry);
         }
         catch (e) {
-          ERROR("Unable to read add-on manifest from " + stageDirEntry.path, e);
+          logger.error("Unable to read add-on manifest from " + stageDirEntry.path, e);
           // This add-on can't be installed so just remove it now
           seenFiles.push(stageDirEntry.leafName);
           seenFiles.push(jsonfile.leafName);
           continue;
         }
 
         // Check for a cached metadata for this add-on, it may contain updated
         // compatibility information
         if (jsonfile.exists()) {
-          LOG("Found updated metadata for " + id + " in " + aLocation.name);
+          logger.debug("Found updated metadata for " + id + " in " + aLocation.name);
           let fis = Cc["@mozilla.org/network/file-input-stream;1"].
                        createInstance(Ci.nsIFileInputStream);
           let json = Cc["@mozilla.org/dom/json;1"].
                      createInstance(Ci.nsIJSON);
 
           try {
             fis.init(jsonfile, -1, 0, 0);
             let metadata = json.decodeFromStream(fis, jsonfile.fileSize);
             aManifests[aLocation.name][id].importMetadata(metadata);
           }
           catch (e) {
             // If some data can't be recovered from the cached metadata then it
             // is unlikely to be a problem big enough to justify throwing away
             // the install, just log and error and continue
-            ERROR("Unable to read metadata from " + jsonfile.path, e);
+            logger.error("Unable to read metadata from " + jsonfile.path, e);
           }
           finally {
             fis.close();
           }
         }
         seenFiles.push(jsonfile.leafName);
 
         existingAddonID = aManifests[aLocation.name][id].existingAddonID || id;
 
         var oldBootstrap = null;
-        LOG("Processing install of " + id + " in " + aLocation.name);
+        logger.debug("Processing install of " + id + " in " + aLocation.name);
         if (existingAddonID in this.bootstrappedAddons) {
           try {
             var existingAddon = aLocation.getLocationForID(existingAddonID);
             if (this.bootstrappedAddons[existingAddonID].descriptor ==
                 existingAddon.persistentDescriptor) {
               oldBootstrap = this.bootstrappedAddons[existingAddonID];
 
               // We'll be replacing a currently active bootstrapped add-on so
@@ -2609,17 +2603,17 @@ var XPIProvider = {
 
         try {
           var addonInstallLocation = aLocation.installAddon(id, stageDirEntry,
                                                             existingAddonID);
           if (aManifests[aLocation.name][id])
             aManifests[aLocation.name][id]._sourceBundle = addonInstallLocation;
         }
         catch (e) {
-          ERROR("Failed to install staged add-on " + id + " in " + aLocation.name,
+          logger.error("Failed to install staged add-on " + id + " in " + aLocation.name,
                 e);
           // Re-create the staged install
           AddonInstall.createStagedInstall(aLocation, stageDirEntry,
                                            aManifests[aLocation.name][id]);
           // Make sure not to delete the cached manifest json file
           seenFiles.pop();
 
           delete aManifests[aLocation.name][id];
@@ -2634,17 +2628,17 @@ var XPIProvider = {
         }
       }
 
       try {
         aLocation.cleanStagingDir(seenFiles);
       }
       catch (e) {
         // Non-critical, just saves some perf on startup if we clean this up.
-        LOG("Error cleaning staging dir " + stagingDir.path, e);
+        logger.debug("Error cleaning staging dir " + stagingDir.path, e);
       }
     }, this);
     return changed;
   },
 
   /**
    * Installs any add-ons located in the extensions directory of the
    * application's distribution specific directory into the profile unless a
@@ -2681,43 +2675,43 @@ var XPIProvider = {
 
       let id = entry.leafName;
 
       if (entry.isFile()) {
         if (id.substring(id.length - 4).toLowerCase() == ".xpi") {
           id = id.substring(0, id.length - 4);
         }
         else {
-          LOG("Ignoring distribution add-on that isn't an XPI: " + entry.path);
+          logger.debug("Ignoring distribution add-on that isn't an XPI: " + entry.path);
           continue;
         }
       }
       else if (!entry.isDirectory()) {
-        LOG("Ignoring distribution add-on that isn't a file or directory: " +
+        logger.debug("Ignoring distribution add-on that isn't a file or directory: " +
             entry.path);
         continue;
       }
 
       if (!gIDTest.test(id)) {
-        LOG("Ignoring distribution add-on whose name is not a valid add-on ID: " +
+        logger.debug("Ignoring distribution add-on whose name is not a valid add-on ID: " +
             entry.path);
         continue;
       }
 
       let addon;
       try {
         addon = loadManifestFromFile(entry);
       }
       catch (e) {
-        WARN("File entry " + entry.path + " contains an invalid add-on", e);
+        logger.warn("File entry " + entry.path + " contains an invalid add-on", e);
         continue;
       }
 
       if (addon.id != id) {
-        WARN("File entry " + entry.path + " contains an add-on with an " +
+        logger.warn("File entry " + entry.path + " contains an add-on with an " +
              "incorrect ID")
         continue;
       }
 
       let existingEntry = null;
       try {
         existingEntry = profileLocation.getLocationForID(id);
       }
@@ -2729,41 +2723,41 @@ var XPIProvider = {
         try {
           existingAddon = loadManifestFromFile(existingEntry);
 
           if (Services.vc.compare(addon.version, existingAddon.version) <= 0)
             continue;
         }
         catch (e) {
           // Bad add-on in the profile so just proceed and install over the top
-          WARN("Profile contains an add-on with a bad or missing install " +
+          logger.warn("Profile contains an add-on with a bad or missing install " +
                "manifest at " + existingEntry.path + ", overwriting", e);
         }
       }
       else if (Prefs.getBoolPref(PREF_BRANCH_INSTALLED_ADDON + id, false)) {
         continue;
       }
 
       // Install the add-on
       try {
         profileLocation.installAddon(id, entry, null, true);
-        LOG("Installed distribution add-on " + id);
+        logger.debug("Installed distribution add-on " + id);
 
         Services.prefs.setBoolPref(PREF_BRANCH_INSTALLED_ADDON + id, true)
 
         // aManifests may contain a copy of a newly installed add-on's manifest
         // and we'll have overwritten that so instead cache our install manifest
         // which will later be put into the database in processFileChanges
         if (!(KEY_APP_PROFILE in aManifests))
           aManifests[KEY_APP_PROFILE] = {};
         aManifests[KEY_APP_PROFILE][id] = addon;
         changed = true;
       }
       catch (e) {
-        ERROR("Failed to install distribution add-on " + entry.path, e);
+        logger.error("Failed to install distribution add-on " + entry.path, e);
       }
     }
 
     entries.close();
 
     return changed;
   },
 
@@ -2809,17 +2803,17 @@ var XPIProvider = {
      *         The AddonInternal as it appeared the last time the application
      *         ran
      * @param  aAddonState
      *         The new state of the add-on
      * @return a boolean indicating if flushing caches is required to complete
      *         changing this add-on
      */
     function updateMetadata(aInstallLocation, aOldAddon, aAddonState) {
-      LOG("Add-on " + aOldAddon.id + " modified in " + aInstallLocation.name);
+      logger.debug("Add-on " + aOldAddon.id + " modified in " + aInstallLocation.name);
 
       // Check if there is an updated install manifest for this add-on
       let newAddon = aManifests[aInstallLocation.name][aOldAddon.id];
 
       try {
         // If not load it
         if (!newAddon) {
           let file = aInstallLocation.getLocationForID(aOldAddon.id);
@@ -2834,22 +2828,22 @@ var XPIProvider = {
         }
 
         // The ID in the manifest that was loaded must match the ID of the old
         // add-on.
         if (newAddon.id != aOldAddon.id)
           throw new Error("Incorrect id in install manifest");
       }
       catch (e) {
-        WARN("Add-on is invalid", e);
+        logger.warn("Add-on is invalid", e);
         XPIDatabase.removeAddonMetadata(aOldAddon);
         if (!aInstallLocation.locked)
           aInstallLocation.uninstallAddon(aOldAddon.id);
         else
-          WARN("Could not uninstall invalid item from locked install location");
+          logger.warn("Could not uninstall invalid item from locked install location");
         // If this was an active add-on then we must force a restart
         if (aOldAddon.active)
           return true;
 
         return false;
       }
 
       // Set the additional properties on the new AddonInternal
@@ -2904,17 +2898,17 @@ var XPIProvider = {
      *         The AddonInternal as it appeared the last time the application
      *         ran
      * @param  aAddonState
      *         The new state of the add-on
      * @return a boolean indicating if flushing caches is required to complete
      *         changing this add-on
      */
     function updateDescriptor(aInstallLocation, aOldAddon, aAddonState) {
-      LOG("Add-on " + aOldAddon.id + " moved to " + aAddonState.descriptor);
+      logger.debug("Add-on " + aOldAddon.id + " moved to " + aAddonState.descriptor);
 
       aOldAddon.descriptor = aAddonState.descriptor;
       aOldAddon.visible = !(aOldAddon.id in visibleAddons);
       XPIDatabase.saveChanges();
 
       if (aOldAddon.visible) {
         visibleAddons[aOldAddon.id] = aOldAddon;
 
@@ -2997,17 +2991,17 @@ var XPIProvider = {
         aOldAddon.appDisabled = !isUsableAddon(aOldAddon);
 
         let isDisabled = isAddonDisabled(aOldAddon);
 
         // If either property has changed update the database.
         if (wasAppDisabled != aOldAddon.appDisabled ||
             wasUserDisabled != aOldAddon.userDisabled ||
             wasSoftDisabled != aOldAddon.softDisabled) {
-          LOG("Add-on " + aOldAddon.id + " changed appDisabled state to " +
+          logger.debug("Add-on " + aOldAddon.id + " changed appDisabled state to " +
               aOldAddon.appDisabled + ", userDisabled state to " +
               aOldAddon.userDisabled + " and softDisabled state to " +
               aOldAddon.softDisabled);
           XPIDatabase.saveChanges();
         }
 
         // If this is a visible add-on and it has changed disabled state then we
         // may need a restart or to update the bootstrap list.
@@ -3044,17 +3038,17 @@ var XPIProvider = {
      * @param  aOldAddon
      *         The AddonInternal as it appeared the last time the application
      *         ran
      * @return a boolean indicating if flushing caches is required to complete
      *         changing this add-on
      */
     function removeMetadata(aOldAddon) {
       // This add-on has disappeared
-      LOG("Add-on " + aOldAddon.id + " removed from " + aOldAddon.location);
+      logger.debug("Add-on " + aOldAddon.id + " removed from " + aOldAddon.location);
       XPIDatabase.removeAddonMetadata(aOldAddon);
 
       // Remember add-ons that were uninstalled during startup
       if (aOldAddon.visible) {
         AddonManagerPrivate.addStartupChange(AddonManager.STARTUP_CHANGE_UNINSTALLED,
                                              aOldAddon.id);
       }
       else if (AddonManager.getStartupChanges(AddonManager.STARTUP_CHANGE_INSTALLED)
@@ -3091,17 +3085,17 @@ var XPIProvider = {
      *         The new state of the add-on
      * @param  aMigrateData
      *         If during startup the database had to be upgraded this will
      *         contain data that used to be held about this add-on
      * @return a boolean indicating if flushing caches is required to complete
      *         changing this add-on
      */
     function addMetadata(aInstallLocation, aId, aAddonState, aMigrateData) {
-      LOG("New add-on " + aId + " installed in " + aInstallLocation.name);
+      logger.debug("New add-on " + aId + " installed in " + aInstallLocation.name);
 
       let newAddon = null;
       let sameVersion = false;
       // Check the updated manifests lists for the install location, If there
       // is no manifest for the add-on ID then newAddon will be undefined
       if (aInstallLocation.name in aManifests)
         newAddon = aManifests[aInstallLocation.name][aId];
 
@@ -3123,37 +3117,37 @@ var XPIProvider = {
         }
         // The add-on in the manifest should match the add-on ID.
         if (newAddon.id != aId) {
           throw new Error("Invalid addon ID: expected addon ID " + aId +
                           ", found " + newAddon.id + " in manifest");
         }
       }
       catch (e) {
-        WARN("Add-on is invalid", e);
+        logger.warn("Add-on is invalid", e);
 
         // Remove the invalid add-on from the install location if the install
         // location isn't locked, no restart will be necessary
         if (!aInstallLocation.locked)
           aInstallLocation.uninstallAddon(aId);
         else
-          WARN("Could not uninstall invalid item from locked install location");
+          logger.warn("Could not uninstall invalid item from locked install location");
         return false;
       }
 
       // Update the AddonInternal properties.
       newAddon._installLocation = aInstallLocation;
       newAddon.visible = !(newAddon.id in visibleAddons);
       newAddon.installDate = aAddonState.mtime;
       newAddon.updateDate = aAddonState.mtime;
       newAddon.foreignInstall = isDetectedInstall;
 
       if (aMigrateData) {
         // If there is migration data then apply it.
-        LOG("Migrating data from old database");
+        logger.debug("Migrating data from old database");
 
         DB_MIGRATE_METADATA.forEach(function(aProp) {
           // A theme's disabled state is determined by the selected theme
           // preference which is read in loadManifestFromRDF
           if (aProp == "userDisabled" && newAddon.type == "theme")
             return;
 
           if (aProp in aMigrateData)
@@ -3163,17 +3157,17 @@ var XPIProvider = {
         // Force all non-profile add-ons to be foreignInstalls since they can't
         // have been installed through the API
         newAddon.foreignInstall |= aInstallLocation.name != KEY_APP_PROFILE;
 
         // Some properties should only be migrated if the add-on hasn't changed.
         // The version property isn't a perfect check for this but covers the
         // vast majority of cases.
         if (aMigrateData.version == newAddon.version) {
-          LOG("Migrating compatibility info");
+          logger.debug("Migrating compatibility info");
           sameVersion = true;
           if ("targetApplications" in aMigrateData)
             newAddon.applyCompatibilityUpdate(aMigrateData, true);
         }
 
         // Since the DB schema has changed make sure softDisabled is correct
         applyBlocklistChanges(newAddon, newAddon, aOldAppVersion,
                               aOldPlatformVersion);
@@ -3444,32 +3438,32 @@ var XPIProvider = {
    *         if it is a new profile or the version is unknown
    * @param  aOldPlatformVersion
    *         The version of the platform last run with this profile or null
    *         if it is a new profile or the version is unknown
    * @return true if a change requiring a restart was detected
    */
   checkForChanges: function XPI_checkForChanges(aAppChanged, aOldAppVersion,
                                                 aOldPlatformVersion) {
-    LOG("checkForChanges");
+    logger.debug("checkForChanges");
 
     // Keep track of whether and why we need to open and update the database at
     // startup time.
     let updateReasons = [];
     if (aAppChanged) {
       updateReasons.push("appChanged");
     }
 
     // Load the list of bootstrapped add-ons first so processFileChanges can
     // modify it
     try {
       this.bootstrappedAddons = JSON.parse(Prefs.getCharPref(PREF_BOOTSTRAP_ADDONS,
                                            "{}"));
     } catch (e) {
-      WARN("Error parsing enabled bootstrapped extensions cache", e);
+      logger.warn("Error parsing enabled bootstrapped extensions cache", e);
     }
 
     // First install any new add-ons into the locations, if there are any
     // changes then we must update the database with the information in the
     // install locations
     let manifests = {};
     let updated = this.processPendingFileChanges(manifests);
     if (updated) {
@@ -3501,31 +3495,31 @@ var XPIProvider = {
     telemetry.getHistogramById("CHECK_ADDONS_MODIFIED_MS").add(Date.now() - telemetryCaptureTime);
 
     // If the install directory state has changed then we must update the database
     let cache = Prefs.getCharPref(PREF_INSTALL_CACHE, "[]");
     // For a little while, gather telemetry on whether the deep comparison
     // makes a difference
     let newState = JSON.stringify(this.installStates);
     if (cache != newState) {
-      LOG("Directory state JSON differs: cache " + cache + " state " + newState);
+      logger.debug("Directory state JSON differs: cache " + cache + " state " + newState);
       if (directoryStateDiffers(this.installStates, cache)) {
         updateReasons.push("directoryState");
       }
       else {
         AddonManagerPrivate.recordSimpleMeasure("XPIDB_startup_state_badCompare", 1);
       }
     }
 
     // If the schema appears to have changed then we should update the database
     if (DB_SCHEMA != Prefs.getIntPref(PREF_DB_SCHEMA, 0)) {
       // If we don't have any add-ons, just update the pref, since we don't need to
       // write the database
       if (this.installStates.length == 0) {
-        LOG("Empty XPI database, setting schema version preference to " + DB_SCHEMA);
+        logger.debug("Empty XPI database, setting schema version preference to " + DB_SCHEMA);
         Services.prefs.setIntPref(PREF_DB_SCHEMA, DB_SCHEMA);
       }
       else {
         updateReasons.push("schemaChanged");
       }
     }
 
     // If the database doesn't exist and there are add-ons installed then we
@@ -3544,17 +3538,17 @@ var XPIProvider = {
         for (let id in aInstallLocationState.addons) {
           let pos = bootstrapDescriptors.indexOf(aInstallLocationState.addons[id].descriptor);
           if (pos != -1)
             bootstrapDescriptors.splice(pos, 1);
         }
       });
 
       if (bootstrapDescriptors.length > 0) {
-        WARN("Bootstrap state is invalid (missing add-ons: " + bootstrapDescriptors.toSource() + ")");
+        logger.warn("Bootstrap state is invalid (missing add-ons: " + bootstrapDescriptors.toSource() + ")");
         updateReasons.push("missingBootstrapAddon");
       }
     }
 
     // Catch and log any errors during the main startup
     try {
       let extensionListChanged = false;
       // If the database needs to be updated then open it and then update it
@@ -3564,17 +3558,17 @@ var XPIProvider = {
         XPIDatabase.syncLoadDB(false);
         try {
           extensionListChanged = this.processFileChanges(this.installStates, manifests,
                                                          aAppChanged,
                                                          aOldAppVersion,
                                                          aOldPlatformVersion);
         }
         catch (e) {
-          ERROR("Failed to process extension changes at startup", e);
+          logger.error("Failed to process extension changes at startup", e);
         }
       }
 
       if (aAppChanged) {
         // When upgrading the app and using a custom skin make sure it is still
         // compatible otherwise switch back the default
         if (this.currentSkin != this.defaultSkin) {
           let oldSkin = XPIDatabase.getVisibleAddonForInternalName(this.currentSkin);
@@ -3585,43 +3579,43 @@ var XPIProvider = {
         // When upgrading remove the old extensions cache to force older
         // versions to rescan the entire list of extensions
         try {
           let oldCache = FileUtils.getFile(KEY_PROFILEDIR, [FILE_OLD_CACHE], true);
           if (oldCache.exists())
             oldCache.remove(true);
         }
         catch (e) {
-          WARN("Unable to remove old extension cache " + oldCache.path, e);
+          logger.warn("Unable to remove old extension cache " + oldCache.path, e);
         }
       }
 
       // If the application crashed before completing any pending operations then
       // we should perform them now.
       if (extensionListChanged || hasPendingChanges) {
-        LOG("Updating database with changes to installed add-ons");
+        logger.debug("Updating database with changes to installed add-ons");
         XPIDatabase.updateActiveAddons();
         Services.prefs.setBoolPref(PREF_PENDING_OPERATIONS,
                                    !XPIDatabase.writeAddonsList());
         Services.prefs.setCharPref(PREF_BOOTSTRAP_ADDONS,
                                    JSON.stringify(this.bootstrappedAddons));
         return true;
       }
 
-      LOG("No changes found");
+      logger.debug("No changes found");
     }
     catch (e) {
-      ERROR("Error during startup file checks", e);
+      logger.error("Error during startup file checks", e);
     }
 
     // Check that the add-ons list still exists
     let addonsList = FileUtils.getFile(KEY_PROFILEDIR, [FILE_XPI_ADDONS_LIST],
                                        true);
     if (addonsList.exists() == (this.installStates.length == 0)) {
-      LOG("Add-ons list is invalid, rebuilding");
+      logger.debug("Add-ons list is invalid, rebuilding");
       XPIDatabase.writeAddonsList();
     }
 
     return false;
   },
 
   /**
    * Called to test whether this provider supports installing a particular
@@ -3950,38 +3944,38 @@ var XPIProvider = {
     });
   },
 
   /**
    * When the previously selected theme is removed this method will be called
    * to enable the default theme.
    */
   enableDefaultTheme: function XPI_enableDefaultTheme() {
-    LOG("Activating default theme");
+    logger.debug("Activating default theme");
     let addon = XPIDatabase.getVisibleAddonForInternalName(this.defaultSkin);
     if (addon) {
       if (addon.userDisabled) {
         this.updateAddonDisabledState(addon, false);
       }
       else if (!this.extensionsActive) {
         // During startup we may end up trying to enable the default theme when
         // the database thinks it is already enabled (see f.e. bug 638847). In
         // this case just force the theme preferences to be correct
         Services.prefs.setCharPref(PREF_GENERAL_SKINS_SELECTEDSKIN,
                                    addon.internalName);
         this.currentSkin = this.selectedSkin = addon.internalName;
         Prefs.clearUserPref(PREF_DSS_SKIN_TO_SELECT);
         Prefs.clearUserPref(PREF_DSS_SWITCHPENDING);
       }
       else {
-        WARN("Attempting to activate an already active default theme");
+        logger.warn("Attempting to activate an already active default theme");
       }
     }
     else {
-      WARN("Unable to activate the default theme");
+      logger.warn("Unable to activate the default theme");
     }
   },
 
   /**
    * Notified when a preference we're interested in has changed.
    *
    * @see nsIObserver
    */
@@ -4187,26 +4181,26 @@ var XPIProvider = {
     this.addAddonsToCrashReporter();
 
     // Locales only contain chrome and can't have bootstrap scripts
     if (aType == "locale") {
       this.bootstrapScopes[aId] = null;
       return;
     }
 
-    LOG("Loading bootstrap scope from " + aFile.path);
+    logger.debug("Loading bootstrap scope from " + aFile.path);
 
     let principal = Cc["@mozilla.org/systemprincipal;1"].
                     createInstance(Ci.nsIPrincipal);
 
     if (!aFile.exists()) {
       this.bootstrapScopes[aId] =
         new Cu.Sandbox(principal, {sandboxName: aFile.path,
                                    wantGlobalProperties: ["indexedDB"]});
-      ERROR("Attempted to load bootstrap scope from missing directory " + aFile.path);
+      logger.error("Attempted to load bootstrap scope from missing directory " + aFile.path);
       return;
     }
 
     let uri = getURIForResourceInFile(aFile, "bootstrap.js").spec;
     this.bootstrapScopes[aId] =
       new Cu.Sandbox(principal, {sandboxName: uri,
                                  wantGlobalProperties: ["indexedDB"]});
 
@@ -4237,17 +4231,17 @@ var XPIProvider = {
         this.bootstrapScopes[aId].__SCRIPT_URI_SPEC__ = uri;
       }
       Components.utils.evalInSandbox(
         "Components.classes['@mozilla.org/moz/jssubscript-loader;1'] \
                    .createInstance(Components.interfaces.mozIJSSubScriptLoader) \
                    .loadSubScript(__SCRIPT_URI_SPEC__);", this.bootstrapScopes[aId], "ECMAv5");
     }
     catch (e) {
-      WARN("Error loading bootstrap.js for " + aId, e);
+      logger.warn("Error loading bootstrap.js for " + aId, e);
     }
   },
 
   /**
    * Unloads a bootstrap scope by dropping all references to it and then
    * updating the list of active add-ons with the crash reporter.
    *
    * @param  aId
@@ -4282,59 +4276,59 @@ var XPIProvider = {
   callBootstrapMethod: function XPI_callBootstrapMethod(aId, aVersion, aType, aFile,
                                                         aMethod, aReason, aExtraParams) {
     // Never call any bootstrap methods in safe mode
     if (Services.appinfo.inSafeMode)
       return;
 
     let timeStart = new Date();
     if (aMethod == "startup") {
-      LOG("Registering manifest for " + aFile.path);
+      logger.debug("Registering manifest for " + aFile.path);
       Components.manager.addBootstrappedManifestLocation(aFile);
     }
 
     try {
       // Load the scope if it hasn't already been loaded
       if (!(aId in this.bootstrapScopes))
         this.loadBootstrapScope(aId, aFile, aVersion, aType);
 
       // Nothing to call for locales
       if (aType == "locale")
         return;
 
       if (!(aMethod in this.bootstrapScopes[aId])) {
-        WARN("Add-on " + aId + " is missing bootstrap method " + aMethod);
+        logger.warn("Add-on " + aId + " is missing bootstrap method " + aMethod);
         return;
       }
 
       let params = {
         id: aId,
         version: aVersion,
         installPath: aFile.clone(),
         resourceURI: getURIForResourceInFile(aFile, "")
       };
 
       if (aExtraParams) {
         for (let key in aExtraParams) {
           params[key] = aExtraParams[key];
         }
       }
 
-      LOG("Calling bootstrap method " + aMethod + " on " + aId + " version " +
+      logger.debug("Calling bootstrap method " + aMethod + " on " + aId + " version " +
           aVersion);
       try {
         this.bootstrapScopes[aId][aMethod](params, aReason);
       }
       catch (e) {
-        WARN("Exception running bootstrap method " + aMethod + " on " + aId, e);
+        logger.warn("Exception running bootstrap method " + aMethod + " on " + aId, e);
       }
     }
     finally {
       if (aMethod == "shutdown" && aReason != BOOTSTRAP_REASONS.APP_SHUTDOWN) {
-        LOG("Removing manifest for " + aFile.path);
+        logger.debug("Removing manifest for " + aFile.path);
         Components.manager.removeBootstrappedManifestLocation(aFile);
       }
       this.setTelemetry(aId, aMethod + "_MS", new Date() - timeStart);
     }
   },
 
   /**
    * Updates the disabled state for an add-on. Its appDisabled property will be
@@ -4468,17 +4462,17 @@ var XPIProvider = {
 
     if (aAddon._installLocation.locked)
       throw new Error("Cannot uninstall addons from locked install locations");
 
     if ("_hasResourceCache" in aAddon)
       aAddon._hasResourceCache = new Map();
 
     if (aAddon._updateCheck) {
-      LOG("Cancel in-progress update check for " + aAddon.id);
+      logger.debug("Cancel in-progress update check for " + aAddon.id);
       aAddon._updateCheck.cancel();
     }
 
     // Inactive add-ons don't require a restart to uninstall
     let requiresRestart = this.uninstallRequiresRestart(aAddon);
 
     if (requiresRestart) {
       // We create an empty directory in the staging directory to indicate that
@@ -4653,18 +4647,18 @@ function AddonInstall(aInstallLocation, 
   this.existingAddon = aExistingAddon;
   this.error = 0;
   if (aLoadGroup)
     this.window = aLoadGroup.notificationCallbacks
                             .getInterface(Ci.nsIDOMWindow);
   else
     this.window = null;
 
-  this.WARN = WARN;
-  this.LOG = LOG;
+  // Giving each instance of AddonInstall a reference to the logger.
+  this.logger = logger;
 }
 
 AddonInstall.prototype = {
   installLocation: null,
   wrapper: null,
   stream: null,
   crypto: null,
   originalHash: null,
@@ -4725,17 +4719,17 @@ AddonInstall.prototype = {
    * @param  aCallback
    *         The callback to pass the initialised AddonInstall to
    */
   initLocalInstall: function AI_initLocalInstall(aCallback) {
     aCallback = makeSafe(aCallback);
     this.file = this.sourceURI.QueryInterface(Ci.nsIFileURL).file;
 
     if (!this.file.exists()) {
-      WARN("XPI file " + this.file.path + " does not exist");
+      logger.warn("XPI file " + this.file.path + " does not exist");
       this.state = AddonManager.STATE_DOWNLOAD_FAILED;
       this.error = AddonManager.ERROR_NETWORK_FAILURE;
       aCallback(this);
       return;
     }
 
     this.state = AddonManager.STATE_DOWNLOADED;
     this.progress = this.file.fileSize;
@@ -4743,30 +4737,30 @@ AddonInstall.prototype = {
 
     if (this.hash) {
       let crypto = Cc["@mozilla.org/security/hash;1"].
                    createInstance(Ci.nsICryptoHash);
       try {
         crypto.initWithString(this.hash.algorithm);
       }
       catch (e) {
-        WARN("Unknown hash algorithm '" + this.hash.algorithm + "' for addon " + this.sourceURI.spec, e);
+        logger.warn("Unknown hash algorithm '" + this.hash.algorithm + "' for addon " + this.sourceURI.spec, e);
         this.state = AddonManager.STATE_DOWNLOAD_FAILED;
         this.error = AddonManager.ERROR_INCORRECT_HASH;
         aCallback(this);
         return;
       }
 
       let fis = Cc["@mozilla.org/network/file-input-stream;1"].
                 createInstance(Ci.nsIFileInputStream);
       fis.init(this.file, -1, -1, false);
       crypto.updateFromStream(fis, this.file.fileSize);
       let calculatedHash = getHashStringForCrypto(crypto);
       if (calculatedHash != this.hash.data) {
-        WARN("File hash (" + calculatedHash + ") did not match provided hash (" +
+        logger.warn("File hash (" + calculatedHash + ") did not match provided hash (" +
              this.hash.data + ")");
         this.state = AddonManager.STATE_DOWNLOAD_FAILED;
         this.error = AddonManager.ERROR_INCORRECT_HASH;
         aCallback(this);
         return;
       }
     }
 
@@ -4802,17 +4796,17 @@ AddonInstall.prototype = {
                                                      self.wrapper);
 
             aCallback(self);
           }
         });
       });
     }
     catch (e) {
-      WARN("Invalid XPI", e);
+      logger.warn("Invalid XPI", e);
       this.state = AddonManager.STATE_DOWNLOAD_FAILED;
       this.error = AddonManager.ERROR_CORRUPT_FILE;
       aCallback(this);
       return;
     }
   },
 
   /**
@@ -4888,25 +4882,25 @@ AddonInstall.prototype = {
    */
   cancel: function AI_cancel() {
     switch (this.state) {
     case AddonManager.STATE_DOWNLOADING:
       if (this.channel)
         this.channel.cancel(Cr.NS_BINDING_ABORTED);
     case AddonManager.STATE_AVAILABLE:
     case AddonManager.STATE_DOWNLOADED:
-      LOG("Cancelling download of " + this.sourceURI.spec);
+      logger.debug("Cancelling download of " + this.sourceURI.spec);
       this.state = AddonManager.STATE_CANCELLED;
       XPIProvider.removeActiveInstall(this);
       AddonManagerPrivate.callInstallListeners("onDownloadCancelled",
                                                this.listeners, this.wrapper);
       this.removeTemporaryFile();
       break;
     case AddonManager.STATE_INSTALLED:
-      LOG("Cancelling install of " + this.addon.id);
+      logger.debug("Cancelling install of " + this.addon.id);
       let xpi = this.installLocation.getStagingDir();
       xpi.append(this.addon.id + ".xpi");
       flushJarCache(xpi);
       this.installLocation.cleanStagingDir([this.addon.id, this.addon.id + ".xpi",
                                             this.addon.id + ".json"]);
       this.state = AddonManager.STATE_CANCELLED;
       XPIProvider.removeActiveInstall(this);
 
@@ -4951,28 +4945,28 @@ AddonInstall.prototype = {
   },
 
   /**
    * Removes the temporary file owned by this AddonInstall if there is one.
    */
   removeTemporaryFile: function AI_removeTemporaryFile() {
     // Only proceed if this AddonInstall owns its XPI file
     if (!this.ownsTempFile) {
-      this.LOG("removeTemporaryFile: " + this.sourceURI.spec + " does not own temp file");
+      this.logger.debug("removeTemporaryFile: " + this.sourceURI.spec + " does not own temp file");
       return;
     }
 
     try {
-      this.LOG("removeTemporaryFile: " + this.sourceURI.spec + " removing temp file " +
+      this.logger.debug("removeTemporaryFile: " + this.sourceURI.spec + " removing temp file " +
           this.file.path);
       this.file.remove(true);
       this.ownsTempFile = false;
     }
     catch (e) {
-      this.WARN("Failed to remove temporary file " + this.file.path + " for addon " +
+      this.logger.warn("Failed to remove temporary file " + this.file.path + " for addon " +
           this.sourceURI.spec,
           e);
     }
   },
 
   /**
    * Updates the sourceURI and releaseNotesURI values on the Addon being
    * installed by this AddonInstall instance.
@@ -5004,17 +4998,17 @@ AddonInstall.prototype = {
     while (entries.hasMore()) {
       let entryName = entries.getNext();
       var target = getTemporaryFile();
       try {
         aZipReader.extract(entryName, target);
         files.push(target);
       }
       catch (e) {
-        WARN("Failed to extract " + entryName + " from multi-package " +
+        logger.warn("Failed to extract " + entryName + " from multi-package " +
              "XPI", e);
         target.remove(false);
       }
     }
 
     aZipReader.close();
 
     if (files.length == 0) {
@@ -5030,17 +5024,17 @@ AddonInstall.prototype = {
       this.removeTemporaryFile();
       this.file = files.shift();
       this.ownsTempFile = true;
       try {
         addon = loadManifestFromZipFile(this.file);
         break;
       }
       catch (e) {
-        WARN(this.file.leafName + " cannot be installed from multi-package " +
+        logger.warn(this.file.leafName + " cannot be installed from multi-package " +
              "XPI", e);
       }
     }
 
     if (!addon) {
       // No valid add-on was found
       makeSafe(aCallback)();
       return;
@@ -5141,17 +5135,17 @@ AddonInstall.prototype = {
     }
     catch (e) {
       zipreader.close();
       throw e;
     }
 
     let principal = zipreader.getCertificatePrincipal(null);
     if (principal && principal.hasCertificate) {
-      LOG("Verifying XPI signature");
+      logger.debug("Verifying XPI signature");
       if (verifyZipSigning(zipreader, principal)) {
         let x509 = principal.certificate;
         if (x509 instanceof Ci.nsIX509Cert)
           this.certificate = x509;
         if (this.certificate && this.certificate.commonName.length > 0)
           this.certName = this.certificate.commonName;
         else
           this.certName = principal.prettyName;
@@ -5202,32 +5196,32 @@ AddonInstall.prototype = {
 
   /**
    * Starts downloading the add-on's XPI file.
    */
   startDownload: function AI_startDownload() {
     this.state = AddonManager.STATE_DOWNLOADING;
     if (!AddonManagerPrivate.callInstallListeners("onDownloadStarted",
                                                   this.listeners, this.wrapper)) {
-      LOG("onDownloadStarted listeners cancelled installation of addon " + this.sourceURI.spec);
+      logger.debug("onDownloadStarted listeners cancelled installation of addon " + this.sourceURI.spec);
       this.state = AddonManager.STATE_CANCELLED;
       XPIProvider.removeActiveInstall(this);
       AddonManagerPrivate.callInstallListeners("onDownloadCancelled",
                                                this.listeners, this.wrapper)
       return;
     }
 
     // If a listener changed our state then do not proceed with the download
     if (this.state != AddonManager.STATE_DOWNLOADING)
       return;
 
     if (this.channel) {
       // A previous download attempt hasn't finished cleaning up yet, signal
       // that it should restart when complete
-      LOG("Waiting for previous download to complete");
+      logger.debug("Waiting for previous download to complete");
       this.restartDownload = true;
       return;
     }
 
     this.openChannel();
   },
 
   openChannel: function AI_openChannel() {
@@ -5237,17 +5231,17 @@ AddonInstall.prototype = {
       this.file = getTemporaryFile();
       this.ownsTempFile = true;
       this.stream = Cc["@mozilla.org/network/file-output-stream;1"].
                     createInstance(Ci.nsIFileOutputStream);
       this.stream.init(this.file, FileUtils.MODE_WRONLY | FileUtils.MODE_CREATE |
                        FileUtils.MODE_TRUNCATE, FileUtils.PERMS_FILE, 0);
     }
     catch (e) {
-      WARN("Failed to start download for addon " + this.sourceURI.spec, e);
+      logger.warn("Failed to start download for addon " + this.sourceURI.spec, e);
       this.state = AddonManager.STATE_DOWNLOAD_FAILED;
       this.error = AddonManager.ERROR_FILE_ACCESS;
       XPIProvider.removeActiveInstall(this);
       AddonManagerPrivate.callInstallListeners("onDownloadFailed",
                                                this.listeners, this.wrapper);
       return;
     }
 
@@ -5263,17 +5257,17 @@ AddonInstall.prototype = {
       this.channel.notificationCallbacks = this;
       if (this.channel instanceof Ci.nsIHttpChannelInternal)
         this.channel.forceAllowThirdPartyCookie = true;
       this.channel.asyncOpen(listener, null);
 
       Services.obs.addObserver(this, "network:offline-about-to-go-offline", false);
     }
     catch (e) {
-      WARN("Failed to start download for addon " + this.sourceURI.spec, e);
+      logger.warn("Failed to start download for addon " + this.sourceURI.spec, e);
       this.state = AddonManager.STATE_DOWNLOAD_FAILED;
       this.error = AddonManager.ERROR_NETWORK_FAILURE;
       XPIProvider.removeActiveInstall(this);
       AddonManagerPrivate.callInstallListeners("onDownloadFailed",
                                                this.listeners, this.wrapper);
     }
   },
 
@@ -5331,17 +5325,17 @@ AddonInstall.prototype = {
   onStartRequest: function AI_onStartRequest(aRequest, aContext) {
     this.crypto = Cc["@mozilla.org/security/hash;1"].
                   createInstance(Ci.nsICryptoHash);
     if (this.hash) {
       try {
         this.crypto.initWithString(this.hash.algorithm);
       }
       catch (e) {
-        WARN("Unknown hash algorithm '" + this.hash.algorithm + "' for addon " + this.sourceURI.spec, e);
+        logger.warn("Unknown hash algorithm '" + this.hash.algorithm + "' for addon " + this.sourceURI.spec, e);
         this.state = AddonManager.STATE_DOWNLOAD_FAILED;
         this.error = AddonManager.ERROR_INCORRECT_HASH;
         XPIProvider.removeActiveInstall(this);
         AddonManagerPrivate.callInstallListeners("onDownloadFailed",
                                                  this.listeners, this.wrapper);
         aRequest.cancel(Cr.NS_BINDING_ABORTED);
         return;
       }
@@ -5354,17 +5348,17 @@ AddonInstall.prototype = {
 
     this.progress = 0;
     if (aRequest instanceof Ci.nsIChannel) {
       try {
         this.maxProgress = aRequest.contentLength;
       }
       catch (e) {
       }
-      LOG("Download started for " + this.sourceURI.spec + " to file " +
+      logger.debug("Download started for " + this.sourceURI.spec + " to file " +
           this.file.path);
     }
   },
 
   /**
    * The download is complete.
    *
    * @see nsIStreamListener
@@ -5378,17 +5372,17 @@ AddonInstall.prototype = {
     // If the download was cancelled then all events will have already been sent
     if (aStatus == Cr.NS_BINDING_ABORTED) {
       this.removeTemporaryFile();
       if (this.restartDownload)
         this.openChannel();
       return;
     }
 
-    LOG("Download of " + this.sourceURI.spec + " completed.");
+    logger.debug("Download of " + this.sourceURI.spec + " completed.");
 
     if (Components.isSuccessCode(aStatus)) {
       if (!(aRequest instanceof Ci.nsIHttpChannel) || aRequest.requestSucceeded) {
         if (!this.hash && (aRequest instanceof Ci.nsIChannel)) {
           try {
             checkCert(aRequest,
                       !Prefs.getBoolPref(PREF_INSTALL_REQUIREBUILTINCERTS, true));
           }
@@ -5446,31 +5440,31 @@ AddonInstall.prototype = {
    * Notify listeners that the download failed.
    *
    * @param  aReason
    *         Something to log about the failure
    * @param  error
    *         The error code to pass to the listeners
    */
   downloadFailed: function AI_downloadFailed(aReason, aError) {
-    WARN("Download failed", aError);
+    logger.warn("Download failed", aError);
     this.state = AddonManager.STATE_DOWNLOAD_FAILED;
     this.error = aReason;
     XPIProvider.removeActiveInstall(this);
     AddonManagerPrivate.callInstallListeners("onDownloadFailed", this.listeners,
                                              this.wrapper);
 
     // If the listener hasn't restarted the download then remove any temporary
     // file
     if (this.state == AddonManager.STATE_DOWNLOAD_FAILED) {
-      LOG("downloadFailed: removing temp file for " + this.sourceURI.spec);
+      logger.debug("downloadFailed: removing temp file for " + this.sourceURI.spec);
       this.removeTemporaryFile();
     }
     else
-      LOG("downloadFailed: listener changed AddonInstall state for " +
+      logger.debug("downloadFailed: listener changed AddonInstall state for " +
           this.sourceURI.spec + " to " + this.state);
   },
 
   /**
    * Notify listeners that the download completed.
    */
   downloadCompleted: function AI_downloadCompleted() {
     let self = this;
@@ -5533,40 +5527,40 @@ AddonInstall.prototype = {
           aInstall.addon.id == this.addon.id)
         aInstall.cancel();
     }, this);
 
     let isUpgrade = this.existingAddon &&
                     this.existingAddon._installLocation == this.installLocation;
     let requiresRestart = XPIProvider.installRequiresRestart(this.addon);
 
-    LOG("Starting install of " + this.sourceURI.spec);
+    logger.debug("Starting install of " + this.sourceURI.spec);
     AddonManagerPrivate.callAddonListeners("onInstalling",
                                            createWrapper(this.addon),
                                            requiresRestart);
 
     let stagingDir = this.installLocation.getStagingDir();
     let stagedAddon = stagingDir.clone();
 
     Task.spawn((function() {
       let installedUnpacked = 0;
       yield this.installLocation.requestStagingDir();
 
       // First stage the file regardless of whether restarting is necessary
       if (this.addon.unpack || Prefs.getBoolPref(PREF_XPI_UNPACK, false)) {
-        LOG("Addon " + this.addon.id + " will be installed as " +
+        logger.debug("Addon " + this.addon.id + " will be installed as " +
             "an unpacked directory");
         stagedAddon.append(this.addon.id);
         yield removeAsync(stagedAddon);
         yield OS.File.makeDir(stagedAddon.path);
         yield extractFilesAsync(this.file, stagedAddon);
         installedUnpacked = 1;
       }
       else {
-        LOG("Addon " + this.addon.id + " will be installed as " +
+        logger.debug("Addon " + this.addon.id + " will be installed as " +
             "a packed xpi");
         stagedAddon.append(this.addon.id + ".xpi");
         yield removeAsync(stagedAddon);
         yield OS.File.copy(this.file.path, stagedAddon.path);
       }
 
       if (requiresRestart) {
         // Point the add-on to its extracted files as the xpi may get deleted
@@ -5589,17 +5583,17 @@ AddonInstall.prototype = {
           converter.init(stream, "UTF-8", 0, 0x0000);
           converter.writeString(JSON.stringify(this.addon));
         }
         finally {
           converter.close();
           stream.close();
         }
 
-        LOG("Staged install of " + this.sourceURI.spec + " ready; waiting for restart.");
+        logger.debug("Staged install of " + this.sourceURI.spec + " ready; waiting for restart.");
         this.state = AddonManager.STATE_INSTALLED;
         if (isUpgrade) {
           delete this.existingAddon.pendingUpgrade;
           this.existingAddon.pendingUpgrade = this.addon;
         }
         AddonManagerPrivate.callInstallListeners("onInstallEnded",
                                                  this.listeners, this.wrapper,
                                                  createWrapper(this.addon));
@@ -5677,17 +5671,17 @@ AddonInstall.prototype = {
           XPIProvider.callBootstrapMethod(this.addon.id, this.addon.version,
                                           this.addon.type, file, "install",
                                           reason, extraParams);
         }
 
         AddonManagerPrivate.callAddonListeners("onInstalled",
                                                createWrapper(this.addon));
 
-        LOG("Install of " + this.sourceURI.spec + " completed.");
+        logger.debug("Install of " + this.sourceURI.spec + " completed.");
         this.state = AddonManager.STATE_INSTALLED;
         AddonManagerPrivate.callInstallListeners("onInstallEnded",
                                                  this.listeners, this.wrapper,
                                                  createWrapper(this.addon));
 
         if (this.addon.bootstrap) {
           if (this.addon.active) {
             XPIProvider.callBootstrapMethod(this.addon.id, this.addon.version,
@@ -5706,17 +5700,17 @@ AddonInstall.prototype = {
         XPIProvider.setTelemetry(this.addon.id, "scan_items", scanItems);
         let loc = this.addon.defaultLocale;
         if (loc) {
           XPIProvider.setTelemetry(this.addon.id, "name", loc.name);
           XPIProvider.setTelemetry(this.addon.id, "creator", loc.creator);
         }
       }
     }).bind(this)).then(null, (e) => {
-      WARN("Failed to install " + this.file.path + " from " + this.sourceURI.spec, e);
+      logger.warn("Failed to install " + this.file.path + " from " + this.sourceURI.spec, e);
       if (stagedAddon.exists())
         recursiveRemove(stagedAddon);
       this.state = AddonManager.STATE_INSTALL_FAILED;
       this.error = AddonManager.ERROR_FILE_ACCESS;
       XPIProvider.removeActiveInstall(this);
       AddonManagerPrivate.callAddonListeners("onOperationCancelled",
                                              createWrapper(this.addon));
       AddonManagerPrivate.callInstallListeners("onInstallFailed",
@@ -5771,17 +5765,17 @@ AddonInstall.createInstall = function AI
   let location = XPIProvider.installLocationsByName[KEY_APP_PROFILE];
   let url = Services.io.newFileURI(aFile);
 
   try {
     let install = new AddonInstall(location, url);
     install.initLocalInstall(aCallback);
   }
   catch(e) {
-    ERROR("Error creating install", e);
+    logger.error("Error creating install", e);
     makeSafe(aCallback)(null);
   }
 };
 
 /**
  * Creates a new AddonInstall to download and install a URL.
  *
  * @param  aCallback
@@ -5962,17 +5956,17 @@ UpdateChecker.prototype = {
   callListener: function UC_callListener(aMethod, ...aArgs) {
     if (!(aMethod in this.listener))
       return;
 
     try {
       this.listener[aMethod].apply(this.listener, aArgs);
     }
     catch (e) {
-      WARN("Exception calling UpdateListener method " + aMethod, e);
+      logger.warn("Exception calling UpdateListener method " + aMethod, e);
     }
   },
 
   /**
    * Called when AddonUpdateChecker completes the update check
    *
    * @param  updates
    *         The list of update details for the add-on
@@ -6893,31 +6887,31 @@ DirectoryInstallLocation.prototype = {
       try {
         linkedDirectory.initWithPath(line.value);
       }
       catch (e) {
         linkedDirectory.setRelativeDescriptor(aFile.parent, line.value);
       }
 
       if (!linkedDirectory.exists()) {
-        WARN("File pointer " + aFile.path + " points to " + linkedDirectory.path +
+        logger.warn("File pointer " + aFile.path + " points to " + linkedDirectory.path +
              " which does not exist");
         return null;
       }
 
       if (!linkedDirectory.isDirectory()) {
-        WARN("File pointer " + aFile.path + " points to " + linkedDirectory.path +
+        logger.warn("File pointer " + aFile.path + " points to " + linkedDirectory.path +
              " which is not a directory");
         return null;
       }
 
       return linkedDirectory;
     }
 
-    WARN("File pointer " + aFile.path + " does not contain a path");
+    logger.warn("File pointer " + aFile.path + " does not contain a path");
     return null;
   },
 
   /**
    * Finds all the add-ons installed in this location.
    */
   _readAddons: function DirInstallLocation__readAddons() {
     // Use a snapshot of the directory contents to avoid possible issues with
@@ -6933,30 +6927,30 @@ DirectoryInstallLocation.prototype = {
       let directLoad = false;
       if (entry.isFile() &&
           id.substring(id.length - 4).toLowerCase() == ".xpi") {
         directLoad = true;
         id = id.substring(0, id.length - 4);
       }
 
       if (!gIDTest.test(id)) {
-        LOG("Ignoring file entry whose name is not a valid add-on ID: " +
+        logger.debug("Ignoring file entry whose name is not a valid add-on ID: " +
              entry.path);
         continue;
       }
 
       if (entry.isFile() && !directLoad) {
         let newEntry = this._readDirectoryFromFile(entry);
         if (!newEntry) {
-          LOG("Deleting stale pointer file " + entry.path);
+          logger.debug("Deleting stale pointer file " + entry.path);
           try {
             entry.remove(true);
           }
           catch (e) {
-            WARN("Failed to remove stale pointer file " + entry.path, e);
+            logger.warn("Failed to remove stale pointer file " + entry.path, e);
             // Failing to remove the stale pointer file is ignorable
           }
           continue;
         }
 
         entry = newEntry;
         this._linkedAddons.push(id);
       }
@@ -7009,17 +7003,17 @@ DirectoryInstallLocation.prototype = {
     if (this._stagingDirPromise)
       return this._stagingDirPromise;
 
     OS.File.makeDir(this._directory.path);
     let stagepath = OS.Path.join(this._directory.path, DIR_STAGE);
     return this._stagingDirPromise = OS.File.makeDir(stagepath).then(null, (e) => {
       if (e instanceof OS.File.Error && e.becauseExists)
         return;
-      ERROR("Failed to create staging directory", e);
+      logger.error("Failed to create staging directory", e);
       throw e;
     });
   },
 
   releaseStagingDir: function() {
     this._stagingDirLock--;
 
     if (this._stagingDirLock == 0) {
@@ -7059,17 +7053,17 @@ DirectoryInstallLocation.prototype = {
       dirEntries.close();
     }
 
     try {
       setFilePermissions(dir, FileUtils.PERMS_DIRECTORY);
       dir.remove(false);
     }
     catch (e) {
-      WARN("Failed to remove staging dir", e);
+      logger.warn("Failed to remove staging dir", e);
       // Failing to remove the staging directory is ignorable
     }
   },
 
   /**
    * Gets the directory used by old versions for staging XPI and JAR files ready
    * to be installed.
    *
@@ -7154,26 +7148,26 @@ DirectoryInstallLocation.prototype = {
     }
     finally {
       // It isn't ideal if this cleanup fails but it isn't worth rolling back
       // the install because of it.
       try {
         recursiveRemove(trashDir);
       }
       catch (e) {
-        WARN("Failed to remove trash directory when installing " + aId, e);
+        logger.warn("Failed to remove trash directory when installing " + aId, e);
       }
     }
 
     let newFile = this._directory.clone();
     newFile.append(aSource.leafName);
     try {
       newFile.lastModifiedTime = Date.now();
     } catch (e)  {
-      WARN("failed to set lastModifiedTime on " + newFile.path, e);
+      logger.warn("failed to set lastModifiedTime on " + newFile.path, e);
     }
     this._FileToIDMap[newFile.path] = aId;
     this._IDToFileMap[aId] = newFile;
 
     if (aExistingAddonID && aExistingAddonID != aId &&
         aExistingAddonID in this._IDToFileMap) {
       delete this._FileToIDMap[this._IDToFileMap[aExistingAddonID]];
       delete this._IDToFileMap[aExistingAddonID];
@@ -7187,55 +7181,55 @@ DirectoryInstallLocation.prototype = {
    *
    * @param  aId
    *         The ID of the add-on to uninstall
    * @throws if the ID does not match any of the add-ons installed
    */
   uninstallAddon: function DirInstallLocation_uninstallAddon(aId) {
     let file = this._IDToFileMap[aId];
     if (!file) {
-      WARN("Attempted to remove " + aId + " from " +
+      logger.warn("Attempted to remove " + aId + " from " +
            this._name + " but it was already gone");
       return;
     }
 
     file = this._directory.clone();
     file.append(aId);
     if (!file.exists())
       file.leafName += ".xpi";
 
     if (!file.exists()) {
-      WARN("Attempted to remove " + aId + " from " +
+      logger.warn("Attempted to remove " + aId + " from " +
            this._name + " but it was already gone");
 
       delete this._FileToIDMap[file.path];
       delete this._IDToFileMap[aId];
       return;
     }
 
     let trashDir = this.getTrashDir();
 
     if (file.leafName != aId) {
-      LOG("uninstallAddon: flushing jar cache " + file.path + " for addon " + aId);
+      logger.debug("uninstallAddon: flushing jar cache " + file.path + " for addon " + aId);
       flushJarCache(file);
     }
 
     let transaction = new SafeInstallOperation();
 
     try {
       transaction.move(file, trashDir);
     }
     finally {
       // It isn't ideal if this cleanup fails, but it is probably better than
       // rolling back the uninstall at this point
       try {
         recursiveRemove(trashDir);
       }
       catch (e) {
-        WARN("Failed to remove trash directory when uninstalling " + aId, e);
+        logger.warn("Failed to remove trash directory when uninstalling " + aId, e);
       }
     }
 
     delete this._FileToIDMap[file.path];
     delete this._IDToFileMap[aId];
   },
 
   /**
@@ -7355,17 +7349,17 @@ WinRegInstallLocation.prototype = {
     for (let i = 0; i < count; ++i) {
       let id = aKey.getValueName(i);
 
       let file = Cc["@mozilla.org/file/local;1"].
                 createInstance(Ci.nsIFile);
       file.initWithPath(aKey.readStringValue(id));
 
       if (!file.exists()) {
-        WARN("Ignoring missing add-on in " + file.path);
+        logger.warn("Ignoring missing add-on in " + file.path);
         continue;
       }
 
       this._IDToFileMap[id] = file;
       this._FileToIDMap[file.path] = id;
     }
   },
 
--- a/toolkit/mozapps/extensions/internal/XPIProviderUtils.js
+++ b/toolkit/mozapps/extensions/internal/XPIProviderUtils.js
@@ -19,28 +19,22 @@ XPCOMUtils.defineLazyModuleGetter(this, 
                                   "resource://gre/modules/FileUtils.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "DeferredSave",
                                   "resource://gre/modules/DeferredSave.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "Promise",
                                   "resource://gre/modules/Promise.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "OS",
                                   "resource://gre/modules/osfile.jsm");
 
-["LOG", "WARN", "ERROR"].forEach(function(aName) {
-  Object.defineProperty(this, aName, {
-    get: function logFuncGetter () {
-      Cu.import("resource://gre/modules/addons/AddonLogging.jsm");
+Cu.import("resource://gre/modules/Log.jsm");
+const LOGGER_ID = "addons.xpi-utils";
 
-      LogManager.getLogger("addons.xpi-utils", this);
-      return this[aName];
-    },
-    configurable: true
-  });
-}, this);
-
+// Create a new logger for use by the Addons XPI Provider Utils
+// (Requires AddonManager.jsm)
+let logger = Log.repository.getLogger(LOGGER_ID);
 
 const KEY_PROFILEDIR                  = "ProfD";
 const FILE_DATABASE                   = "extensions.sqlite";
 const FILE_JSON_DB                    = "extensions.json";
 const FILE_OLD_DATABASE               = "extensions.rdf";
 const FILE_XPI_ADDONS_LIST            = "extensions.ini";
 
 // The value for this is in Makefile.in
@@ -147,17 +141,17 @@ function getRepositoryAddon(aAddon, aCal
  * Wrap an API-supplied function in an exception handler to make it safe to call
  */
 function makeSafe(aCallback) {
   return function(...aArgs) {
     try {
       aCallback(...aArgs);
     }
     catch(ex) {
-      WARN("XPI Database callback failed", ex);
+      logger.warn("XPI Database callback failed", ex);
     }
   }
 }
 
 /**
  * A helper method to asynchronously call a function on an array
  * of objects, calling a callback when function(x) has been gathered
  * for every element of the array.
@@ -190,17 +184,17 @@ function asyncMap(aObjects, aMethod, aCa
 
   aObjects.map(function asyncMap_each(aObject, aIndex, aArray) {
     try {
       aMethod(aObject, function asyncMap_callback(aResult) {
         asyncMap_gotValue(aIndex, aResult);
       });
     }
     catch (e) {
-      WARN("Async map function failed", e);
+      logger.warn("Async map function failed", e);
       asyncMap_gotValue(aIndex, undefined);
     }
   });
 }
 
 /**
  * A generator to synchronously return result rows from an mozIStorageStatement.
  *
@@ -222,17 +216,17 @@ function resultRows(aStatement) {
  * A helper function to log an SQL error.
  *
  * @param  aError
  *         The storage error code associated with the error
  * @param  aErrorString
  *         An error message
  */
 function logSQLError(aError, aErrorString) {
-  ERROR("SQL error " + aError + ": " + aErrorString);
+  logger.error("SQL error " + aError + ": " + aErrorString);
 }
 
 /**
  * A helper function to log any errors that occur during async statements.
  *
  * @param  aError
  *         A mozIStorageError to log
  */
@@ -437,25 +431,25 @@ this.XPIDatabase = {
 
     let promise = this._deferredSave.saveChanges();
     if (!this._schemaVersionSet) {
       this._schemaVersionSet = true;
       promise.then(
         count => {
           // Update the XPIDB schema version preference the first time we successfully
           // save the database.
-          LOG("XPI Database saved, setting schema version preference to " + DB_SCHEMA);
+          logger.debug("XPI Database saved, setting schema version preference to " + DB_SCHEMA);
           Services.prefs.setIntPref(PREF_DB_SCHEMA, DB_SCHEMA);
           // Reading the DB worked once, so we don't need the load error
           this._loadError = null;
         },
         error => {
           // Need to try setting the schema version again later
           this._schemaVersionSet = false;
-          WARN("Failed to save XPI database", error);
+          logger.warn("Failed to save XPI database", error);
           // this._deferredSave.lastError has the most recent error so we don't
           // need this any more
           this._loadError = null;
         });
     }
   },
 
   flush: function() {
@@ -497,20 +491,20 @@ this.XPIDatabase = {
   getMigrateDataFromSQLITE: function XPIDB_getMigrateDataFromSQLITE() {
     let connection = null;
     let dbfile = FileUtils.getFile(KEY_PROFILEDIR, [FILE_DATABASE], true);
     // Attempt to open the database
     try {
       connection = Services.storage.openUnsharedDatabase(dbfile);
     }
     catch (e) {
-      WARN("Failed to open sqlite database " + dbfile.path + " for upgrade", e);
+      logger.warn("Failed to open sqlite database " + dbfile.path + " for upgrade", e);
       return null;
     }
-    LOG("Migrating data from sqlite");
+    logger.debug("Migrating data from sqlite");
     let migrateData = this.getMigrateDataFromDatabase(connection);
     connection.close();
     return migrateData;
   },
 
   /**
    * Synchronously opens and reads the database file, upgrading from old
    * databases or making a new DB if needed.
@@ -530,17 +524,17 @@ this.XPIDatabase = {
    *         (if false, caller is XPIProvider.checkForChanges() which will rebuild)
    */
   syncLoadDB: function XPIDB_syncLoadDB(aRebuildOnError) {
     this.migrateData = null;
     let fstream = null;
     let data = "";
     try {
       let readTimer = AddonManagerPrivate.simpleTimer("XPIDB_syncRead_MS");
-      LOG("Opening XPI database " + this.jsonFile.path);
+      logger.debug("Opening XPI database " + this.jsonFile.path);
       fstream = Components.classes["@mozilla.org/network/file-input-stream;1"].
               createInstance(Components.interfaces.nsIFileInputStream);
       fstream.init(this.jsonFile, -1, 0, 0);
       let cstream = null;
       try {
         cstream = Components.classes["@mozilla.org/intl/converter-input-stream;1"].
                 createInstance(Components.interfaces.nsIConverterInputStream);
         cstream.init(fstream, "UTF-8", 0, 0);
@@ -550,17 +544,17 @@ this.XPIDatabase = {
             read = cstream.readString(0xffffffff, str); // read as much as we can and put it in str.value
             data += str.value;
           } while (read != 0);
         }
         readTimer.done();
         this.parseDB(data, aRebuildOnError);
       }
       catch(e) {
-        ERROR("Failed to load XPI JSON data from profile", e);
+        logger.error("Failed to load XPI JSON data from profile", e);
         let rebuildTimer = AddonManagerPrivate.simpleTimer("XPIDB_rebuildReadFailed_MS");
         this.rebuildDatabase(aRebuildOnError);
         rebuildTimer.done();
       }
       finally {
         if (cstream)
           cstream.close();
       }
@@ -596,57 +590,57 @@ this.XPIDatabase = {
     let parseTimer = AddonManagerPrivate.simpleTimer("XPIDB_parseDB_MS");
     try {
       // dump("Loaded JSON:\n" + aData + "\n");
       let inputAddons = JSON.parse(aData);
       // Now do some sanity checks on our JSON db
       if (!("schemaVersion" in inputAddons) || !("addons" in inputAddons)) {
         parseTimer.done();
         // Content of JSON file is bad, need to rebuild from scratch
-        ERROR("bad JSON file contents");
+        logger.error("bad JSON file contents");
         AddonManagerPrivate.recordSimpleMeasure("XPIDB_startupError", "badJSON");
         let rebuildTimer = AddonManagerPrivate.simpleTimer("XPIDB_rebuildBadJSON_MS");
         this.rebuildDatabase(aRebuildOnError);
         rebuildTimer.done();
         return;
       }
       if (inputAddons.schemaVersion != DB_SCHEMA) {
         // Handle mismatched JSON schema version. For now, we assume
         // compatibility for JSON data, though we throw away any fields we
         // don't know about (bug 902956)
         AddonManagerPrivate.recordSimpleMeasure("XPIDB_startupError",
                                                 "schemaMismatch-" + inputAddons.schemaVersion);
-        LOG("JSON schema mismatch: expected " + DB_SCHEMA +
+        logger.debug("JSON schema mismatch: expected " + DB_SCHEMA +
             ", actual " + inputAddons.schemaVersion);
         // When we rev the schema of the JSON database, we need to make sure we
         // force the DB to save so that the DB_SCHEMA value in the JSON file and
         // the preference are updated.
       }
       // If we got here, we probably have good data
       // Make AddonInternal instances from the loaded data and save them
       let addonDB = new Map();
       for (let loadedAddon of inputAddons.addons) {
         let newAddon = new DBAddonInternal(loadedAddon);
         addonDB.set(newAddon._key, newAddon);
       };
       parseTimer.done();
       this.addonDB = addonDB;
-      LOG("Successfully read XPI database");
+      logger.debug("Successfully read XPI database");
       this.initialized = true;
     }
     catch(e) {
       // If we catch and log a SyntaxError from the JSON
       // parser, the xpcshell test harness fails the test for us: bug 870828
       parseTimer.done();
       if (e.name == "SyntaxError") {
-        ERROR("Syntax error parsing saved XPI JSON data");
+        logger.error("Syntax error parsing saved XPI JSON data");
         AddonManagerPrivate.recordSimpleMeasure("XPIDB_startupError", "syntax");
       }
       else {
-        ERROR("Failed to load XPI JSON data from profile", e);
+        logger.error("Failed to load XPI JSON data from profile", e);
         AddonManagerPrivate.recordSimpleMeasure("XPIDB_startupError", "other");
       }
       let rebuildTimer = AddonManagerPrivate.simpleTimer("XPIDB_rebuildReadFailed_MS");
       this.rebuildDatabase(aRebuildOnError);
       rebuildTimer.done();
     }
   },
 
@@ -654,17 +648,17 @@ this.XPIDatabase = {
    * Upgrade database from earlier (sqlite or RDF) version if available
    */
   upgradeDB: function(aRebuildOnError) {
     let upgradeTimer = AddonManagerPrivate.simpleTimer("XPIDB_upgradeDB_MS");
     try {
       let schemaVersion = Services.prefs.getIntPref(PREF_DB_SCHEMA);
       if (schemaVersion <= LAST_SQLITE_DB_SCHEMA) {
         // we should have an older SQLITE database
-        LOG("Attempting to upgrade from SQLITE database");
+        logger.debug("Attempting to upgrade from SQLITE database");
         this.migrateData = this.getMigrateDataFromSQLITE();
       }
       else {
         // we've upgraded before but the JSON file is gone, fall through
         // and rebuild from scratch
         AddonManagerPrivate.recordSimpleMeasure("XPIDB_startupError", "dbMissing");
       }
     }
@@ -679,17 +673,17 @@ this.XPIDatabase = {
   },
 
   /**
    * Reconstruct when the DB file exists but is unreadable
    * (for example because read permission is denied)
    */
   rebuildUnreadableDB: function(aError, aRebuildOnError) {
     let rebuildTimer = AddonManagerPrivate.simpleTimer("XPIDB_rebuildUnreadableDB_MS");
-    WARN("Extensions database " + this.jsonFile.path +
+    logger.warn("Extensions database " + this.jsonFile.path +
         " exists but is not readable; rebuilding", aError);
     // Remember the error message until we try and write at least once, so
     // we know at shutdown time that there was a problem
     this._loadError = aError;
     AddonManagerPrivate.recordSimpleMeasure("XPIDB_startupError", "unreadable");
     this.rebuildDatabase(aRebuildOnError);
     rebuildTimer.done();
   },
@@ -703,42 +697,42 @@ this.XPIDatabase = {
    *         in this.addonDB; never rejects.
    */
   asyncLoadDB: function XPIDB_asyncLoadDB() {
     // Already started (and possibly finished) loading
     if (this._dbPromise) {
       return this._dbPromise;
     }
 
-    LOG("Starting async load of XPI database " + this.jsonFile.path);
+    logger.debug("Starting async load of XPI database " + this.jsonFile.path);
     AddonManagerPrivate.recordSimpleMeasure("XPIDB_async_load", XPIProvider.runPhase);
     let readOptions = {
       outExecutionDuration: 0
     };
     return this._dbPromise = OS.File.read(this.jsonFile.path, null, readOptions).then(
       byteArray => {
-        LOG("Async JSON file read took " + readOptions.outExecutionDuration + " MS");
+        logger.debug("Async JSON file read took " + readOptions.outExecutionDuration + " MS");
         AddonManagerPrivate.recordSimpleMeasure("XPIDB_asyncRead_MS",
           readOptions.outExecutionDuration);
         if (this._addonDB) {
-          LOG("Synchronous load completed while waiting for async load");
+          logger.debug("Synchronous load completed while waiting for async load");
           return this.addonDB;
         }
-        LOG("Finished async read of XPI database, parsing...");
+        logger.debug("Finished async read of XPI database, parsing...");
         let decodeTimer = AddonManagerPrivate.simpleTimer("XPIDB_decode_MS");
         let decoder = new TextDecoder();
         let data = decoder.decode(byteArray);
         decodeTimer.done();
         this.parseDB(data, true);
         return this.addonDB;
       })
     .then(null,
       error => {
         if (this._addonDB) {
-          LOG("Synchronous load completed while waiting for async load");
+          logger.debug("Synchronous load completed while waiting for async load");
           return this.addonDB;
         }
         if (error.becauseNoSuchFile) {
           this.upgradeDB(true);
         }
         else {
           // it's there but unreadable
           this.rebuildUnreadableDB(error, true);
@@ -757,32 +751,32 @@ this.XPIDatabase = {
    *         (if false, caller is XPIProvider.checkForChanges() which will rebuild)
    */
   rebuildDatabase: function XIPDB_rebuildDatabase(aRebuildOnError) {
     this.addonDB = new Map();
     this.initialized = true;
 
     if (XPIProvider.installStates && XPIProvider.installStates.length == 0) {
       // No extensions installed, so we're done
-      LOG("Rebuilding XPI database with no extensions");
+      logger.debug("Rebuilding XPI database with no extensions");
       return;
     }
 
     // If there is no migration data then load the list of add-on directories
     // that were active during the last run
     if (!this.migrateData)
       this.activeBundles = this.getActiveBundles();
 
     if (aRebuildOnError) {
-      WARN("Rebuilding add-ons database from installed extensions.");
+      logger.warn("Rebuilding add-ons database from installed extensions.");
       try {
         XPIProvider.processFileChanges(XPIProvider.installStates, {}, false);
       }
       catch (e) {
-        ERROR("Failed to rebuild XPI database from installed extensions", e);
+        logger.error("Failed to rebuild XPI database from installed extensions", e);
       }
       // Make sure to update the active add-ons and add-ons list on shutdown
       Services.prefs.setBoolPref(PREF_PENDING_OPERATIONS, true);
     }
   },
 
   /**
    * Gets the list of file descriptors of active extension directories or XPI
@@ -809,17 +803,17 @@ this.XPIDatabase = {
                          .getService(Ci.nsIINIParserFactory);
       let parser = iniFactory.createINIParser(addonsList);
       let keys = parser.getKeys("ExtensionDirs");
 
       while (keys.hasMore())
         bundles.push(parser.getString("ExtensionDirs", keys.getNext()));
     }
     catch (e) {
-      WARN("Failed to parse extensions.ini", e);
+      logger.warn("Failed to parse extensions.ini", e);
       return null;
     }
 
     // Also include the list of active bootstrapped extensions
     for (let id in XPIProvider.bootstrappedAddons)
       bundles.push(XPIProvider.bootstrappedAddons[id].descriptor);
 
     return bundles;
@@ -833,17 +827,17 @@ this.XPIDatabase = {
    */
   getMigrateDataFromRDF: function XPIDB_getMigrateDataFromRDF(aDbWasMissing) {
 
     // Migrate data from extensions.rdf
     let rdffile = FileUtils.getFile(KEY_PROFILEDIR, [FILE_OLD_DATABASE], true);
     if (!rdffile.exists())
       return null;
 
-    LOG("Migrating data from " + FILE_OLD_DATABASE);
+    logger.debug("Migrating data from " + FILE_OLD_DATABASE);
     let migrateData = {};
 
     try {
       let ds = gRDF.GetDataSourceBlocking(Services.io.newFileURI(rdffile).spec);
       let root = Cc["@mozilla.org/rdf/container;1"].
                  createInstance(Ci.nsIRDFContainer);
       root.Init(ds, gRDF.GetResource(RDFURI_ITEM_ROOT));
       let elements = root.GetElements();
@@ -885,17 +879,17 @@ this.XPIDatabase = {
               appInfo.maxVersion = getRDFProperty(ds, targetApp, "maxVersion");
             }
             migrateData[location][id].targetApplications.push(appInfo);
           }
         }
       }
     }
     catch (e) {
-      WARN("Error reading " + FILE_OLD_DATABASE, e);
+      logger.warn("Error reading " + FILE_OLD_DATABASE, e);
       migrateData = null;
     }
 
     return migrateData;
   },
 
   /**
    * Retrieves migration data from a database that has an older or newer schema.
@@ -925,17 +919,17 @@ this.XPIDatabase = {
           props.push(row.name);
         }
         else if (DB_BOOL_METADATA.indexOf(row.name) != -1) {
           props.push(row.name);
         }
       }
 
       if (reqCount < REQUIRED.length) {
-        ERROR("Unable to read anything useful from the database");
+        logger.error("Unable to read anything useful from the database");
         return null;
       }
       stmt.finalize();
 
       stmt = aConnection.createStatement("SELECT " + props.join(",") + " FROM addon");
       for (let row in resultRows(stmt)) {
         if (!(row.location in migrateData))
           migrateData[row.location] = {};
@@ -970,17 +964,17 @@ this.XPIDatabase = {
               maxVersion: row.maxVersion
             });
           }
         }
       }
     }
     catch (e) {
       // An error here means the schema is too different to read
-      ERROR("Error migrating data", e);
+      logger.error("Error migrating data", e);
       return null;
     }
     finally {
       if (taStmt)
         taStmt.finalize();
       if (stmt)
         stmt.finalize();
     }
@@ -990,17 +984,17 @@ this.XPIDatabase = {
 
   /**
    * Shuts down the database connection and releases all cached objects.
    * Return: Promise{integer} resolves / rejects with the result of the DB
    *                          flush after the database is flushed and
    *                          all cleanup is done
    */
   shutdown: function XPIDB_shutdown() {
-    LOG("shutdown");
+    logger.debug("shutdown");
     if (this.initialized) {
       // If our last database I/O had an error, try one last time to save.
       if (this.lastError)
         this.saveChanges();
 
       this.initialized = false;
 
       if (this._deferredSave) {
@@ -1011,17 +1005,17 @@ this.XPIDatabase = {
         AddonManagerPrivate.recordSimpleMeasure(
             "XPIDB_saves_late", this._deferredSave.dirty ? 1 : 0);
       }
 
       // Return a promise that any pending writes of the DB are complete and we
       // are finished cleaning up
       let flushPromise = this.flush();
       flushPromise.then(null, error => {
-          ERROR("Flush of XPI database failed", error);
+          logger.error("Flush of XPI database failed", error);
           AddonManagerPrivate.recordSimpleMeasure("XPIDB_shutdownFlush_failed", 1);
           // If our last attempt to read or write the DB failed, force a new
           // extensions.ini to be written to disk on the next startup
           Services.prefs.setBoolPref(PREF_PENDING_OPERATIONS, true);
         })
         .then(count => {
           // Clear out the cached addons data loaded from JSON
           delete this.addonDB;
@@ -1068,17 +1062,17 @@ this.XPIDatabase = {
   getAddonList: function(aFilter, aCallback) {
     this.asyncLoadDB().then(
       addonDB => {
         let addonList = _filterDB(addonDB, aFilter);
         asyncMap(addonList, getRepositoryAddon, makeSafe(aCallback));
       })
     .then(null,
         error => {
-          ERROR("getAddonList failed", e);
+          logger.error("getAddonList failed", e);
           makeSafe(aCallback)([]);
         });
   },
 
   /**
    * (Possibly asynchronously) get the first addon that matches the filter function
    * @param  aFilter
    *         Function that takes an addon instance and returns
@@ -1088,17 +1082,17 @@ this.XPIDatabase = {
    */
   getAddon: function(aFilter, aCallback) {
     return this.asyncLoadDB().then(
       addonDB => {
         getRepositoryAddon(_findAddon(addonDB, aFilter), makeSafe(aCallback));
       })
     .then(null,
         error => {
-          ERROR("getAddon failed", e);
+          logger.error("getAddon failed", e);
           makeSafe(aCallback)(null);
         });
   },
 
   /**
    * Synchronously reads all the add-ons in a particular install location.
    * Always called with the addon database already loaded.
    *
@@ -1162,34 +1156,34 @@ this.XPIDatabase = {
    *         The type of add-on to retrieve
    * @return an array of DBAddonInternals
    */
   getAddonsByType: function XPIDB_getAddonsByType(aType) {
     if (!this.addonDB) {
       // jank-tastic! Must synchronously load DB if the theme switches from
       // an XPI theme to a lightweight theme before the DB has loaded,
       // because we're called from sync XPIProvider.addonChanged
-      WARN("Synchronous load of XPI database due to getAddonsByType(" + aType + ")");
+      logger.warn("Synchronous load of XPI database due to getAddonsByType(" + aType + ")");
       AddonManagerPrivate.recordSimpleMeasure("XPIDB_lateOpen_byType", XPIProvider.runPhase);
       this.syncLoadDB(true);
     }
     return _filterDB(this.addonDB, aAddon => (aAddon.type == aType));
   },
 
   /**
    * Synchronously gets an add-on with a particular internalName.
    *
    * @param  aInternalName
    *         The internalName of the add-on to retrieve
    * @return a DBAddonInternal
    */
   getVisibleAddonForInternalName: function XPIDB_getVisibleAddonForInternalName(aInternalName) {
     if (!this.addonDB) {
       // This may be called when the DB hasn't otherwise been loaded
-      WARN("Synchronous load of XPI database due to getVisibleAddonForInternalName");
+      logger.warn("Synchronous load of XPI database due to getVisibleAddonForInternalName");
       AddonManagerPrivate.recordSimpleMeasure("XPIDB_lateOpen_forInternalName",
           XPIProvider.runPhase);
       this.syncLoadDB(true);
     }
     
     return _findAddon(this.addonDB,
                       aAddon => aAddon.visible &&
                                 (aAddon.internalName == aInternalName));
@@ -1316,20 +1310,20 @@ this.XPIDatabase = {
    * instances with the same ID as not visible.
    *
    * @param  aAddon
    *         The DBAddonInternal to make visible
    * @param  callback
    *         A callback to pass the DBAddonInternal to
    */
   makeAddonVisible: function XPIDB_makeAddonVisible(aAddon) {
-    LOG("Make addon " + aAddon._key + " visible");
+    logger.debug("Make addon " + aAddon._key + " visible");
     for (let [, otherAddon] of this.addonDB) {
       if ((otherAddon.id == aAddon.id) && (otherAddon._key != aAddon._key)) {
-        LOG("Hide addon " + otherAddon._key);
+        logger.debug("Hide addon " + otherAddon._key);
         otherAddon.visible = false;
       }
     }
     aAddon.visible = true;
     this.saveChanges();
   },
 
   /**
@@ -1373,34 +1367,34 @@ this.XPIDatabase = {
 
   /**
    * Synchronously updates an add-on's active flag in the database.
    *
    * @param  aAddon
    *         The DBAddonInternal to update
    */
   updateAddonActive: function XPIDB_updateAddonActive(aAddon, aActive) {
-    LOG("Updating active state for add-on " + aAddon.id + " to " + aActive);
+    logger.debug("Updating active state for add-on " + aAddon.id + " to " + aActive);
 
     aAddon.active = aActive;
     this.saveChanges();
   },
 
   /**
    * Synchronously calculates and updates all the active flags in the database.
    */
   updateActiveAddons: function XPIDB_updateActiveAddons() {
     if (!this.addonDB) {
-      WARN("updateActiveAddons called when DB isn't loaded");
+      logger.warn("updateActiveAddons called when DB isn't loaded");
       // force the DB to load
       AddonManagerPrivate.recordSimpleMeasure("XPIDB_lateOpen_updateActive",
           XPIProvider.runPhase);
       this.syncLoadDB(true);
     }
-    LOG("Updating add-on states");
+    logger.debug("Updating add-on states");
     for (let [, addon] of this.addonDB) {
       let newActive = (addon.visible && !addon.userDisabled &&
                       !addon.softDisabled && !addon.appDisabled &&
                       !addon.pendingUninstall);
       if (newActive != addon.active) {
         addon.active = newActive;
         this.saveChanges();
       }
@@ -1467,42 +1461,42 @@ this.XPIDatabase = {
         text += "Extension" + (count++) + "=" + row.descriptor + "\r\n";
         enabledAddons.push(encodeURIComponent(row.id) + ":" +
                            encodeURIComponent(row.version));
       }
       fullCount += count;
     }
 
     if (fullCount > 0) {
-      LOG("Writing add-ons list");
+      logger.debug("Writing add-ons list");
 
       try {
         let addonsListTmp = FileUtils.getFile(KEY_PROFILEDIR, [FILE_XPI_ADDONS_LIST + ".tmp"],
                                               true);
         var fos = FileUtils.openFileOutputStream(addonsListTmp);
         fos.write(text, text.length);
         fos.close();
         addonsListTmp.moveTo(addonsListTmp.parent, FILE_XPI_ADDONS_LIST);
 
         Services.prefs.setCharPref(PREF_EM_ENABLED_ADDONS, enabledAddons.join(","));
       }
       catch (e) {
-        ERROR("Failed to write add-ons list to " + addonsListTmp.parent + "/" +
+        logger.error("Failed to write add-ons list to " + addonsListTmp.parent + "/" +
               FILE_XPI_ADDONS_LIST, e);
         return false;
       }
     }
     else {
       if (addonsList.exists()) {
-        LOG("Deleting add-ons list");
+        logger.debug("Deleting add-ons list");
         try {
           addonsList.remove(false);
         }
         catch (e) {
-          ERROR("Failed to remove " + addonsList.path, e);
+          logger.error("Failed to remove " + addonsList.path, e);
           return false;
         }
       }
 
       Services.prefs.clearUserPref(PREF_EM_ENABLED_ADDONS);
     }
     return true;
   }
--- a/toolkit/xre/glxtest.cpp
+++ b/toolkit/xre/glxtest.cpp
@@ -204,17 +204,17 @@ static void glxtest()
   void* glXBindTexImageEXT = glXGetProcAddress("glXBindTexImageEXT"); 
 
   ///// Get GL vendor/renderer/versions strings /////
   enum { bufsize = 1024 };
   char buf[bufsize];
   const GLubyte *vendorString = glGetString(GL_VENDOR);
   const GLubyte *rendererString = glGetString(GL_RENDERER);
   const GLubyte *versionString = glGetString(GL_VERSION);
-  
+
   if (!vendorString || !rendererString || !versionString)
     fatal_error("glGetString returned null");
 
   int length = snprintf(buf, bufsize,
                         "VENDOR\n%s\nRENDERER\n%s\nVERSION\n%s\nTFP\n%s\n",
                         vendorString,
                         rendererString,
                         versionString,
@@ -224,17 +224,26 @@ static void glxtest()
 
   ///// Clean up. Indeed, the parent process might fail to kill us (e.g. if it doesn't need to check GL info)
   ///// so we might be staying alive for longer than expected, so it's important to consume as little memory as
   ///// possible. Also we want to check that we're able to do that too without generating X errors.
   glXMakeCurrent(dpy, None, nullptr); // must release the GL context before destroying it
   glXDestroyContext(dpy, context);
   XDestroyWindow(dpy, window);
   XFreeColormap(dpy, swa.colormap);
+
+#ifdef NS_FREE_PERMANENT_DATA // conditionally defined in nscore.h, don't forget to #include it above
   XCloseDisplay(dpy);
+#else
+  // This XSync call wanted to be instead:
+  //   XCloseDisplay(dpy);
+  // but this can cause 1-minute stalls on certain setups using Nouveau, see bug 973192
+  XSync(dpy, False);
+#endif
+
   dlclose(libgl);
 
   ///// Finally write data to the pipe
   write(write_end_of_the_pipe, buf, length);
 }
 
 /** \returns true in the child glxtest process, false in the parent process */
 bool fire_glxtest_process()
--- a/widget/xpwidgets/moz.build
+++ b/widget/xpwidgets/moz.build
@@ -21,16 +21,17 @@ UNIFIED_SOURCES += [
     'GfxInfoWebGL.cpp',
     'InputData.cpp',
     'nsBaseAppShell.cpp',
     'nsBaseDragService.cpp',
     'nsBaseScreen.cpp',
     'nsClipboardHelper.cpp',
     'nsClipboardPrivacyHandler.cpp',
     'nsClipboardProxy.cpp',
+    'nsColorPickerProxy.cpp',
     'nsContentProcessWidgetFactory.cpp',
     'nsFilePickerProxy.cpp',
     'nsHTMLFormatConverter.cpp',
     'nsIdleService.cpp',
     'nsIWidgetListener.cpp',
     'nsPrimitiveHelpers.cpp',
     'nsPrintOptionsImpl.cpp',
     'nsPrintSession.cpp',
new file mode 100644
--- /dev/null
+++ b/widget/xpwidgets/nsColorPickerProxy.cpp
@@ -0,0 +1,60 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsColorPickerProxy.h"
+
+#include "mozilla/dom/TabChild.h"
+
+using namespace mozilla::dom;
+
+NS_IMPL_ISUPPORTS1(nsColorPickerProxy, nsIColorPicker)
+
+/* void init (in nsIDOMWindow parent, in AString title, in short mode); */
+NS_IMETHODIMP
+nsColorPickerProxy::Init(nsIDOMWindow* aParent, const nsAString& aTitle,
+                         const nsAString& aInitialColor)
+{
+  TabChild* tabChild = TabChild::GetFrom(aParent);
+  if (!tabChild) {
+    return NS_ERROR_FAILURE;
+  }
+
+  tabChild->SendPColorPickerConstructor(this,
+                                        nsString(aTitle),
+                                        nsString(aInitialColor));
+  NS_ADDREF_THIS();
+  return NS_OK;
+}
+
+/* void open (in nsIColorPickerShownCallback aColorPickerShownCallback); */
+NS_IMETHODIMP
+nsColorPickerProxy::Open(nsIColorPickerShownCallback* aColorPickerShownCallback)
+{
+  NS_ENSURE_STATE(!mCallback);
+  mCallback = aColorPickerShownCallback;
+
+  SendOpen();
+  return NS_OK;
+}
+
+bool
+nsColorPickerProxy::RecvUpdate(const nsString& aColor)
+{
+  if (mCallback) {
+    mCallback->Update(aColor);
+  }
+  return true;
+}
+
+bool
+nsColorPickerProxy::Recv__delete__(const nsString& aColor)
+{
+  if (mCallback) {
+    mCallback->Done(aColor);
+    mCallback = nullptr;
+  }
+  return true;
+}
\ No newline at end of file
new file mode 100644
--- /dev/null
+++ b/widget/xpwidgets/nsColorPickerProxy.h
@@ -0,0 +1,32 @@
+/* -*- 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 nsColorPickerProxy_h
+#define nsColorPickerProxy_h
+
+#include "nsIColorPicker.h"
+
+#include "mozilla/dom/PColorPickerChild.h"
+
+class nsColorPickerProxy MOZ_FINAL : public nsIColorPicker,
+                                     public mozilla::dom::PColorPickerChild
+{
+public:
+  NS_DECL_ISUPPORTS
+  NS_DECL_NSICOLORPICKER
+
+  nsColorPickerProxy() {}
+  ~nsColorPickerProxy() {}
+
+  virtual bool RecvUpdate(const nsString& aColor) MOZ_OVERRIDE;
+  virtual bool Recv__delete__(const nsString& aColor) MOZ_OVERRIDE;
+
+private:
+  nsCOMPtr<nsIColorPickerShownCallback> mCallback;
+  nsString mTitle;
+  nsString mInitialColor;
+};
+
+#endif // nsColorPickerProxy_h
--- a/widget/xpwidgets/nsContentProcessWidgetFactory.cpp
+++ b/widget/xpwidgets/nsContentProcessWidgetFactory.cpp
@@ -3,38 +3,44 @@
  */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "mozilla/ModuleUtils.h"
 #include "nsWidgetsCID.h"
 #include "nsClipboardProxy.h"
+#include "nsColorPickerProxy.h"
 #include "nsFilePickerProxy.h"
 
 using namespace mozilla;
 
 #ifndef MOZ_B2G
 
 NS_GENERIC_FACTORY_CONSTRUCTOR(nsClipboardProxy)
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsColorPickerProxy)
 NS_GENERIC_FACTORY_CONSTRUCTOR(nsFilePickerProxy)
 
 NS_DEFINE_NAMED_CID(NS_CLIPBOARD_CID);
+NS_DEFINE_NAMED_CID(NS_COLORPICKER_CID);
 NS_DEFINE_NAMED_CID(NS_FILEPICKER_CID);
 
 static const mozilla::Module::CIDEntry kWidgetCIDs[] = {
     { &kNS_CLIPBOARD_CID, false, nullptr, nsClipboardProxyConstructor,
       Module::CONTENT_PROCESS_ONLY },
+    { &kNS_COLORPICKER_CID, false, nullptr, nsColorPickerProxyConstructor,
+      Module::CONTENT_PROCESS_ONLY },
     { &kNS_FILEPICKER_CID, false, nullptr, nsFilePickerProxyConstructor,
       Module::CONTENT_PROCESS_ONLY },
     { nullptr }
 };
 
 static const mozilla::Module::ContractIDEntry kWidgetContracts[] = {
     { "@mozilla.org/widget/clipboard;1", &kNS_CLIPBOARD_CID, Module::CONTENT_PROCESS_ONLY },
+    { "@mozilla.org/colorpicker;1", &kNS_COLORPICKER_CID, Module::CONTENT_PROCESS_ONLY },
     { "@mozilla.org/filepicker;1", &kNS_FILEPICKER_CID, Module::CONTENT_PROCESS_ONLY },
     { nullptr }
 };
 
 static const mozilla::Module kWidgetModule = {
     mozilla::Module::kVersion,
     kWidgetCIDs,
     kWidgetContracts
--- a/xpfe/components/autocomplete/resources/content/autocomplete.xml
+++ b/xpfe/components/autocomplete/resources/content/autocomplete.xml
@@ -5,24 +5,24 @@
 
 
 <bindings id="autocompleteBindings"
           xmlns="http://www.mozilla.org/xbl"
           xmlns:html="http://www.w3.org/1999/xhtml"
           xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
           xmlns:xbl="http://www.mozilla.org/xbl">
 
-  <binding id="autocomplete" role="xulcombobox"
+  <binding id="autocomplete" role="xul:combobox"
            extends="chrome://global/content/bindings/textbox.xml#textbox">
     <resources>
       <stylesheet src="chrome://global/content/autocomplete.css"/>
       <stylesheet src="chrome://global/skin/autocomplete.css"/>
     </resources>
 
-    <content sizetopopup="pref">
+    <content>
       <children includes="menupopup"/>
 
       <xul:hbox class="autocomplete-textbox-container" flex="1" align="center">
         <children includes="image|deck|stack|box">
           <xul:image class="autocomplete-icon" allowevents="true"/>
         </children>
 
         <xul:hbox class="textbox-input-box" flex="1" xbl:inherits="context,tooltiptext=inputtooltiptext">
@@ -1623,16 +1623,19 @@
   <binding id="history-dropmarker" extends="chrome://global/content/bindings/general.xml#dropmarker">
 
     <implementation>
       <method name="showPopup">
         <body><![CDATA[
           var textbox = document.getBindingParent(this);
           var kids = textbox.getElementsByClassName("autocomplete-history-popup");
           if (kids.item(0) && textbox.getAttribute("open") != "true") { // Open history popup
+            var w = textbox.boxObject.width;
+            if (w != kids[0].boxObject.width)
+              kids[0].width = w;
             kids[0].showPopup(textbox, -1, -1, "popup", "bottomleft", "topleft");
             textbox.setAttribute("open", "true");
           }
         ]]></body>
       </method>
     </implementation>
 
     <handlers>