Bug 845546 - Port b2g identity implementation to desktop. r=benadida
☠☠ backed out by 56e0d7ad86f2 ☠ ☠
authorJed Parsons <jparsons@mozilla.com>
Wed, 27 Mar 2013 11:19:09 -0400
changeset 136810 6580ca3028de2fe920f66751507066dd226a46ef
parent 136809 4b80fc3424651d3c5f4a55e0db058aac7e87bcbb
child 136811 f76635ab8c2b41bf5f63ab43861e483002fbb008
push idunknown
push userunknown
push dateunknown
reviewersbenadida
bugs845546
milestone22.0a1
Bug 845546 - Port b2g identity implementation to desktop. r=benadida
browser/base/content/browser-identity.js
browser/base/content/browser.css
browser/base/content/urlbarBindings.xml
browser/base/jar.mn
browser/modules/SignInToWebsite.jsm
dom/identity/DOMIdentity.jsm
dom/identity/nsDOMIdentity.js
modules/libpref/src/init/all.js
toolkit/identity/Identity.jsm
toolkit/identity/IdentityProvider.jsm
toolkit/identity/IdentityUtils.jsm
toolkit/identity/LogUtils.jsm
toolkit/identity/MinimalIdentity.jsm
toolkit/identity/RelyingParty.jsm
toolkit/identity/Sandbox.jsm
toolkit/identity/jwcrypto.jsm
toolkit/identity/tests/unit/head_identity.js
toolkit/identity/tests/unit/test_crypto_service.js
toolkit/identity/tests/unit/test_jwcrypto.js
toolkit/identity/tests/unit/test_log_utils.js
toolkit/identity/tests/unit/test_minimalidentity.js
new file mode 100644
--- /dev/null
+++ b/browser/base/content/browser-identity.js
@@ -0,0 +1,149 @@
+/* -*- Mode: js2; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- /
+/* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */
+/* 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/. */
+
+// This JS shim contains the callbacks to fire DOMRequest events for
+// navigator.pay API within the payment processor's scope.
+
+'use strict';
+
+let { classes: Cc, interfaces: Ci, utils: Cu } = Components;
+Cu.import('resource://gre/modules/XPCOMUtils.jsm');
+
+XPCOMUtils.defineLazyGetter(this, "logger", function() {
+  Cu.import('resource://gre/modules/identity/LogUtils.jsm');
+  return getLogger("Identity", "toolkit.identity.debug");
+});
+
+function IdentityShim() {
+  this.isLoaded = false;
+}
+
+IdentityShim.prototype = {
+  init: function IdentityShim_init() {
+    addMessageListener('identity-delegate-watch', this);
+    addMessageListener('identity-delegate-request', this);
+    addMessageListener('identity-delegate-logout', this);
+    sendAsyncMessage('identity-delegate-loaded');
+    logger.log('init().  sent identity-delegate-complete');
+    this.isLoaded = true;
+  },
+
+  uninit: function IdentityShim_uninit() {
+    if (this.isLoaded) {
+      removeMessageListener('identity-delegate-watch', this);
+      removeMessageListener('identity-delegate-request', this);
+      removeMessageListener('identity-delegate-logout', this);
+      sendAsyncMessage('identity-delegate-complete', null);
+      logger.log('uninit().  sent identity-delegate-complete');
+      this.isLoaded = false;
+    }
+  },
+
+  receiveMessage: function IdentityShim_receiveMessage(aMessage) {
+    switch (aMessage.name) {
+      case 'identity-delegate-watch':
+        this.watch(aMessage.json);
+        break;
+      case 'identity-delegate-request':
+        this.request(aMessage.json);
+        break;
+      case 'identity-delegate-logout':
+        this.logout(aMessage.json);
+        break;
+      default:
+        logger.error("received unexpected message:", aMessage.name);
+        break;
+    }
+  },
+
+  _identityDoMethod: function IdentityShim__identityDoMethod(message) {
+    sendAsyncMessage('identity-service-doMethod', message);
+  },
+
+  _close: function IdentityShim__close() {
+    this.uninit();
+  },
+
+  watch: function IdentityShim_watch(options) {
+    logger.log('doInternalWatch: isLoaded:', this.isLoaded, 'options:', options);
+    if (options) {
+      let BrowserID = content.wrappedJSObject.BrowserID;
+      let callback = function(aParams, aInternalParams) {
+        this._identityDoMethod(aParams);
+        if (aParams.method === 'ready') {
+          this._close();
+        }
+      }.bind(this);
+
+      BrowserID.internal.watch(
+        callback,
+        JSON.stringify(options),
+        function(...things) {
+          logger.log('internal watch returned:', things);
+        }
+      );
+    }
+  },
+
+  request: function IdentityShim_request(options) {
+    logger.log('doInternalRequest: isLoaded:', this.isLoaded, 'options:', options);
+    if (options) {
+      var stringifiedOptions = JSON.stringify(options);
+      let callback = function(assertion, internalParams) {
+        logger.log("received assertion:", assertion);
+        internalParams = internalParams || {};
+        if (assertion) {
+          logger.log("got assertion");
+          this._identityDoMethod({
+            method: 'login',
+            assertion: assertion,
+            _internal: options._internal,
+            _internalParams: internalParams});
+        }
+        this._close();
+      }.bind(this);
+
+      logger.log('call get() with origin', options.origin, ', callback', callback, ', and options', stringifiedOptions);
+      content.wrappedJSObject.BrowserID.internal.get(
+        options.origin,
+        callback,
+        stringifiedOptions
+      );
+      logger.log('called get()');
+    }
+  },
+
+  logout: function IdentityShim_logout(options) {
+    logger.log('doInternalLogout: isLoaded:', this.isLoaded, 'options:', options);
+    if (options) {
+      let BrowserID = content.wrappedJSObject.BrowserID;
+      let callback = function() {
+        this._identityDoMethod({method: 'logout', _internal: options._internal});
+        this._close();
+      }.bind(this);
+
+      BrowserID.internal.logout(options.origin, callback);
+    }
+  }
+};
+
+this.shim = null;
+
+addEventListener('DOMContentLoaded', function(e) {
+  content.addEventListener('load', function(e) {
+    logger.log('content loaded');
+    this.shim = new IdentityShim();
+    this.shim.init();
+  });
+});
+
+content.addEventListener('beforeunload', function(e) {
+  if (this.shim) {
+    this.shim.uninit();
+  }
+});
+
+
--- a/browser/base/content/browser.css
+++ b/browser/base/content/browser.css
@@ -262,36 +262,16 @@ panel[noactions] > richlistbox > richlis
 #urlbar[pageproxystate="invalid"] > #urlbar-icons > .urlbar-icon:not(#go-button),
 #urlbar[pageproxystate="valid"] > #urlbar-icons > #go-button,
 #urlbar[pageproxystate="invalid"][focused="true"] > #urlbar-go-button ~ toolbarbutton,
 #urlbar[pageproxystate="valid"] > #urlbar-go-button,
 #urlbar:not([focused="true"]) > #urlbar-go-button {
   visibility: collapse;
 }
 
-#urlbar[pageproxystate="invalid"] > #identity-box > #identity-icon-labels {
-  visibility: collapse;
-}
-
-#urlbar[pageproxystate="invalid"] > #identity-box {
-  pointer-events: none;
-}
-
-#identity-icon-labels {
-  max-width: 18em;
-}
-
-#identity-icon-country-label {
-  direction: ltr;
-}
-
-#identity-box.verifiedIdentity > #identity-icon-labels > #identity-icon-label {
-  -moz-margin-end: 0.25em !important;
-}
-
 #wrapper-search-container > #search-container > #searchbar > .searchbar-textbox > .autocomplete-textbox-container > .textbox-input-box > html|*.textbox-input {
   visibility: hidden;
 }
 
 /* ::::: Unified Back-/Forward Button ::::: */
 #back-button > .toolbarbutton-menu-dropmarker,
 #forward-button > .toolbarbutton-menu-dropmarker {
   display: none;
@@ -353,26 +333,16 @@ window[chromehidden~="toolbar"] toolbar:
   overflow-y: visible !important;
 }
 
 #sync-notifications notification {
   -moz-binding: url("chrome://browser/content/sync/notification.xml#notification");
 }
 %endif
 
-/* Identity UI */
-#identity-popup-content-box.unknownIdentity > #identity-popup-connectedToLabel ,
-#identity-popup-content-box.unknownIdentity > #identity-popup-runByLabel ,
-#identity-popup-content-box.unknownIdentity > #identity-popup-content-host ,
-#identity-popup-content-box.unknownIdentity > #identity-popup-content-owner ,
-#identity-popup-content-box.verifiedIdentity > #identity-popup-connectedToLabel2 ,
-#identity-popup-content-box.verifiedDomain > #identity-popup-connectedToLabel2 {
-  display: none;
-}
-
 /*  Full Screen UI */
 
 #fullscr-toggler {
   height: 1px;
   background: black;
 }
 
 #full-screen-warning-container {
--- a/browser/base/content/urlbarBindings.xml
+++ b/browser/base/content/urlbarBindings.xml
@@ -1117,264 +1117,70 @@
       <method name="onDownloadEnded">
         <body><![CDATA[
           this.updateProgress();
         ]]></body>
       </method>
     </implementation>
   </binding>
 
-  <binding id="identity-request-notification" extends="chrome://global/content/bindings/notification.xml#popup-notification">
-    <content align="start">
+  <binding 
+    id="identity-request-notification" 
+    extends="chrome://global/content/bindings/notification.xml#popup-notification">
 
-      <xul:image class="popup-notification-icon"
-                 xbl:inherits="popupid,src=icon"/>
+    <content>
+      <panel id="persona-container" anonid="persona-container" flex="1" />
+    </content>
 
-      <xul:vbox flex="1">
-        <xul:vbox anonid="identity-deck">
-          <xul:vbox flex="1" pack="center"> <!-- 1: add an email -->
-            <html:input type="email" anonid="email" required="required" size="30"/>
-            <xul:description anonid="newidentitydesc"/>
-            <xul:spacer flex="1"/>
-            <xul:label class="text-link custom-link small-margin" anonid="chooseemail" hidden="true"/>
-          </xul:vbox>
-          <xul:vbox flex="1" hidden="true"> <!-- 2: choose an email -->
-            <xul:description anonid="chooseidentitydesc"/>
-            <xul:radiogroup anonid="identities">
-            </xul:radiogroup>
-            <xul:label class="text-link custom-link" anonid="newemail"/>
-          </xul:vbox>
-        </xul:vbox>
-        <xul:hbox class="popup-notification-button-container"
-                  pack="end" align="center">
-          <xul:label anonid="tos" class="text-link" hidden="true"/>
-          <xul:label anonid="privacypolicy" class="text-link" hidden="true"/>
-          <xul:spacer flex="1"/>
-          <xul:image anonid="throbber" src="chrome://browser/skin/tabbrowser/loading.png"
-                     style="visibility:hidden" width="16" height="16"/>
-          <xul:button anonid="button"
-                      type="menu-button"
-                      class="popup-notification-menubutton"
-                      xbl:inherits="oncommand=buttoncommand,label=buttonlabel,accesskey=buttonaccesskey">
-            <xul:menupopup anonid="menupopup"
-                           xbl:inherits="oncommand=menucommand">
-              <children/>
-              <xul:menuitem class="menuitem-iconic popup-notification-closeitem"
-                            label="&closeNotificationItem.label;"
-                            xbl:inherits="oncommand=closeitemcommand"/>
-            </xul:menupopup>
-          </xul:button>
-        </xul:hbox>
-      </xul:vbox>
-      <xul:vbox pack="start">
-        <xul:toolbarbutton anonid="closebutton"
-                           class="messageCloseButton popup-notification-closebutton tabbable"
-                           xbl:inherits="oncommand=closebuttoncommand"
-                           tooltiptext="&closeNotification.tooltip;"/>
-      </xul:vbox>
-    </content>
-    <implementation>
+    <implementation implements="nsIObserver, nsIDOMEventListener">
       <constructor><![CDATA[
-        // this.notification.options.identity is used to pass identity-specific info to the binding
-        let origin = this.identity.origin
+        dump(" ** in identity xul constructor\n");
+        this.complete = false;
+
+        // Mark outgoing messages with the id of the caller
+        this.messageSubject = Components.classes["@mozilla.org/supports-string;1"]
+          .createInstance(Components.interfaces.nsISupportsString);
+        this.messageSubject.data = this.notification.options.context.id;
 
-        // Populate text
-        this.emailField.placeholder = gNavigatorBundle.
-                                      getString("identity.newIdentity.email.placeholder");
-        this.newIdentityDesc.textContent = gNavigatorBundle.getFormattedString(
-                                             "identity.newIdentity.description", [origin]);
-        this.chooseIdentityDesc.textContent = gNavigatorBundle.getFormattedString(
-                                                "identity.chooseIdentity.description", [origin]);
+        // adopt the iframe and display it in the panel
+        // XXX this does not work in FF3.6 or older
+        let personaIframe = this.notification.options.context.iframe;
+        let node = document.adoptNode(personaIframe);
+        let panel = document.getAnonymousElementByAttribute(this, "anonid", "persona-container");
+        panel.appendChild(node);
 
-        // Show optional terms of service and privacy policy links
-        this._populateLink(this.identity.termsOfService, "tos", "identity.termsOfService");
-        this._populateLink(this.identity.privacyPolicy, "privacypolicy", "identity.privacyPolicy");
-
-        // Populate the list of identities to choose from. The origin is used to provide
-        // better suggestions.
-        let identities = this.SignInToWebsiteUX.getIdentitiesForSite(origin);
-
-        this._populateIdentityList(identities);
+        // The dimensions of the panel are modified dynamically by
+        // SignInToWebsite.jsm.
 
-        if (typeof this.step == "undefined") {
-          // First opening of this notification
-          // Show the add email pane (0) if there are no existing identities otherwise show the list
-          this.step = "result" in identities && identities.result.length ? 1 : 0;
-        } else {
-          // Already opened so restore previous state
-          if (this.identity.typedEmail) {
-            this.emailField.value = this.identity.typedEmail;
-          }
-          if (this.identity.selected) {
-            // If the user already chose an identity then update the UI to reflect that
-            this.onIdentitySelected();
-          }
-          // Update the view for the step
-          this.step = this.step;
-        }
+        dump(" ** Persona host iframe attached to xul panel\n");
 
-        // Fire notification with the chosen identity when main button is clicked
-        this.button.addEventListener("command", this._onButtonCommand.bind(this), true);
+        // Listen to messages from SignInToWebsite.jsm
+        Services.obs.addObserver(this, "identity-delegate-ui-close", false);
 
-        // Do the same if enter is pressed in the email field
-        this.emailField.addEventListener("keypress", function emailFieldKeypress(aEvent) {
-          if (aEvent.keyCode != aEvent.DOM_VK_RETURN)
-            return;
-          this._onButtonCommand(aEvent);
-        }.bind(this));
-
-        this.addEmailLink.value = gNavigatorBundle.getString("identity.newIdentity.label");
-        this.addEmailLink.accessKey = gNavigatorBundle.getString("identity.newIdentity.accessKey");
-        this.addEmailLink.addEventListener("click", function addEmailClick(evt) {
-          this.step = 0;
-        }.bind(this));
-
-        this.chooseEmailLink.value = gNavigatorBundle.getString("identity.chooseIdentity.label");
-        this.chooseEmailLink.hidden = !("result" in identities && identities.result.length);
-        this.chooseEmailLink.addEventListener("click", function chooseEmailClick(evt) {
-          this.step = 1;
-        }.bind(this));
-
-        this.emailField.addEventListener("blur", function onEmailBlur() {
-          this.identity.typedEmail = this.emailField.value;
-        }.bind(this));
+        // message back to SignInToWebsite that we've started
+        // and the flow with the given id can go ahead
+        // XXX we might not need this
+        Services.obs.notifyObservers(this.messageSubject, "identity-delegate-ui-open", null);
       ]]></constructor>
 
-      <field name="SignInToWebsiteUX" readonly="true">
-        let sitw = {};
-        Components.utils.import("resource:///modules/SignInToWebsite.jsm", sitw);
-        sitw.SignInToWebsiteUX;
-      </field>
-
-      <field name="newIdentityDesc" readonly="true">
-        document.getAnonymousElementByAttribute(this, "anonid", "newidentitydesc");
-      </field>
-
-      <field name="chooseIdentityDesc" readonly="true">
-        document.getAnonymousElementByAttribute(this, "anonid", "chooseidentitydesc");
-      </field>
-
-      <field name="identityList" readonly="true">
-        document.getAnonymousElementByAttribute(this, "anonid", "identities");
-      </field>
-
-      <field name="emailField" readonly="true">
-        document.getAnonymousElementByAttribute(this, "anonid", "email");
-      </field>
-
-      <field name="addEmailLink" readonly="true">
-        document.getAnonymousElementByAttribute(this, "anonid", "newemail");
-      </field>
-
-      <field name="chooseEmailLink" readonly="true">
-        document.getAnonymousElementByAttribute(this, "anonid", "chooseemail");
-      </field>
+      <destructor><![CDATA[
+        if (!this.complete) {
+          Services.obs.notifyObservers(this.messageSubject, "identity-delegate-canceled", null);
+        }
+        Services.obs.removeObserver(this, "identity-delegate-ui-close", false);
+      ]]></destructor>
 
-      <field name="throbber" readonly="true">
-        document.getAnonymousElementByAttribute(this, "anonid", "throbber");
-      </field>
-
-      <field name="identity" readonly="true">
-        this.notification.options.identity;
-      </field>
-
-      <!-- persist the state on the identity object so we can re-create the
-           notification state upon re-opening -->
-      <property name="step">
-        <getter>
-          return this.identity.step;
-        </getter>
-        <setter><![CDATA[
-          let deck = document.getAnonymousElementByAttribute(this, "anonid", "identity-deck");
-          for (let i = 0; i < deck.children.length; i++) {
-            deck.children[i].hidden = (val != i);
-          }
-          this.identity.step = val;
-          switch (val) {
-            case 0:
-              this.emailField.focus();
-              break;
-          }]]>
-        </setter>
-      </property>
-
-      <method name="onIdentitySelected">
+      <method name="observe">
+        <parameter name="aSubject"/>
+        <parameter name="aTopic"/>
+        <parameter name="aData"/>
         <body><![CDATA[
-          this.throbber.style.visibility = "visible";
-          this.button.disabled = true;
-          this.emailField.value = this.identity.selected
-          this.emailField.disabled = true;
-          this.identityList.disabled = true;
-        ]]></body>
-      </method>
-
-      <method name="_populateLink">
-        <parameter name="aURL"/>
-        <parameter name="aLinkId"/>
-        <parameter name="aStringId"/>
-        <body><![CDATA[
-          if (aURL) {
-            // Show optional link to aURL
-            let link = document.getAnonymousElementByAttribute(this, "anonid", aLinkId);
-            link.value = gNavigatorBundle.getString(aStringId);
-            link.href = aURL;
-            link.hidden = false;
-          }
-        ]]></body>
-      </method>
-
-      <method name="_populateIdentityList">
-        <parameter name="aIdentities"/>
-        <body><![CDATA[
-          let foundLastUsed = false;
-          let lastUsed = this.identity.selected || aIdentities.lastUsed;
-          for (let id in aIdentities.result) {
-            let label = aIdentities.result[id];
-            let opt = this.identityList.appendItem(label);
-            if (label == lastUsed) {
-              this.identityList.selectedItem = opt;
-              foundLastUsed = true;
-            }
-          }
-          if (!foundLastUsed) {
-            this.identityList.selectedIndex = -1;
-          }
-        ]]></body>
-      </method>
-
-      <method name="_onButtonCommand">
-        <parameter name="aEvent"/>
-        <body><![CDATA[
-          if (aEvent.target != aEvent.currentTarget)
-            return;
-          let chosenId;
-          switch (this.step) {
-            case 0:
-              aEvent.stopPropagation();
-              if (!this.emailField.validity.valid) {
-                this.emailField.focus();
-                return;
-              }
-              chosenId = this.emailField.value;
-              break;
-            case 1:
-              aEvent.stopPropagation();
-              let selectedItem = this.identityList.selectedItem
-              chosenId = selectedItem ? selectedItem.label : null;
-              if (!chosenId)
-                return;
-              break;
-            default:
-              throw new Error("Unknown case");
-              return;
-          }
-          // Actually select the identity
-          this.SignInToWebsiteUX.selectIdentity(this.identity.rpId, chosenId);
-          this.identity.selected = chosenId;
-          this.onIdentitySelected();
+          // The only message we observe is identity-delegate-ui-close
+          this.complete = true;
+          this.notification.remove();
         ]]></body>
       </method>
 
     </implementation>
   </binding>
 
   <binding id="center-item">
     <content align="center">
--- a/browser/base/jar.mn
+++ b/browser/base/jar.mn
@@ -55,16 +55,17 @@ browser.jar:
         content/browser/abouthealthreport/abouthealth.css     (content/abouthealthreport/abouthealth.css)
 #endif
         content/browser/aboutRobots-icon.png          (content/aboutRobots-icon.png)
         content/browser/aboutRobots-widget-left.png   (content/aboutRobots-widget-left.png)
         content/browser/aboutSocialError.xhtml        (content/aboutSocialError.xhtml)
 *       content/browser/browser.css                   (content/browser.css)
 *       content/browser/browser.js                    (content/browser.js)
 *       content/browser/browser.xul                   (content/browser.xul)
+        content/browser/browser-identity.js           (content/browser-identity.js)
 *       content/browser/browser-tabPreviews.xml       (content/browser-tabPreviews.xml)
         content/browser/content.js                    (content/content.js)
         content/browser/newtab/newTab.xul             (content/newtab/newTab.xul)
 *       content/browser/newtab/newTab.js              (content/newtab/newTab.js)
         content/browser/newtab/newTab.css             (content/newtab/newTab.css)
         content/browser/newtab/preload.xhtml          (content/newtab/preload.xhtml)
 *       content/browser/pageinfo/pageInfo.xul         (content/pageinfo/pageInfo.xul)
 *       content/browser/pageinfo/pageInfo.js          (content/pageinfo/pageInfo.js)
--- a/browser/modules/SignInToWebsite.jsm
+++ b/browser/modules/SignInToWebsite.jsm
@@ -1,247 +1,445 @@
 /* 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";
+'use strict';
 
-this.EXPORTED_SYMBOLS = ["SignInToWebsiteUX"];
+this.EXPORTED_SYMBOLS = ['SignInToWebsiteUX'];
 
 const Cc = Components.classes;
 const Ci = Components.interfaces;
 const Cu = Components.utils;
 
-Cu.import("resource://gre/modules/Services.jsm");
-Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import('resource://gre/modules/Services.jsm');
+Cu.import('resource://gre/modules/XPCOMUtils.jsm');
+Cu.import('resource://gre/modules/identity/IdentityUtils.jsm');
+
+const kIdentityScreen = 'https://picl.personatest.org/sign_in#NATIVE';
+const kIdentityFrame = 'https://picl.personatest.org/communication_iframe';
+const kIdentityShim = 'chrome://browser/content/browser-identity.js';
 
-XPCOMUtils.defineLazyModuleGetter(this, "IdentityService",
-                                  "resource://gre/modules/identity/Identity.jsm");
+const PANEL_MIN_HEIGHT = 440;
+const PANEL_MIN_WIDTH = 300;
+
+XPCOMUtils.defineLazyModuleGetter(this, 'IdentityService',
+                                  'resource://gre/modules/identity/MinimalIdentity.jsm');
+
+XPCOMUtils.defineLazyGetter(this, "logger", function() {
+  Cu.import('resource://gre/modules/identity/LogUtils.jsm');
+  return getLogger("Identity", "toolkit.identity.debug");
+});
 
-XPCOMUtils.defineLazyModuleGetter(this, "Logger",
-                                  "resource://gre/modules/identity/LogUtils.jsm");
+/**
+ * Ripped off from the resize watcher in base/content/browser-social.js
+ */
+
+function sizePanelToContent(iframe) {
+  // FIXME: bug 764787: Maybe we can use nsIDOMWindowUtils.getRootBounds() here?
+  let doc = iframe.contentDocument;
+  if (!doc || !doc.body) {
+    return;
+  }
+  let body = doc.body;
 
-function log(...aMessageArgs) {
-  Logger.log.apply(Logger, ["SignInToWebsiteUX"].concat(aMessageArgs));
+  // offsetHeight/Width don't include margins, so account for that.
+  let cs = doc.defaultView.getComputedStyle(body);
+  let computedHeight = parseInt(cs.marginTop) + body.offsetHeight + parseInt(cs.marginBottom);
+  let height = Math.max(computedHeight, PANEL_MIN_HEIGHT);
+  let computedWidth = parseInt(cs.marginLeft) + body.offsetWidth + parseInt(cs.marginRight);
+  let width = Math.max(computedWidth, PANEL_MIN_WIDTH);
+
+  // The panel can only resize vertically; otherwise, we would have to
+  // compensate for leftward or rightward shifts here
+  iframe.style.height = height + "px";
+  iframe.style.width = width + "px";
 }
 
-this.SignInToWebsiteUX = {
+function ResizeWatcher(iframe) {
+  this._mutationObserver = null;
+  this._iframe = iframe;
 
-  init: function SignInToWebsiteUX_init() {
+  this.start();
+}
+
+ResizeWatcher.prototype = {
+  start: function ResizeWatcher_start() {
+    this.stop(); // just in case...
+    let doc = this._iframe.contentDocument;
 
-    /*
-     * bug 793906 - temporarily disabling desktop UI so we can
-     * focus on b2g without worrying about desktop as well
-     *
-    Services.obs.addObserver(this, "identity-request", false);
-    Services.obs.addObserver(this, "identity-auth", false);
-    Services.obs.addObserver(this, "identity-auth-complete", false);
-    Services.obs.addObserver(this, "identity-login-state-changed", false);
-     */
+    this._mutationObserver = new this._iframe.contentWindow.MutationObserver(
+      function(mutations) {
+        sizePanelToContent(this._iframe);
+      }.bind(this));
+
+    // Observe anything that causes the size to change.
+    let config = {
+      attributes: true,
+      characterData: true,
+      childList: true,
+      subtree: true
+    };
+
+    this._mutationObserver.observe(doc, config);
+
+    // and since this may be setup after the load event has fired we do an
+    // initial resize now.
+    sizePanelToContent(this._iframe);
   },
 
-  uninit: function SignInToWebsiteUX_uninit() {
-    /*
-     * As above:
-     * bug 793906 - temporarily disabling desktop UI so we can
-     * focus on b2g without worrying about desktop as well
-     *
-    Services.obs.removeObserver(this, "identity-request");
-    Services.obs.removeObserver(this, "identity-auth");
-    Services.obs.removeObserver(this, "identity-auth-complete");
-    Services.obs.removeObserver(this, "identity-login-state-changed");
-     */
+  stop: function ResizeWatcher_stop() {
+    if (this._mutationObserver) {
+      try {
+        this._mutationObserver.disconnect();
+      } catch (ex) {
+        // may get "TypeError: can't access dead object" which seems strange,
+        // but doesn't seem to indicate a real problem, so ignore it...
+      }
+      this._mutationObserver = null;
+    }
+  }
+};
+
+/**
+ * Return the chrome window and <browser> for the given outer window ID.
+ */
+function getUIForWindowID(aWindowId) {
+  let someWindow = Services.wm.getMostRecentWindow('navigator:browser');
+  if (!someWindow) {
+    logger.error('SignInToWebsiteUX', 'no window');
+    return {};
+  }
+
+  let windowUtils = someWindow.QueryInterface(Ci.nsIInterfaceRequestor)
+                              .getInterface(Ci.nsIDOMWindowUtils);
+  let content = windowUtils.getOuterWindowWithId(aWindowId);
+
+  if (content) {
+    let browser = content.QueryInterface(Ci.nsIInterfaceRequestor)
+                         .getInterface(Ci.nsIWebNavigation)
+                         .QueryInterface(Ci.nsIDocShell).chromeEventHandler;
+    //let browser = someWindow.gBrowser;
+    let chromeWin = browser.ownerDocument.defaultView;
+
+    return {
+      content: content,
+      browser: browser,
+      chromeWin: chromeWin
+    };
+  }
+  logger.error('SignInToWebsiteUX', 'no content');
+
+  return {};
+}
+
+function requestUI(aContext) {
+  logger.log('requestUI for windowId', aContext.id);
+  let UI = getUIForWindowID(aContext.id);
+
+  // message is not shown in the UI but is required
+  let mainAction = {
+    label: UI.chromeWin.gNavigatorBundle.getString('identity.next.label'),
+    accessKey: UI.chromeWin.gNavigatorBundle.getString('identity.next.accessKey'),
+    callback: function() {} // required
+  };
+  let secondaryActions = [];
+  let options = {
+    context: aContext
+  };
+
+  UI.chromeWin.PopupNotifications.show(UI.browser,
+                                       'identity-request', aContext.id,
+                                       'identity-notification-icon', mainAction,
+                                       [], options);
+}
+
+function HostFrame() {
+  this._iframe = null;
+  this._resizeWatcher = null;
+}
+
+HostFrame.prototype = {
+  /*
+   * getIframe - adds iframe to aOptions
+   */
+  getIframe: function HostFrame_getIframe(aOptions, aCallback) {
+    if (this._gotIframe) {
+      logger.error("Can only get iframe once with HostFrame helper");
+      return;
+    }
+
+    this._createIframe(aOptions);
+    aCallback();
   },
 
-  observe: function SignInToWebsiteUX_observe(aSubject, aTopic, aData) {
-    log("observe: received", aTopic, "with", aData, "for", aSubject);
-    let options = null;
-    if (aSubject) {
-      options = aSubject.wrappedJSObject;
+  cleanUp: function HostFrame_cleanUp() {
+    if (this._resizeWatcher) {
+      this._resizeWatcher.stop();
     }
-    switch(aTopic) {
-      case "identity-request":
-        this.requestLogin(options);
-        break;
-      case "identity-auth":
-        this._openAuthenticationUI(aData, options);
+  },
+
+  /*
+   * create an iframe and insert it into aOptions.  If showUI is
+   * true, attach the iframe to a xul panel in the popup notification.
+   * Otherwise attach to a hidden document.
+   */
+  _createIframe: function HostFrame_createIframe(aOptions) {
+    let srcURI = aOptions.showUI ? kIdentityScreen : kIdentityFrame;
+    logger.log('showUI is', aOptions.showUI, 'so iframe src =', srcURI);
+
+    let hiddenDoc = Services.appShell.hiddenDOMWindow.document;
+    this._iframe = hiddenDoc.createElementNS('http://www.w3.org/1999/xhtml', 'iframe');
+
+    this._iframe.setAttribute('mozbrowser', true);
+    this._iframe.setAttribute('mozframetype', 'content');
+    this._iframe.setAttribute('type', 'content');
+    this._iframe.setAttribute('remote', true);
+    this._iframe.setAttribute('id', 'persona-host-frame');
+    this._iframe.setAttribute('src', srcURI);
+
+    // implement a dynamic resize watcher a la Social API
+    this._iframe.style.height = "440px";
+    this._iframe.style.width = "300px";
+
+    aOptions.iframe = this._iframe;
+
+    if (aOptions.showUI) {
+      // synchronous, so we can call _injectShim below with no race condition
+      requestUI(aOptions);
+      this._resizeWatcher = new ResizeWatcher(this._iframe);
+    } else {
+      hiddenDoc.documentElement.appendChild(this._iframe);
+    }
+    this._injectShim(this._iframe);
+  },
+
+  _injectShim: function HostFrame_injectShim(aIframe) {
+    let mm = aIframe.QueryInterface(Ci.nsIFrameLoaderOwner).frameLoader.messageManager;
+    logger.log('loadFrameScript:', kIdentityShim);
+    mm.loadFrameScript(kIdentityShim, true);
+  }
+};
+
+function Pipe(aOptions, aController) {
+  this.options = aOptions;
+  this.controller = aController;
+  this.mm = null;
+  this._closed = false;
+  return this;
+}
+
+Pipe.prototype = {
+  observe: function pipe_observe(aSubject, aTopic, aData) {
+    logger.log('pipe observed', aTopic);
+    switch (aTopic) {
+      case 'identity-delegate-canceled':
+        this._close();
+        this.controller.serviceDoMethod({method: 'cancel'}, this.options.id);
         break;
-      case "identity-auth-complete":
-        this._closeAuthenticationUI(aData);
-        break;
-      case "identity-login-state-changed":
-        let emailAddress = aData;
-        if (emailAddress) {
-          this._removeRequestUI(options);
-          this._showLoggedInUI(emailAddress, options);
-        } else {
-          this._removeLoggedInUI(options);
-        }
-        break;
+
       default:
-        Logger.reportError("SignInToWebsiteUX", "Unknown observer notification:", aTopic);
+        logger.error('pipe observed unexpected topic: ' + aTopic);
         break;
     }
   },
 
-  /**
-   * The website is requesting login so the user must choose an identity to use.
-   */
-  requestLogin: function SignInToWebsiteUX_requestLogin(aOptions) {
-    let windowID = aOptions.rpId;
-    log("requestLogin", aOptions);
-    let [chromeWin, browserEl] = this._getUIForWindowID(windowID);
+  _close: function pipe__delegateClose() {
+    this._closed = true;
+    Services.obs.removeObserver(this, 'identity-delegate-canceled');
+    if (this.mm) {
+      this.mm.removeMessageListener('identity-service-doMethod', this._serviceDoMethod);
+      this.mm.removeMessageListener('identity-delegate-complete', this._delegateComplete);
+      this.mm.removeMessageListener('identity-delegate-loaded', this._delegateLoaded);
+    }
+    let subject = Cc['@mozilla.org/supports-string;1'].createInstance(Ci.nsISupportsString);
+    subject.data = this.options.id;
+    Services.obs.notifyObservers(subject, 'identity-delegate-ui-close', null);
 
-    // message is not shown in the UI but is required
-    let message = aOptions.origin;
-    let mainAction = {
-      label: chromeWin.gNavigatorBundle.getString("identity.next.label"),
-      accessKey: chromeWin.gNavigatorBundle.getString("identity.next.accessKey"),
-      callback: function() {}, // required
-    };
-    let options = {
-      identity: {
-        origin: aOptions.origin,
-      },
-    };
-    let secondaryActions = [];
+    if (typeof this.options.onComplete === 'function') {
+      this.options.onComplete();
+    }
+  },
 
-    // add some extra properties to the notification to store some identity-related state
-    for (let opt in aOptions) {
-      options.identity[opt] = aOptions[opt];
-    }
-    log("requestLogin: rpId: ", options.identity.rpId);
+  _delegateLoaded: function pipe__delegateLoaded() {
+    this.mm.sendAsyncMessage(this.options.message, this.options.rpOptions);
+    //this.resizer = new DynamicResizeWatcher();
+    //this.resizer.start(
+  },
 
-    chromeWin.PopupNotifications.show(browserEl, "identity-request", message,
-                                      "identity-notification-icon", mainAction,
-                                      [], options);
+  _delegateComplete: function pipe__delegateComplete() {
+    this._close();
   },
 
-  /**
-   * Get the list of possible identities to login to the given origin.
-   */
-  getIdentitiesForSite: function SignInToWebsiteUX_getIdentitiesForSite(aOrigin) {
-    return IdentityService.RP.getIdentitiesForSite(aOrigin);
-  },
-
-  /**
-   * User chose a new or existing identity from the doorhanger after a request() call
-   */
-  selectIdentity: function SignInToWebsiteUX_selectIdentity(aRpId, aIdentity) {
-    log("selectIdentity: rpId: ", aRpId, " identity: ", aIdentity);
-    IdentityService.selectIdentity(aRpId, aIdentity);
+  _serviceDoMethod: function pipe__doMethod(aMethodOptions) {
+    let message = aMethodOptions.json;
+    if (typeof message === 'string') {
+      try {
+        message = JSON.parse(message);
+      } catch (err) {
+        logger.error('Bad json message: ' + message);
+        return;
+      }
+    }
+    this.controller.serviceDoMethod(message, this.options.id);
   },
 
-  // Private
-
-  /**
-   * Return the chrome window and <browser> for the given outer window ID.
-   */
-  _getUIForWindowID: function(aWindowID) {
-    let someWindow = Services.wm.getMostRecentWindow("navigator:browser");
-    if (!someWindow) {
-      Logger.reportError("SignInToWebsiteUX", "no window");
-      return [null, null];
+  communicate: function pipe_communicate() {
+    if (this._closed) {
+      logger.error('Cannot communicate with persona frame; pipe already closed');
+      return;
     }
+    Services.obs.addObserver(this, 'identity-delegate-canceled', false);
 
-    let windowUtils = someWindow.QueryInterface(Ci.nsIInterfaceRequestor)
-                                .getInterface(Ci.nsIDOMWindowUtils);
-    let content = windowUtils.getOuterWindowWithId(aWindowID);
+    let frameLoader = this.options.iframe.QueryInterface(Ci.nsIFrameLoaderOwner).frameLoader;
+    if (frameLoader) {
+      this.mm = frameLoader.messageManager;
+      this.mm.addMessageListener('identity-service-doMethod', this._serviceDoMethod.bind(this));
+      this.mm.addMessageListener('identity-delegate-loaded', this._delegateLoaded.bind(this));
+      this.mm.addMessageListener('identity-delegate-complete', this._delegateComplete.bind(this));
+    } else {
+      logger.error('FrameLoader unavailable; Frame did not get attached properly?');
+    }
+  }
+};
 
-    if (content) {
-      let browser = content.QueryInterface(Ci.nsIInterfaceRequestor)
-                           .getInterface(Ci.nsIWebNavigation)
-                           .QueryInterface(Ci.nsIDocShell).chromeEventHandler;
-      let chromeWin = browser.ownerDocument.defaultView;
-      return [chromeWin, browser];
-    }
-    Logger.reportError("SignInToWebsiteUX", "no content");
+this.SignInToWebsiteUX = {
+  init: function SignInToWebsiteUX_init() {
+    this.contexts = {};
+    Services.obs.addObserver(this, 'identity-controller-watch', false);
+    Services.obs.addObserver(this, 'identity-controller-request', false);
+    Services.obs.addObserver(this, 'identity-controller-logout', false);
+    Services.obs.addObserver(this, 'identity-controller-canceled', false);
+  },
 
-    return [null, null];
+  uninit: function SignInToWebsiteUX_uninit() {
+    Services.obs.removeObserver(this, 'identity-controller-watch');
+    Services.obs.removeObserver(this, 'identity-controller-request');
+    Services.obs.removeObserver(this, 'identity-controller-logout');
+    Services.obs.removeObserver(this, 'identity-controller-canceled');
   },
 
-  /**
-   * Open UI with a content frame displaying aAuthURI so that the user can authenticate with their
-   * IDP.  Then tell Identity.jsm the identifier for the window so that it knows that the DOM API
-   * calls are for this authentication flow.
-   */
-  _openAuthenticationUI: function _openAuthenticationUI(aAuthURI, aContext) {
-    // Open a tab/window with aAuthURI with an identifier (aID) attached so that the DOM APIs know this is an auth. window.
-    let chromeWin = Services.wm.getMostRecentWindow('navigator:browser');
-    let features = "chrome=false,width=640,height=480,centerscreen,location=yes,resizable=yes,scrollbars=yes,status=yes";
-    log("aAuthURI: ", aAuthURI);
-    let authWin = Services.ww.openWindow(chromeWin, "about:blank", "", features, null);
-    let windowID = authWin.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindowUtils).outerWindowID;
-    log("authWin outer id: ", windowID);
+  observe: function SignInToWebsiteUX_observe(aSubject, aTopic, aData) {
+    logger.log('controller observed:', aTopic);
+    // XXX need to detect page unload of any of our flows
+    // XXX we get strings from xul, and objects from elsewhere
+    let rpOptions = {};
+    if (aSubject) {
+      if (aSubject.wrappedJSObject) {
+        rpOptions = aSubject.wrappedJSObject;
+      } else {
+        rpOptions = {id: aSubject.QueryInterface(Ci.nsISupportsString).data};
+      }
+    }
+    if (!rpOptions.id) {
+      logger.error('Got a message with no RP id');
+      return;
+    }
+
+    let rpId = rpOptions.id;
+    let UI = getUIForWindowID(rpId);
 
-    let provId = aContext.provId;
-    // Tell the ID service about the id before loading the url
-    IdentityService.IDP.setAuthenticationFlow(windowID, provId);
+    let options = {
+      id: rpOptions.id,
+      rpOptions: rpOptions
+    };
 
-    authWin.location = aAuthURI;
-  },
+    switch (aTopic) {
+      case 'identity-controller-watch':
+        this.doWatch(options);
+        break;
 
-  _closeAuthenticationUI: function _closeAuthenticationUI(aAuthId) {
-    log("_closeAuthenticationUI:", aAuthId);
-    let [chromeWin, browserEl] = this._getUIForWindowID(aAuthId);
-    if (chromeWin)
-      chromeWin.close();
-    else
-      Logger.reportError("SignInToWebsite", "Could not close window with ID", aAuthId);
+      case 'identity-controller-request':
+        this.doRequest(options);
+        break;
+
+      case 'identity-controller-logout':
+        this.doLogout(options);
+        break;
+
+      default:
+        logger.error('SignInToWebsiteUX', 'Unknown observer notification:', aTopic);
+        break;
+    }
   },
 
-  /**
-   * Show a doorhanger indicating the currently logged-in user.
-   */
-  _showLoggedInUI: function _showLoggedInUI(aIdentity, aContext) {
-    let windowID = aContext.rpId;
-    log("_showLoggedInUI for ", windowID);
-    let [chromeWin, browserEl] = this._getUIForWindowID(windowID);
+  serviceDoMethod: function SignInToWebsiteUX_doMethod(aMessage, aId) {
+    logger.log('serviceDoMethod received:', aMessage);
+    switch (aMessage.method) {
+      case 'ready':
+        IdentityService.doReady(aId);
+        break;
 
-    let message = chromeWin.gNavigatorBundle.getFormattedString("identity.loggedIn.description",
-                                                          [aIdentity]);
-    let mainAction = {
-      label: chromeWin.gNavigatorBundle.getString("identity.loggedIn.signOut.label"),
-      accessKey: chromeWin.gNavigatorBundle.getString("identity.loggedIn.signOut.accessKey"),
-      callback: function() {
-        log("sign out callback fired");
-        IdentityService.RP.logout(windowID);
-      },
-    };
-    let secondaryActions = [];
-    let options = {
-      dismissed: true,
-    };
-    let loggedInNot = chromeWin.PopupNotifications.show(browserEl, "identity-logged-in", message,
-                                                  "identity-notification-icon", mainAction,
-                                                  secondaryActions, options);
-    loggedInNot.rpId = windowID;
+      case 'login':
+        if (aMessage._internalParams) {
+          IdentityService.doLogin(aId, aMessage.assertion, aMessage._internalParams);
+        } else {
+          IdentityService.doLogin(aId, aMessage.assertion);
+        }
+        break;
+
+      case 'logout':
+        IdentityService.doLogout(aId);
+        break;
+
+      case 'cancel':
+        IdentityService.doCancel(aId);
+        break;
+
+      default:
+        logger.error('Unknown identity method: ' + aMessage.method);
+        break;
+    }
   },
 
-  /**
-   * Remove the doorhanger indicating the currently logged-in user.
-   */
-  _removeLoggedInUI: function _removeLoggedInUI(aContext) {
-    let windowID = aContext.rpId;
-    log("_removeLoggedInUI for ", windowID);
-    if (!windowID)
-      throw "_removeLoggedInUI: Invalid RP ID";
-    let [chromeWin, browserEl] = this._getUIForWindowID(windowID);
-
-    let loggedInNot = chromeWin.PopupNotifications.getNotification("identity-logged-in", browserEl);
-    if (loggedInNot)
-      chromeWin.PopupNotifications.remove(loggedInNot);
+  cleanUp: function SignInToWebsiteUX_cleanUp(aId) {
+    let context = this.contexts[aId];
+    if (context) {
+      if (context.hostFrame) {
+        context.hostFrame.cleanUp();
+      }
+      if (context.iframe && context.iframe.parentNode) {
+        logger.log("removing iframe from parent node and deleting it");
+        context.iframe.parentNode.removeChild(context.iframe);
+        delete context.iframe;
+      }
+      this.contexts[aId] = {};
+      delete this.contexts[aId];
+    }
   },
 
-  /**
-   * Remove the doorhanger indicating the currently logged-in user.
-   */
-  _removeRequestUI: function _removeRequestUI(aContext) {
-    let windowID = aContext.rpId;
-    log("_removeRequestUI for ", windowID);
-    let [chromeWin, browserEl] = this._getUIForWindowID(windowID);
+  delegate: function SignInToWebsiteUX_delegate(aOptions) {
+    let hostFrame = new HostFrame();
+    hostFrame.getIframe(aOptions, function() {
+      // iframe has been added to aOptions
 
-    let requestNot = chromeWin.PopupNotifications.getNotification("identity-request", browserEl);
-    if (requestNot)
-      chromeWin.PopupNotifications.remove(requestNot);
+      // callback for the pipe when flow is complete
+      aOptions.onComplete = function pipe_onComplete() {
+        this.cleanUp(aOptions.id);
+      }.bind(this);
+
+      // store context and communicate with pipe
+      this.contexts[aOptions.id] = aOptions;
+      this.contexts[aOptions.id].hostFrame = hostFrame;
+
+      let pipe = new Pipe(aOptions, this);
+      pipe.communicate();
+    }.bind(this));
   },
 
+  doWatch: function SignInToWebsiteUX_doWatch(aOptions) {
+    aOptions.message = 'identity-delegate-watch';
+    aOptions.showUI = false;
+    this.delegate(aOptions);
+  },
+
+  doRequest: function SignInToWebsiteUX_doRequest(aOptions) {
+    aOptions.message = 'identity-delegate-request';
+    aOptions.showUI = true;
+    this.delegate(aOptions);
+  },
+
+  doLogout: function SignInToWebsiteUX_doLogout(aOptions) {
+    aOptions.message = 'identity-delegate-logout';
+    aOptions.showUI = false;
+    this.delegate(aOptions);
+  }
 };
--- a/dom/identity/DOMIdentity.jsm
+++ b/dom/identity/DOMIdentity.jsm
@@ -9,33 +9,26 @@ const {classes: Cc, interfaces: Ci, util
 // This is the parent process corresponding to nsDOMIdentity.
 this.EXPORTED_SYMBOLS = ["DOMIdentity"];
 
 Cu.import("resource://gre/modules/Services.jsm");
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 Cu.import("resource://gre/modules/identity/IdentityUtils.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "IdentityService",
-#ifdef MOZ_B2G_VERSION
                                   "resource://gre/modules/identity/MinimalIdentity.jsm");
-#else
-                                  "resource://gre/modules/identity/Identity.jsm");
-#endif
-
-XPCOMUtils.defineLazyModuleGetter(this,
-                                  "Logger",
-                                  "resource://gre/modules/identity/LogUtils.jsm");
 
 XPCOMUtils.defineLazyServiceGetter(this, "ppmm",
                                    "@mozilla.org/parentprocessmessagemanager;1",
                                    "nsIMessageListenerManager");
 
-function log(...aMessageArgs) {
-  Logger.log.apply(Logger, ["DOMIdentity"].concat(aMessageArgs));
-}
+XPCOMUtils.defineLazyGetter(this, "logger", function() {
+  Cu.import('resource://gre/modules/identity/LogUtils.jsm');
+  return getLogger("Identity", "toolkit.identity.debug");
+});
 
 function IDDOMMessage(aOptions) {
   objectCopy(aOptions, this);
 }
 
 function IDPProvisioningContext(aID, aOrigin, aTargetMM) {
   this._id = aID;
   this._origin = aOrigin;
@@ -50,24 +43,24 @@ IDPProvisioningContext.prototype = {
     let message = new IDDOMMessage({id: this.id});
     message.identity = aID;
     message.certDuration = aCertDuration;
     this._mm.sendAsyncMessage("Identity:IDP:CallBeginProvisioningCallback",
                               message);
   },
 
   doGenKeyPairCallback: function IDPPC_doGenKeyPairCallback(aPublicKey) {
-    log("doGenKeyPairCallback");
+    logger.log("doGenKeyPairCallback");
     let message = new IDDOMMessage({id: this.id});
     message.publicKey = aPublicKey;
     this._mm.sendAsyncMessage("Identity:IDP:CallGenKeyPairCallback", message);
   },
 
   doError: function(msg) {
-    log("Provisioning ERROR: " + msg);
+    logger.warning(msg);
   }
 };
 
 function IDPAuthenticationContext(aID, aOrigin, aTargetMM) {
   this._id = aID;
   this._origin = aOrigin;
   this._mm = aTargetMM;
 }
@@ -79,61 +72,63 @@ IDPAuthenticationContext.prototype = {
   doBeginAuthenticationCallback: function IDPAC_doBeginAuthCB(aIdentity) {
     let message = new IDDOMMessage({id: this.id});
     message.identity = aIdentity;
     this._mm.sendAsyncMessage("Identity:IDP:CallBeginAuthenticationCallback",
                               message);
   },
 
   doError: function IDPAC_doError(msg) {
-    log("Authentication ERROR: " + msg);
+    logger.warning(msg);
   }
 };
 
 function RPWatchContext(aOptions, aTargetMM) {
   objectCopy(aOptions, this);
 
   // id and origin are required
   if (! (this.id && this.origin)) {
-    throw new Error("id and origin are required for RP watch context");
+    let err = "id and origin are required for RP watch context";
+    logger.error(err);
+    throw new Error(err);
   }
 
   // default for no loggedInUser is undefined, not null
   this.loggedInUser = aOptions.loggedInUser;
 
   // Maybe internal
   this._internal = aOptions._internal;
 
   this._mm = aTargetMM;
 }
 
 RPWatchContext.prototype = {
   doLogin: function RPWatchContext_onlogin(aAssertion, aMaybeInternalParams) {
-    log("doLogin: " + this.id);
+    logger.log("login id: " + this.id);
     let message = new IDDOMMessage({id: this.id, assertion: aAssertion});
     if (aMaybeInternalParams) {
       message._internalParams = aMaybeInternalParams;
     }
     this._mm.sendAsyncMessage("Identity:RP:Watch:OnLogin", message);
   },
 
   doLogout: function RPWatchContext_onlogout() {
-    log("doLogout: " + this.id);
+    logger.log("logout id: " + this.id);
     let message = new IDDOMMessage({id: this.id});
     this._mm.sendAsyncMessage("Identity:RP:Watch:OnLogout", message);
   },
 
   doReady: function RPWatchContext_onready() {
-    log("doReady: " + this.id);
+    logger.log("ready id: " + this.id);
     let message = new IDDOMMessage({id: this.id});
     this._mm.sendAsyncMessage("Identity:RP:Watch:OnReady", message);
   },
 
   doCancel: function RPWatchContext_oncancel() {
-    log("doCancel: " + this.id);
+    logger.log("cancel id: " + this.id);
     let message = new IDDOMMessage({id: this.id});
     this._mm.sendAsyncMessage("Identity:RP:Watch:OnCancel", message);
   },
 
   doError: function RPWatchContext_onerror(aMessage) {
     log("doError: " + aMessage);
   }
 };
@@ -142,16 +137,18 @@ this.DOMIdentity = {
   // nsIMessageListener
   receiveMessage: function DOMIdentity_receiveMessage(aMessage) {
     let msg = aMessage.json;
 
     // Target is the frame message manager that called us and is
     // used to send replies back to the proper window.
     let targetMM = aMessage.target;
 
+    logger.log("received:", aMessage.name);
+
     switch (aMessage.name) {
       // RP
       case "Identity:RP:Watch":
         this._watch(msg, targetMM);
         break;
       case "Identity:RP:Unwatch":
         this._unwatch(msg, targetMM);
         break;
@@ -212,16 +209,17 @@ this.DOMIdentity = {
              "Identity:RP:Unwatch",
              "child-process-shutdown"],
 
   // Private.
   _init: function DOMIdentity__init() {
     Services.ww.registerNotification(this);
     Services.obs.addObserver(this, "xpcom-shutdown", false);
     this._subscribeListeners();
+    logger.log("DOM identity service initialized");
   },
 
   _subscribeListeners: function DOMIdentity__subscribeListeners() {
     if (!ppmm) return;
     for (let message of this.messages) {
       ppmm.addMessageListener(message, this);
     }
   },
@@ -229,26 +227,28 @@ this.DOMIdentity = {
   _unsubscribeListeners: function DOMIdentity__unsubscribeListeners() {
     for (let message of this.messages) {
       ppmm.removeMessageListener(message, this);
     }
     ppmm = null;
   },
 
   _resetFrameState: function(aContext) {
-    log("_resetFrameState: ", aContext.id);
+    logger.log("_resetFrameState: ", aContext.id);
     if (!aContext._mm) {
-      throw new Error("ERROR: Trying to reset an invalid context");
+      let err = "Trying to reset an invalid context";
+      logger.error(err);
+      throw new Error(err);
     }
     let message = new IDDOMMessage({id: aContext.id});
     aContext._mm.sendAsyncMessage("Identity:ResetState", message);
   },
 
   _watch: function DOMIdentity__watch(message, targetMM) {
-    log("DOMIdentity__watch: " + message.id);
+    logger.log("DOMIdentity__watch: " + message.id);
     // Pass an object with the watch members to Identity.jsm so it can call the
     // callbacks.
     let context = new RPWatchContext(message, targetMM);
     IdentityService.RP.watch(context);
   },
 
   _unwatch: function DOMIdentity_unwatch(message, targetMM) {
     IdentityService.RP.unwatch(message.id, targetMM);
--- a/dom/identity/nsDOMIdentity.js
+++ b/dom/identity/nsDOMIdentity.js
@@ -18,22 +18,28 @@ const PREF_SYNTHETIC_EVENTS_OK = "dom.id
 const MAX_STRING_LENGTH = 2048;
 // Maximum number of times navigator.id.request can be called for a document
 const MAX_RP_CALLS = 100;
 
 Cu.import("resource://gre/modules/Services.jsm");
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 Cu.import("resource://gre/modules/identity/IdentityUtils.jsm");
 
+XPCOMUtils.defineLazyGetter(this, "logger", function() {
+  Cu.import('resource://gre/modules/identity/LogUtils.jsm');
+  return getLogger("Identity", "toolkit.identity.debug");
+});
+
 // This is the child process corresponding to nsIDOMIdentity
 XPCOMUtils.defineLazyServiceGetter(this, "cpmm",
                                    "@mozilla.org/childprocessmessagemanager;1",
                                    "nsIMessageSender");
 
 function nsDOMIdentity(aIdentityInternal) {
+  logger.log("nsDOMIdentity constructor");
   this._identityInternal = aIdentityInternal;
 }
 nsDOMIdentity.prototype = {
   __exposedProps__: {
     // Relying Party (RP)
     watch: 'r',
     request: 'r',
     logout: 'r',
@@ -60,16 +66,17 @@ nsDOMIdentity.prototype = {
     return true;
   },
 
   /**
    * Relying Party (RP) APIs
    */
 
   watch: function nsDOMIdentity_watch(aOptions) {
+    logger.log(aOptions);
     if (this._rpWatcher) {
       throw new Error("navigator.id.watch was already called");
     }
 
     if (!aOptions || typeof(aOptions) !== "object") {
       throw new Error("options argument to watch is required");
     }
 
@@ -84,56 +91,59 @@ nsDOMIdentity.prototype = {
 
     // Optional callback "onready"
     if (aOptions["onready"]
         && typeof(aOptions['onready']) !== "function") {
       throw new Error("onready must be a function");
     }
 
     let message = this.DOMIdentityMessage(aOptions);
+    logger.log(message);
 
     // loggedInUser vs loggedInEmail
     // https://developer.mozilla.org/en-US/docs/DOM/navigator.id.watch
     // This parameter, loggedInUser, was renamed from loggedInEmail in early
     // September, 2012. Both names will continue to work for the time being,
     // but code should be changed to use loggedInUser instead.
     checkRenamed(aOptions, "loggedInEmail", "loggedInUser");
     message["loggedInUser"] = aOptions["loggedInUser"];
 
+    logger.log(message);
     let emailType = typeof(aOptions["loggedInUser"]);
     if (aOptions["loggedInUser"] && aOptions["loggedInUser"] !== "undefined") {
       if (emailType !== "string") {
         throw new Error("loggedInUser must be a String or null");
       }
 
       // TODO: Bug 767610 - check email format.
       // See nsHTMLInputElement::IsValidEmailAddress
       if (aOptions["loggedInUser"].indexOf("@") == -1
           || aOptions["loggedInUser"].length > MAX_STRING_LENGTH) {
         throw new Error("loggedInUser is not valid");
       }
       // Set loggedInUser in this block that "undefined" doesn't get through.
       message.loggedInUser = aOptions.loggedInUser;
     }
-    this._log("loggedInUser: " + message.loggedInUser);
+    logger.log("loggedInUser:", message.loggedInUser);
 
     this._rpWatcher = aOptions;
     this._identityInternal._mm.sendAsyncMessage("Identity:RP:Watch", message);
   },
 
   request: function nsDOMIdentity_request(aOptions) {
+    logger.log(aOptions);
     let util = this._window.QueryInterface(Ci.nsIInterfaceRequestor)
                            .getInterface(Ci.nsIDOMWindowUtils);
 
     // The only time we permit calling of request() outside of a user
     // input handler is when we are handling the (deprecated) get() or
     // getVerifiedEmail() calls, which make use of an RP context
     // marked as _internal.
     if (this.nativeEventsRequired && !util.isHandlingUserInput && !aOptions._internal) {
-      this._log("request: rejecting non-native event");
+      error("request: rejecting non-native event");
       return;
     }
 
     // Has the caller called watch() before this?
     if (!this._rpWatcher) {
       throw new Error("navigator.id.request called before navigator.id.watch");
     }
     if (this._rpCalls > MAX_RP_CALLS) {
@@ -166,16 +176,17 @@ nsDOMIdentity.prototype = {
       }
     }
 
     this._rpCalls++;
     this._identityInternal._mm.sendAsyncMessage("Identity:RP:Request", message);
   },
 
   logout: function nsDOMIdentity_logout() {
+    logger.log("logout");
     if (!this._rpWatcher) {
       throw new Error("navigator.id.logout called before navigator.id.watch");
     }
     if (this._rpCalls > MAX_RP_CALLS) {
       throw new Error("navigator.id.logout called too many times");
     }
 
     this._rpCalls++;
@@ -238,72 +249,72 @@ nsDOMIdentity.prototype = {
       onlogout: function get_onlogout() {},
       onready: function get_onready() {
         self.request(opts);
       }
     });
   },
 
   getVerifiedEmail: function nsDOMIdentity_getVerifiedEmail(aCallback) {
-    Cu.reportError("WARNING: getVerifiedEmail has been deprecated");
+    error("WARNING: getVerifiedEmail has been deprecated");
     this.get(aCallback, {});
   },
 
   /**
    *  Identity Provider (IDP) Provisioning APIs
    */
 
   beginProvisioning: function nsDOMIdentity_beginProvisioning(aCallback) {
-    this._log("beginProvisioning");
+    logger.log("beginProvisioning");
     if (this._beginProvisioningCallback) {
       throw new Error("navigator.id.beginProvisioning already called.");
     }
     if (!aCallback || typeof(aCallback) !== "function") {
       throw new Error("beginProvisioning callback is required.");
     }
 
     this._beginProvisioningCallback = aCallback;
     this._identityInternal._mm.sendAsyncMessage("Identity:IDP:BeginProvisioning",
                                                 this.DOMIdentityMessage());
   },
 
   genKeyPair: function nsDOMIdentity_genKeyPair(aCallback) {
-    this._log("genKeyPair");
+    logger.log("genKeyPair");
     if (!this._beginProvisioningCallback) {
       throw new Error("navigator.id.genKeyPair called outside of provisioning");
     }
     if (this._genKeyPairCallback) {
       throw new Error("navigator.id.genKeyPair already called.");
     }
     if (!aCallback || typeof(aCallback) !== "function") {
       throw new Error("genKeyPair callback is required.");
     }
 
     this._genKeyPairCallback = aCallback;
     this._identityInternal._mm.sendAsyncMessage("Identity:IDP:GenKeyPair",
                                                 this.DOMIdentityMessage());
   },
 
   registerCertificate: function nsDOMIdentity_registerCertificate(aCertificate) {
-    this._log("registerCertificate");
+    logger.log("registerCertificate");
     if (!this._genKeyPairCallback) {
       throw new Error("navigator.id.registerCertificate called outside of provisioning");
     }
     if (this._provisioningEnded) {
       throw new Error("Provisioning already ended");
     }
     this._provisioningEnded = true;
 
     let message = this.DOMIdentityMessage();
     message.cert = aCertificate;
     this._identityInternal._mm.sendAsyncMessage("Identity:IDP:RegisterCertificate", message);
   },
 
   raiseProvisioningFailure: function nsDOMIdentity_raiseProvisioningFailure(aReason) {
-    this._log("raiseProvisioningFailure '" + aReason + "'");
+    logger.log("raiseProvisioningFailure '" + aReason + "'");
     if (this._provisioningEnded) {
       throw new Error("Provisioning already ended");
     }
     if (!aReason || typeof(aReason) != "string") {
       throw new Error("raiseProvisioningFailure reason is required");
     }
     this._provisioningEnded = true;
 
@@ -312,17 +323,17 @@ nsDOMIdentity.prototype = {
     this._identityInternal._mm.sendAsyncMessage("Identity:IDP:ProvisioningFailure", message);
   },
 
   /**
    *  Identity Provider (IDP) Authentication APIs
    */
 
   beginAuthentication: function nsDOMIdentity_beginAuthentication(aCallback) {
-    this._log("beginAuthentication");
+    logger.log("beginAuthentication");
     if (this._beginAuthenticationCallback) {
       throw new Error("navigator.id.beginAuthentication already called.");
     }
     if (typeof(aCallback) !== "function") {
       throw new Error("beginAuthentication callback is required.");
     }
     if (!aCallback || typeof(aCallback) !== "function") {
       throw new Error("beginAuthentication callback is required.");
@@ -400,54 +411,54 @@ nsDOMIdentity.prototype = {
           return;
         }
         this._initializeState();
         Services.obs.notifyObservers(null, "identity-DOM-state-reset", this._id);
         break;
       case "Identity:RP:Watch:OnLogin":
         // Do we have a watcher?
         if (!this._rpWatcher) {
-          this._log("WARNING: Received OnLogin message, but there is no RP watcher");
+          logger.warning("Received OnLogin message, but there is no RP watcher");
           return;
         }
 
         if (this._rpWatcher.onlogin) {
           if (this._rpWatcher._internal) {
             this._rpWatcher.onlogin(msg.assertion, msg._internalParams);
           } else {
             this._rpWatcher.onlogin(msg.assertion);
           }
         }
         break;
       case "Identity:RP:Watch:OnLogout":
         // Do we have a watcher?
         if (!this._rpWatcher) {
-          this._log("WARNING: Received OnLogout message, but there is no RP watcher");
+          logger.warning("Received OnLogout message, but there is no RP watcher");
           return;
         }
 
         if (this._rpWatcher.onlogout) {
           this._rpWatcher.onlogout();
         }
         break;
       case "Identity:RP:Watch:OnReady":
         // Do we have a watcher?
         if (!this._rpWatcher) {
-          this._log("WARNING: Received OnReady message, but there is no RP watcher");
+          logger.warning("Received OnReady message, but there is no RP watcher");
           return;
         }
 
         if (this._rpWatcher.onready) {
           this._rpWatcher.onready();
         }
         break;
       case "Identity:RP:Watch:OnCancel":
         // Do we have a watcher?
         if (!this._rpWatcher) {
-          this._log("WARNING: Received OnCancel message, but there is no RP watcher");
+          logger.warning("Received OnCancel message, but there is no RP watcher");
           return;
         }
 
         if (this._onCancelRequestCallback) {
           this._onCancelRequestCallback();
         }
         break;
       case "Identity:IDP:CallBeginProvisioningCallback":
@@ -457,20 +468,16 @@ nsDOMIdentity.prototype = {
         this._callGenKeyPairCallback(msg);
         break;
       case "Identity:IDP:CallBeginAuthenticationCallback":
         this._callBeginAuthenticationCallback(msg);
         break;
     }
   },
 
-  _log: function nsDOMIdentity__log(msg) {
-    this._identityInternal._log(msg);
-  },
-
   _callGenKeyPairCallback: function nsDOMIdentity__callGenKeyPairCallback(message) {
     // create a pubkey object that works
     let chrome_pubkey = JSON.parse(message.publicKey);
 
     // bunch of stuff to create a proper object in window context
     function genPropDesc(value) {
       return {
         enumerable: true, configurable: true, writable: true, value: value
@@ -520,17 +527,17 @@ nsDOMIdentity.prototype = {
 
     // window origin
     message.origin = this._origin;
 
     return message;
   },
 
   uninit: function DOMIdentity_uninit() {
-    this._log("nsDOMIdentity uninit()");
+    logger.log("unwatch", this._id);
     this._identityInternal._mm.sendAsyncMessage(
       "Identity:RP:Unwatch",
       { id: this._id }
     );
   }
 
 };
 
@@ -593,17 +600,17 @@ nsDOMIdentityInternal.prototype = {
 
     this._identity._init(aWindow);
 
     let util = aWindow.QueryInterface(Ci.nsIInterfaceRequestor)
                       .getInterface(Ci.nsIDOMWindowUtils);
     this._id = util.outerWindowID;
     this._innerWindowID = util.currentInnerWindowID;
 
-    this._log("init was called from " + aWindow.document.location);
+    logger.log("init was called from", aWindow.document.location);
 
     this._mm = cpmm;
 
     // Setup listeners for messages from parent process.
     this._messages = [
       "Identity:ResetState",
       "Identity:RP:Watch:OnLogin",
       "Identity:RP:Watch:OnLogout",
@@ -618,24 +625,16 @@ nsDOMIdentityInternal.prototype = {
     }, this);
 
     // Setup observers so we can remove message listeners.
     Services.obs.addObserver(this, "inner-window-destroyed", false);
 
     return this._identity;
   },
 
-  // Private.
-  _log: function nsDOMIdentityInternal__log(msg) {
-    if (!this._debug) {
-      return;
-    }
-    dump("nsDOMIdentity (" + this._id + "): " + msg + "\n");
-  },
-
   // Component setup.
   classID: Components.ID("{210853d9-2c97-4669-9761-b1ab9cbf57ef}"),
 
   QueryInterface: XPCOMUtils.generateQI(
     [Ci.nsIDOMGlobalPropertyInitializer, Ci.nsIMessageListener]
   ),
 
   classInfo: XPCOMUtils.generateCI({
--- a/modules/libpref/src/init/all.js
+++ b/modules/libpref/src/init/all.js
@@ -379,17 +379,17 @@ pref("toolkit.telemetry.server", "https:
 pref("toolkit.telemetry.server_owner", "Mozilla");
 // Information page about telemetry (temporary ; will be about:telemetry in the end)
 pref("toolkit.telemetry.infoURL", "http://www.mozilla.com/legal/privacy/firefox.html#telemetry");
 // Determines whether full SQL strings are returned when they might contain sensitive info
 // i.e. dynamically constructed SQL strings or SQL executed by addons against addon DBs
 pref("toolkit.telemetry.debugSlowSql", false);
 
 // Identity module
-pref("toolkit.identity.enabled", false);
+pref("dom.identity.enabled", false);
 pref("toolkit.identity.debug", false);
 
 // Enable deprecation warnings.
 pref("devtools.errorconsole.deprecation_warnings", true);
 
 // Disable remote debugging protocol logging
 pref("devtools.debugger.log", false);
 // Disable remote debugging connections
--- a/toolkit/identity/Identity.jsm
+++ b/toolkit/identity/Identity.jsm
@@ -10,31 +10,28 @@ this.EXPORTED_SYMBOLS = ["IdentityServic
 
 const Cu = Components.utils;
 const Ci = Components.interfaces;
 const Cc = Components.classes;
 const Cr = Components.results;
 
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 Cu.import("resource://gre/modules/Services.jsm");
-Cu.import("resource://gre/modules/identity/LogUtils.jsm");
 Cu.import("resource://gre/modules/identity/IdentityStore.jsm");
 Cu.import("resource://gre/modules/identity/RelyingParty.jsm");
 Cu.import("resource://gre/modules/identity/IdentityProvider.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this,
                                   "jwcrypto",
                                   "resource://gre/modules/identity/jwcrypto.jsm");
 
-function log(...aMessageArgs) {
-  Logger.log.apply(Logger, ["core"].concat(aMessageArgs));
-}
-function reportError(...aMessageArgs) {
-  Logger.reportError.apply(Logger, ["core"].concat(aMessageArgs));
-}
+XPCOMUtils.defineLazyGetter(this, "logger", function() {
+  Cu.import('resource://gre/modules/identity/LogUtils.jsm');
+  return getLogger("Identity", "toolkit.identity.debug");
+});
 
 function IDService() {
   Services.obs.addObserver(this, "quit-application-granted", false);
   Services.obs.addObserver(this, "identity-auth-complete", false);
 
   this._store = IdentityStore;
   this.RP = RelyingParty;
   this.IDP = IdentityProvider;
@@ -48,17 +45,17 @@ IDService.prototype = {
       case "quit-application-granted":
         Services.obs.removeObserver(this, "quit-application-granted");
         this.shutdown();
         break;
       case "identity-auth-complete":
         if (!aSubject || !aSubject.wrappedJSObject)
           break;
         let subject = aSubject.wrappedJSObject;
-        log("Auth complete:", aSubject.wrappedJSObject);
+        logger.log("Auth complete:", aSubject.wrappedJSObject);
         // We have authenticated in order to provision an identity.
         // So try again.
         this.selectIdentity(subject.rpId, subject.identity);
         break;
     }
   },
 
   reset: function reset() {
@@ -68,17 +65,17 @@ IDService.prototype = {
     // called here, on RP, on IDP, and on the store.  So you don't
     // need to use this :)
     this._store.reset();
     this.RP.reset();
     this.IDP.reset();
   },
 
   shutdown: function shutdown() {
-    log("shutdown");
+    logger.log("shutdown");
     Services.obs.removeObserver(this, "identity-auth-complete");
     Services.obs.removeObserver(this, "quit-application-granted");
   },
 
   /**
    * Parse an email into username and domain if it is valid, else return null
    */
   parseEmail: function parseEmail(email) {
@@ -112,34 +109,34 @@ IDService.prototype = {
    * @param aRPId
    *        (integer) the id of the doc object obtained in .watch() and
    *                  passed to the UX component.
    *
    * @param aIdentity
    *        (string) the email chosen for login
    */
   selectIdentity: function selectIdentity(aRPId, aIdentity) {
-    log("selectIdentity: RP id:", aRPId, "identity:", aIdentity);
+    logger.log("selectIdentity: RP id:", aRPId, "identity:", aIdentity);
 
     // Get the RP that was stored when watch() was invoked.
     let rp = this.RP._rpFlows[aRPId];
     if (!rp) {
-      reportError("selectIdentity", "Invalid RP id: ", aRPId);
+      logger.warning("selectIdentity", "Invalid RP id: ", aRPId);
       return;
     }
 
     // It's possible that we are in the process of provisioning an
     // identity.
     let provId = rp.provId;
 
     let rpLoginOptions = {
       loggedInUser: aIdentity,
       origin: rp.origin
     };
-    log("selectIdentity: provId:", provId, "origin:", rp.origin);
+    logger.log("selectIdentity: provId:", provId, "origin:", rp.origin);
 
     // Once we have a cert, and once the user is authenticated with the
     // IdP, we can generate an assertion and deliver it to the doc.
     let self = this;
     this.RP._generateAssertion(rp.origin, aIdentity, function hadReadyAssertion(err, assertion) {
       if (!err && assertion) {
         self.RP._doLogin(rp, rpLoginOptions, assertion);
         return;
@@ -170,17 +167,17 @@ IDService.prototype = {
             // We are not authenticated.  If we have already tried to
             // authenticate and failed, then this is a "hard fail" and
             // we give up.  Otherwise we try to authenticate with the
             // IdP.
 
             if (self.IDP._provisionFlows[aProvId].didAuthentication) {
               self.IDP._cleanUpProvisionFlow(aProvId);
               self.RP._cleanUpProvisionFlow(aRPId, aProvId);
-              log("ERROR: selectIdentity: authentication hard fail");
+              logger.error("ERROR: selectIdentity: authentication hard fail");
               rp.doError("Authentication fail.");
               return;
             }
             // Try to authenticate with the IdP.  Note that we do
             // not clean up the provision flow here.  We will continue
             // to use it.
             self.IDP._doAuthentication(aProvId, idpParams);
             return;
@@ -217,17 +214,17 @@ IDService.prototype = {
    */
   _discoverIdentityProvider: function _discoverIdentityProvider(aIdentity, aCallback) {
     // XXX bug 767610 - validate email address call
     // When that is available, we can remove this custom parser
     var parsedEmail = this.parseEmail(aIdentity);
     if (parsedEmail === null) {
       return aCallback("Could not parse email: " + aIdentity);
     }
-    log("_discoverIdentityProvider: identity:", aIdentity, "domain:", parsedEmail.domain);
+    logger.log("_discoverIdentityProvider: identity:", aIdentity, "domain:", parsedEmail.domain);
 
     this._fetchWellKnownFile(parsedEmail.domain, function fetchedWellKnown(err, idpParams) {
       // idpParams includes the pk, authorization url, and
       // provisioning url.
 
       // XXX bug 769861 follow any authority delegations
       // if no well-known at any point in the delegation
       // fall back to browserid.org as IdP
@@ -246,60 +243,60 @@ IDService.prototype = {
    *                 https.
    *
    * @param aCallback
    *
    */
   _fetchWellKnownFile: function _fetchWellKnownFile(aDomain, aCallback, aScheme='https') {
     // XXX bug 769854 make tests https and remove aScheme option
     let url = aScheme + '://' + aDomain + "/.well-known/browserid";
-    log("_fetchWellKnownFile:", url);
+    logger.log("_fetchWellKnownFile:", url);
 
     // this appears to be a more successful way to get at xmlhttprequest (which supposedly will close with a window
     let req = Cc["@mozilla.org/xmlextras/xmlhttprequest;1"]
                 .createInstance(Ci.nsIXMLHttpRequest);
 
     // XXX bug 769865 gracefully handle being off-line
     // XXX bug 769866 decide on how to handle redirects
     req.open("GET", url, true);
     req.responseType = "json";
     req.mozBackgroundRequest = true;
     req.onload = function _fetchWellKnownFile_onload() {
       if (req.status < 200 || req.status >= 400) {
-        log("_fetchWellKnownFile", url, ": server returned status:", req.status);
+        logger.log("_fetchWellKnownFile", url, ": server returned status:", req.status);
         return aCallback("Error");
       }
       try {
         let idpParams = req.response;
 
         // Verify that the IdP returned a valid configuration
         if (! (idpParams.provisioning &&
             idpParams.authentication &&
             idpParams['public-key'])) {
           let errStr= "Invalid well-known file from: " + aDomain;
-          log("_fetchWellKnownFile:", errStr);
+          logger.log("_fetchWellKnownFile:", errStr);
           return aCallback(errStr);
         }
 
         let callbackObj = {
           domain: aDomain,
           idpParams: idpParams,
         };
-        log("_fetchWellKnownFile result: ", callbackObj);
+        logger.log("_fetchWellKnownFile result: ", callbackObj);
         // Yay.  Valid IdP configuration for the domain.
         return aCallback(null, callbackObj);
 
       } catch (err) {
-        reportError("_fetchWellKnownFile", "Bad configuration from", aDomain, err);
+        logger.warning("_fetchWellKnownFile", "Bad configuration from", aDomain, err);
         return aCallback(err.toString());
       }
     };
     req.onerror = function _fetchWellKnownFile_onerror() {
-      log("_fetchWellKnownFile", "ERROR:", req.status, req.statusText);
-      log("ERROR: _fetchWellKnownFile:", err);
+      logger.log("_fetchWellKnownFile", "ERROR:", req.status, req.statusText);
+      logger.error("ERROR: _fetchWellKnownFile:", err);
       return aCallback("Error");
     };
     req.send(null);
   },
 
 };
 
 this.IdentityService = new IDService();
--- a/toolkit/identity/IdentityProvider.jsm
+++ b/toolkit/identity/IdentityProvider.jsm
@@ -8,33 +8,29 @@
 
 const Cu = Components.utils;
 const Ci = Components.interfaces;
 const Cc = Components.classes;
 const Cr = Components.results;
 
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 Cu.import("resource://gre/modules/Services.jsm");
-Cu.import("resource://gre/modules/identity/LogUtils.jsm");
 Cu.import("resource://gre/modules/identity/Sandbox.jsm");
 
 this.EXPORTED_SYMBOLS = ["IdentityProvider"];
 const FALLBACK_PROVIDER = "browserid.org";
 
 XPCOMUtils.defineLazyModuleGetter(this,
                                   "jwcrypto",
                                   "resource://gre/modules/identity/jwcrypto.jsm");
 
-function log(...aMessageArgs) {
-  Logger.log.apply(Logger, ["IDP"].concat(aMessageArgs));
-}
-function reportError(...aMessageArgs) {
-  Logger.reportError.apply(Logger, ["IDP"].concat(aMessageArgs));
-}
-
+XPCOMUtils.defineLazyGetter(this, "logger", function() {
+  Cu.import('resource://gre/modules/identity/LogUtils.jsm');
+  return getLogger("Identity IDP", "toolkit.identity.debug");
+});
 
 function IdentityProviderService() {
   XPCOMUtils.defineLazyModuleGetter(this,
                                     "_store",
                                     "resource://gre/modules/identity/IdentityStore.jsm",
                                     "IdentityStore");
 
   this.reset();
@@ -71,17 +67,17 @@ IdentityProviderService.prototype = {
 
   getProvisionFlow: function getProvisionFlow(aProvId, aErrBack) {
     let provFlow = this._provisionFlows[aProvId];
     if (provFlow) {
       return provFlow;
     }
 
     let err = "No provisioning flow found with id " + aProvId;
-    log("ERROR:", err);
+    logger.warning("ERROR:", err);
     if (typeof aErrBack === 'function') {
       aErrBack(err);
     }
   },
 
   shutdown: function RP_shutdown() {
     this.reset();
 
@@ -117,25 +113,25 @@ IdentityProviderService.prototype = {
    *
    * @param aCallback
    *        (function) callback to invoke on completion
    *                   with first-positional parameter the error.
    */
   _provisionIdentity: function _provisionIdentity(aIdentity, aIDPParams, aProvId, aCallback) {
     let provPath = aIDPParams.idpParams.provisioning;
     let url = Services.io.newURI("https://" + aIDPParams.domain, null, null).resolve(provPath);
-    log("_provisionIdentity: identity:", aIdentity, "url:", url);
+    logger.log("_provisionIdentity: identity:", aIdentity, "url:", url);
 
     // If aProvId is not null, then we already have a flow
     // with a sandbox.  Otherwise, get a sandbox and create a
     // new provision flow.
 
     if (aProvId) {
       // Re-use an existing sandbox
-      log("_provisionIdentity: re-using sandbox in provisioning flow with id:", aProvId);
+      logger.log("_provisionIdentity: re-using sandbox in provisioning flow with id:", aProvId);
       this._provisionFlows[aProvId].provisioningSandbox.reload();
 
     } else {
       this._createProvisioningSandbox(url, function createdSandbox(aSandbox) {
         // create a provisioning flow, using the sandbox id, and
         // stash callback associated with this provisioning workflow.
 
         let provId = aSandbox.id;
@@ -144,17 +140,17 @@ IdentityProviderService.prototype = {
           idpParams: aIDPParams,
           securityLevel: this.securityLevel,
           provisioningSandbox: aSandbox,
           callback: function doCallback(aErr) {
             aCallback(aErr, provId);
           },
         };
 
-        log("_provisionIdentity: Created sandbox and provisioning flow with id:", provId);
+        logger.log("_provisionIdentity: Created sandbox and provisioning flow with id:", provId);
         // XXX bug 769862 - provisioning flow should timeout after N seconds
 
       }.bind(this));
     }
   },
 
   // DOM Methods
   /**
@@ -162,17 +158,17 @@ IdentityProviderService.prototype = {
    *
    * @param aCaller
    *        (object)  the iframe sandbox caller with all callbacks and
    *                  other information.  Callbacks include:
    *                  - doBeginProvisioningCallback(id, duration_s)
    *                  - doGenKeyPairCallback(pk)
    */
   beginProvisioning: function beginProvisioning(aCaller) {
-    log("beginProvisioning:", aCaller.id);
+    logger.log("beginProvisioning:", aCaller.id);
 
     // Expect a flow for this caller already to be underway.
     let provFlow = this.getProvisionFlow(aCaller.id, aCaller.doError);
 
     // keep the caller object around
     provFlow.caller = aCaller;
 
     let identity = provFlow.identity;
@@ -194,17 +190,17 @@ IdentityProviderService.prototype = {
    * the provisioning iframe sandbox has called
    * navigator.id.raiseProvisioningFailure()
    *
    * @param aProvId
    *        (int)  the identifier of the provisioning flow tied to that sandbox
    * @param aReason
    */
   raiseProvisioningFailure: function raiseProvisioningFailure(aProvId, aReason) {
-    reportError("Provisioning failure", aReason);
+    logger.warning("Provisioning failure", aReason);
 
     // look up the provisioning caller and its callback
     let provFlow = this.getProvisionFlow(aProvId);
 
     // Sandbox is deleted in _cleanUpProvisionFlow in case we re-use it.
 
     // This may be either a "soft" or "hard" fail.  If it's a
     // soft fail, we'll flow through setAuthenticationFlow, where
@@ -227,35 +223,35 @@ IdentityProviderService.prototype = {
    * the beginProvisioning() call first.
    */
   genKeyPair: function genKeyPair(aProvId) {
     // Look up the provisioning caller and make sure it's valid.
     let provFlow = this.getProvisionFlow(aProvId);
 
     if (!provFlow.didBeginProvisioning) {
       let errStr = "ERROR: genKeyPair called before beginProvisioning";
-      log(errStr);
+      logger.warning(errStr);
       provFlow.callback(errStr);
       return;
     }
 
     // Ok generate a keypair
     jwcrypto.generateKeyPair(jwcrypto.ALGORITHMS.DS160, function gkpCb(err, kp) {
-      log("in gkp callback");
+      logger.log("in gkp callback");
       if (err) {
-        log("ERROR: genKeyPair:", err);
+        logger.error("ERROR: genKeyPair:", err);
         provFlow.callback(err);
         return;
       }
 
       provFlow.kp = kp;
 
       // Serialize the publicKey of the keypair and send it back to the
       // sandbox.
-      log("genKeyPair: generated keypair for provisioning flow with id:", aProvId);
+      logger.log("genKeyPair: generated keypair for provisioning flow with id:", aProvId);
       provFlow.caller.doGenKeyPairCallback(provFlow.kp.serializedPublicKey);
     }.bind(this));
   },
 
   /**
    * When navigator.id.registerCertificate is called from provisioning iframe
    * sandbox.
    *
@@ -266,28 +262,28 @@ IdentityProviderService.prototype = {
    *        (integer) the identifier of the provisioning caller tied to that
    *                  sandbox
    *
    * @param aCert
    *        (String)  A JWT representing the signed certificate for the user
    *                  being provisioned, provided by the IdP.
    */
   registerCertificate: function registerCertificate(aProvId, aCert) {
-    log("registerCertificate:", aProvId, aCert);
+    logger.log("registerCertificate:", aProvId, aCert);
 
     // look up provisioning caller, make sure it's valid.
     let provFlow = this.getProvisionFlow(aProvId);
 
     if (!provFlow.caller) {
-      reportError("registerCertificate", "No provision flow or caller");
+      logger.warning("registerCertificate", "No provision flow or caller");
       return;
     }
     if (!provFlow.kp)  {
       let errStr = "Cannot register a certificate without a keypair";
-      reportError("registerCertificate", errStr);
+      logger.warning("registerCertificate", errStr);
       provFlow.callback(errStr);
       return;
     }
 
     // store the keypair and certificate just provided in IDStore.
     this._store.addIdentity(provFlow.identity, provFlow.kp, aCert);
 
     // Great success!
@@ -303,17 +299,17 @@ IdentityProviderService.prototype = {
    * @param aProvId
    *        (int) the identifier of the provisioning flow which failed
    *
    * @param aCallback
    *        (function) to invoke upon completion, with
    *                   first-positional-param error.
    */
   _doAuthentication: function _doAuthentication(aProvId, aIDPParams) {
-    log("_doAuthentication: provId:", aProvId, "idpParams:", aIDPParams);
+    logger.log("_doAuthentication: provId:", aProvId, "idpParams:", aIDPParams);
     // create an authentication caller and its identifier AuthId
     // stash aIdentity, idpparams, and callback in it.
 
     // extract authentication URL from idpParams
     let authPath = aIDPParams.idpParams.authentication;
     let authURI = Services.io.newURI("https://" + aIDPParams.domain, null, null).resolve(authPath);
 
     // beginAuthenticationFlow causes the "identity-auth" topic to be
@@ -336,49 +332,49 @@ IdentityProviderService.prototype = {
    * aCaller is an expected authentication-flow identifier. If not, we throw
    * an error or something.
    *
    * @param aCaller
    *        (object)  the authentication caller
    *
    */
   beginAuthentication: function beginAuthentication(aCaller) {
-    log("beginAuthentication: caller id:", aCaller.id);
+    logger.log("beginAuthentication: caller id:", aCaller.id);
 
     // Begin the authentication flow after having concluded a provisioning
     // flow.  The aCaller that the DOM gives us will have the same ID as
     // the provisioning flow we just concluded.  (see setAuthenticationFlow)
     let authFlow = this._authenticationFlows[aCaller.id];
     if (!authFlow) {
       return aCaller.doError("beginAuthentication: no flow for caller id", aCaller.id);
     }
 
     authFlow.caller = aCaller;
 
     let identity = this._provisionFlows[authFlow.provId].identity;
 
     // tell the UI to start the authentication process
-    log("beginAuthentication: authFlow:", aCaller.id, "identity:", identity);
+    logger.log("beginAuthentication: authFlow:", aCaller.id, "identity:", identity);
     return authFlow.caller.doBeginAuthenticationCallback(identity);
   },
 
   /**
    * The auth frame has called navigator.id.completeAuthentication
    *
    * @param aAuthId
    *        (int)  the identifier of the authentication caller tied to that sandbox
    *
    */
   completeAuthentication: function completeAuthentication(aAuthId) {
-    log("completeAuthentication:", aAuthId);
+    logger.log("completeAuthentication:", aAuthId);
 
     // look up the AuthId caller, and get its callback.
     let authFlow = this._authenticationFlows[aAuthId];
     if (!authFlow) {
-      reportError("completeAuthentication", "No auth flow with id", aAuthId);
+      logger.warning("completeAuthentication", "No auth flow with id", aAuthId);
       return;
     }
     let provId = authFlow.provId;
 
     // delete caller
     delete authFlow['caller'];
     delete this._authenticationFlows[aAuthId];
 
@@ -394,92 +390,92 @@ IdentityProviderService.prototype = {
   /**
    * The auth frame has called navigator.id.cancelAuthentication
    *
    * @param aAuthId
    *        (int)  the identifier of the authentication caller
    *
    */
   cancelAuthentication: function cancelAuthentication(aAuthId) {
-    log("cancelAuthentication:", aAuthId);
+    logger.log("cancelAuthentication:", aAuthId);
 
     // look up the AuthId caller, and get its callback.
     let authFlow = this._authenticationFlows[aAuthId];
     if (!authFlow) {
-      reportError("cancelAuthentication", "No auth flow with id:", aAuthId);
+      logger.warning("cancelAuthentication", "No auth flow with id:", aAuthId);
       return;
     }
     let provId = authFlow.provId;
 
     // delete caller
     delete authFlow['caller'];
     delete this._authenticationFlows[aAuthId];
 
     let provFlow = this.getProvisionFlow(provId);
     provFlow.didAuthentication = true;
     Services.obs.notifyObservers(null, "identity-auth-complete", aAuthId);
 
     // invoke callback with ERROR.
     let errStr = "Authentication canceled by IDP";
-    log("ERROR: cancelAuthentication:", errStr);
+    logger.log("ERROR: cancelAuthentication:", errStr);
     provFlow.callback(errStr);
   },
 
   /**
    * Called by the UI to set the ID and caller for the authentication flow after it gets its ID
    */
   setAuthenticationFlow: function(aAuthId, aProvId) {
     // this is the transition point between the two flows,
     // provision and authenticate.  We tell the auth flow which
     // provisioning flow it is started from.
-    log("setAuthenticationFlow: authId:", aAuthId, "provId:", aProvId);
+    logger.log("setAuthenticationFlow: authId:", aAuthId, "provId:", aProvId);
     this._authenticationFlows[aAuthId] = { provId: aProvId };
     this._provisionFlows[aProvId].authId = aAuthId;
   },
 
   /**
    * Load the provisioning URL in a hidden frame to start the provisioning
    * process.
    */
   _createProvisioningSandbox: function _createProvisioningSandbox(aURL, aCallback) {
-    log("_createProvisioningSandbox:", aURL);
+    logger.log("_createProvisioningSandbox:", aURL);
 
     if (!this._sandboxConfigured) {
       // Configure message manager listening on the hidden window
       Cu.import("resource://gre/modules/DOMIdentity.jsm");
       DOMIdentity._configureMessages(Services.appShell.hiddenDOMWindow, true);
       this._sandboxConfigured = true;
     }
 
     new Sandbox(aURL, aCallback);
   },
 
   /**
    * Load the authentication UI to start the authentication process.
    */
   _beginAuthenticationFlow: function _beginAuthenticationFlow(aProvId, aURL) {
-    log("_beginAuthenticationFlow:", aProvId, aURL);
+    logger.log("_beginAuthenticationFlow:", aProvId, aURL);
     let propBag = {provId: aProvId};
 
     Services.obs.notifyObservers({wrappedJSObject:propBag}, "identity-auth", aURL);
   },
 
   /**
    * Clean up a provision flow and the authentication flow and sandbox
    * that may be attached to it.
    */
   _cleanUpProvisionFlow: function _cleanUpProvisionFlow(aProvId) {
-    log('_cleanUpProvisionFlow:', aProvId);
+    logger.log('_cleanUpProvisionFlow:', aProvId);
     let prov = this._provisionFlows[aProvId];
 
     // Clean up the sandbox, if there is one.
     if (prov.provisioningSandbox) {
       let sandbox = this._provisionFlows[aProvId]['provisioningSandbox'];
       if (sandbox.free) {
-        log('_cleanUpProvisionFlow: freeing sandbox');
+        logger.log('_cleanUpProvisionFlow: freeing sandbox');
         sandbox.free();
       }
       delete this._provisionFlows[aProvId]['provisioningSandbox'];
     }
 
     // Clean up a related authentication flow, if there is one.
     if (this._authenticationFlows[prov.authId]) {
       delete this._authenticationFlows[prov.authId];
--- a/toolkit/identity/IdentityUtils.jsm
+++ b/toolkit/identity/IdentityUtils.jsm
@@ -18,40 +18,38 @@ this.EXPORTED_SYMBOLS = [
 const Cu = Components.utils;
 
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 
 XPCOMUtils.defineLazyServiceGetter(this, "uuidgen",
                                    "@mozilla.org/uuid-generator;1",
                                    "nsIUUIDGenerator");
 
-XPCOMUtils.defineLazyModuleGetter(this, "Logger",
-                                  "resource://gre/modules/identity/LogUtils.jsm");
-
-function log(...aMessageArgs) {
-  Logger.log.apply(Logger, ["Identity"].concat(aMessageArgs));
-}
+XPCOMUtils.defineLazyGetter(this, "logger", function() {
+  Cu.import('resource://gre/modules/identity/LogUtils.jsm');
+  return getLogger("Identity", "toolkit.identity.debug");
+});
 
 function defined(item) {
   return typeof item !== 'undefined';
 }
 
 var checkDeprecated = this.checkDeprecated = function checkDeprecated(aOptions, aField) {
   if (defined(aOptions[aField])) {
-    log("WARNING: field is deprecated:", aField);
+    logger.log("WARNING: field is deprecated:", aField);
     return true;
   }
   return false;
 };
 
 this.checkRenamed = function checkRenamed(aOptions, aOldName, aNewName) {
   if (defined(aOptions[aOldName]) &&
       defined(aOptions[aNewName])) {
     let err = "You cannot provide both " + aOldName + " and " + aNewName;
-    Logger.reportError(err);
+    logger.error(err);
     throw new Error(err);
   }
 
   if (checkDeprecated(aOptions, aOldName)) {
     aOptions[aNewName] = aOptions[aOldName];
     delete(aOptions[aOldName]);
   }
 };
@@ -59,17 +57,17 @@ this.checkRenamed = function checkRename
 this.getRandomId = function getRandomId() {
   return uuidgen.generateUUID().toString();
 };
 
 /*
  * copy source object into target, excluding private properties
  * (those whose names begin with an underscore)
  */
-this.objectCopy = function objectCopy(source, target){
+this.objectCopy = function objectCopy(source, target) {
   let desc;
   Object.getOwnPropertyNames(source).forEach(function(name) {
     if (name[0] !== '_') {
       desc = Object.getOwnPropertyDescriptor(source, name);
       Object.defineProperty(target, name, desc);
     }
   });
 };
--- a/toolkit/identity/LogUtils.jsm
+++ b/toolkit/identity/LogUtils.jsm
@@ -1,103 +1,178 @@
 /* -*- Mode: js2; js2-basic-offset: 2; indent-tabs-mode: nil; -*- */
 /* vim: set ft=javascript ts=2 et sw=2 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/. */
 
 "use strict";
 
-this.EXPORTED_SYMBOLS = ["Logger"];
-const PREF_DEBUG = "toolkit.identity.debug";
+this.EXPORTED_SYMBOLS = ["Logger", "getLogger"];
 
 const Cu = Components.utils;
 const Ci = Components.interfaces;
-const Cc = Components.classes;
-const Cr = Components.results;
 
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 Cu.import("resource://gre/modules/Services.jsm");
 
-function IdentityLogger() {
-  Services.prefs.addObserver(PREF_DEBUG, this, false);
-  this._debug = Services.prefs.getBoolPref(PREF_DEBUG);
-  return this;
+function Logger(aIdentifier, aEnablingPref) {
+  this._identifier = aIdentifier;
+  this._enablingPref = aEnablingPref;
+
+  // Enabled by default if a pref for toggling the logger is not given
+  this._enabled = !this._enablingPref;
+
+  this.init();
 }
 
-IdentityLogger.prototype = {
+Logger.prototype = {
   QueryInterface: XPCOMUtils.generateQI([Ci.nsISupports, Ci.nsIObserver]),
 
+  init: function Logger_init() {
+    if (this._enablingPref) {
+      Services.prefs.addObserver(this._enablingPref, this, false);
+      this._enabled = Services.prefs.getBoolPref(this._enablingPref);
+    }
+  },
+
   observe: function observe(aSubject, aTopic, aData) {
-    switch(aTopic) {
+    switch (aTopic) {
       case "nsPref:changed":
-        this._debug = Services.prefs.getBoolPref(PREF_DEBUG);
+        this._enabled = Services.prefs.getBoolPref(this._enablingPref);
+        dump("LogUtils " +
+             (this._enabled ? "enabled" : "disabled") +
+             " for " + this._identifier + "\n");
         break;
 
       case "quit-application-granted":
-        Services.prefs.removeObserver(PREF_DEBUG, this);
+        Services.prefs.removeObserver(this._enablingPref, this);
         break;
 
       default:
         this.log("Logger observer", "Unknown topic:", aTopic);
         break;
     }
   },
 
-  _generateLogMessage: function _generateLogMessage(aPrefix, args) {
-    // create a string representation of a list of arbitrary things
-    let strings = [];
+  _generatePrefix: function _generatePrefix() {
+    let caller = Components.stack.caller.caller;
+    let parts = ['[' + this._identifier + ']'];
 
-    // XXX bug 770418 - args look like flattened array, not list of strings
+    // filename could be like path/to/foo.js or Scratchpad/1
+    if (caller.filename) {
+      let path = caller.filename.split('/');
+      if (path[path.length - 1].match(/\./)) {
+        parts.push(path[path.length - 1])
+      } else {
+        parts.push(caller.filename);
+      }
+    }
 
-    args.forEach(function(arg) {
-      if (typeof arg === 'string') {
-        strings.push(arg);
-      } else if (typeof arg === 'undefined') {
-        strings.push('undefined');
-      } else if (arg === null) {
+    // Might not be called from a function; might be top-level
+    if (caller.name) {
+      parts.push(caller.name + '()');
+    }
+
+    parts.push('line ' + caller.lineNumber + ': ');
+
+    return parts.join(' ');
+  },
+
+  _generateLogMessage: function _generateLogMessage(severity, argList) {
+    let strings = [];
+    argList.forEach(function(arg) {
+      if (arg === null) {
         strings.push('null');
       } else {
-        try {
-          strings.push(JSON.stringify(arg, null, 2));
-        } catch(err) {
-          strings.push("<<something>>");
+        switch (typeof arg) {
+          case 'string':
+            strings.push(arg);
+            break;
+          case 'undefined':
+            strings.push('undefined');
+            break;
+          case 'function':
+            strings.push('<<function>>');
+            break;
+          case 'object':
+            try {
+              strings.push(JSON.stringify(arg, null, 2));
+            } catch (err) {
+              strings.push('<<object>>');
+            }
+            break;
+          default:
+            try {
+              strings.push(arg.toString());
+            } catch (err) {
+              strings.push('<<something>>');
+            }
+            break;
         }
       }
     });
-    return 'Identity ' + aPrefix + ': ' + strings.join(' ');
+    return strings.join(' ');
   },
 
   /**
    * log() - utility function to print a list of arbitrary things
    *
    * Enable with about:config pref toolkit.identity.debug
    */
-  log: function log(aPrefix, ...args) {
-    if (!this._debug) {
+  log: function log(...argList) {
+    if (!this._enabled) {
       return;
     }
-    let output = this._generateLogMessage(aPrefix, args);
+    let output = this._generatePrefix() + this._generateLogMessage('info', argList);
+
+    // print to the shell console and the browser error console
     dump(output + "\n");
-
-    // Additionally, make the output visible in the Error Console
     Services.console.logStringMessage(output);
   },
 
+  warning: function Logger_warning(...argList) {
+    if (!this._enabled) {
+      return;
+    }
+
+    let output = this._generatePrefix() + this._generateLogMessage('warning', argList);
+  },
+
   /**
-   * reportError() - report an error through component utils as well as
+   * error() - report an error through component utils as well as
    * our log function
    */
-  reportError: function reportError(aPrefix, ...aArgs) {
-    let prefix = aPrefix + ' ERROR';
+  error: function Logger_error(...argList) {
+    if (!this._enabled) {
+      return;
+    }
 
     // Report the error in the browser
-    let output = this._generateLogMessage(aPrefix, aArgs);
+    let output = this._generatePrefix() + this._generateLogMessage('error', argList);
     Cu.reportError(output);
+
+    // print to the console
     dump("ERROR: " + output + "\n");
+    dump("   traceback follows:\n");
     for (let frame = Components.stack.caller; frame; frame = frame.caller) {
       dump(frame + "\n");
     }
   }
-
 };
 
-this.Logger = new IdentityLogger();
+/**
+ * let logger = getLogger('my component', 'toolkit.foo.debug');
+ * logger.log("I would like", 42, "pies", {'and-some': 'object'});
+ */
+
+let _loggers = {};
+
+this.getLogger = function(aIdentifier, aEnablingPref) {
+  let key = aIdentifier;
+  if (aEnablingPref) {
+    key = key + '-' + aEnablingPref;
+  }
+  if (!_loggers[key]) {
+    _loggers[key] = new Logger(aIdentifier, aEnablingPref);
+  }
+  return _loggers[key];
+}
--- a/toolkit/identity/MinimalIdentity.jsm
+++ b/toolkit/identity/MinimalIdentity.jsm
@@ -14,34 +14,25 @@
  */
 
 "use strict";
 
 this.EXPORTED_SYMBOLS = ["IdentityService"];
 
 const Cu = Components.utils;
 const Ci = Components.interfaces;
-const Cc = Components.classes;
-const Cr = Components.results;
 
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 Cu.import("resource://gre/modules/Services.jsm");
-Cu.import("resource://gre/modules/identity/LogUtils.jsm");
 Cu.import("resource://gre/modules/identity/IdentityUtils.jsm");
 
-XPCOMUtils.defineLazyModuleGetter(this,
-                                  "jwcrypto",
-                                  "resource://gre/modules/identity/jwcrypto.jsm");
-
-function log(...aMessageArgs) {
-  Logger.log.apply(Logger, ["minimal core"].concat(aMessageArgs));
-}
-function reportError(...aMessageArgs) {
-  Logger.reportError.apply(Logger, ["core"].concat(aMessageArgs));
-}
+XPCOMUtils.defineLazyGetter(this, "logger", function() {
+  Cu.import('resource://gre/modules/identity/LogUtils.jsm');
+  return getLogger("Identity", "toolkit.identity.debug");
+});
 
 function makeMessageObject(aRpCaller) {
   let options = {};
 
   options.id = aRpCaller.id;
   options.origin = aRpCaller.origin;
 
   // loggedInUser can be undefined, null, or a string
@@ -59,64 +50,48 @@ function makeMessageObject(aRpCaller) {
       options[option] = aRpCaller[option];
     }
   });
 
   // check validity of message structure
   if ((typeof options.id === 'undefined') ||
       (typeof options.origin === 'undefined')) {
     let err = "id and origin required in relying-party message: " + JSON.stringify(options);
-    reportError(err);
+    logger.error(err);
     throw new Error(err);
   }
 
   return options;
 }
 
 function IDService() {
   Services.obs.addObserver(this, "quit-application-granted", false);
-  // Services.obs.addObserver(this, "identity-auth-complete", false);
 
   // simplify, it's one object
   this.RP = this;
   this.IDP = this;
 
   // keep track of flows
   this._rpFlows = {};
   this._authFlows = {};
   this._provFlows = {};
 }
 
 IDService.prototype = {
   QueryInterface: XPCOMUtils.generateQI([Ci.nsISupports, Ci.nsIObserver]),
 
-  observe: function observe(aSubject, aTopic, aData) {
+  observe: function IDService_observe(aSubject, aTopic, aData) {
     switch (aTopic) {
       case "quit-application-granted":
         Services.obs.removeObserver(this, "quit-application-granted");
-        // Services.obs.removeObserver(this, "identity-auth-complete");
         break;
     }
   },
 
   /**
-   * Parse an email into username and domain if it is valid, else return null
-   */
-  parseEmail: function parseEmail(email) {
-    var match = email.match(/^([^@]+)@([^@^/]+.[a-z]+)$/);
-    if (match) {
-      return {
-        username: match[1],
-        domain: match[2]
-      };
-    }
-    return null;
-  },
-
-  /**
    * Register a listener for a given windowID as a result of a call to
    * navigator.id.watch().
    *
    * @param aCaller
    *        (Object)  an object that represents the caller document, and
    *                  is expected to have properties:
    *                  - id (unique, e.g. uuid)
    *                  - loggedInUser (string or null)
@@ -125,51 +100,51 @@ IDService.prototype = {
    *                  and a bunch of callbacks
    *                  - doReady()
    *                  - doLogin()
    *                  - doLogout()
    *                  - doError()
    *                  - doCancel()
    *
    */
-  watch: function watch(aRpCaller) {
+  watch: function IDService_watch(aRpCaller) {
     // store the caller structure and notify the UI observers
     this._rpFlows[aRpCaller.id] = aRpCaller;
 
     let options = makeMessageObject(aRpCaller);
-    log("sending identity-controller-watch:", options);
+    logger.log("sending identity-controller-watch:", options);
     Services.obs.notifyObservers({wrappedJSObject: options},"identity-controller-watch", null);
   },
 
   /*
    * The RP has gone away; remove handles to the hidden iframe.
    * It's probable that the frame will already have been cleaned up.
    */
-  unwatch: function unwatch(aRpId, aTargetMM) {
+  unwatch: function IDService_unwatch(aRpId, aTargetMM) {
     let rp = this._rpFlows[aRpId];
     let options = makeMessageObject({
       id: aRpId,
       origin: rp.origin,
       messageManager: aTargetMM
     });
-    log("sending identity-controller-unwatch for id", options.id, options.origin);
+    logger.log("sending identity-controller-unwatch for id", options.id, options.origin);
     Services.obs.notifyObservers({wrappedJSObject: options}, "identity-controller-unwatch", null);
   },
 
   /**
    * Initiate a login with user interaction as a result of a call to
    * navigator.id.request().
    *
    * @param aRPId
    *        (integer)  the id of the doc object obtained in .watch()
    *
    * @param aOptions
    *        (Object)  options including privacyPolicy, termsOfService
    */
-  request: function request(aRPId, aOptions) {
+  request: function IDService_request(aRPId, aOptions) {
     let rp = this._rpFlows[aRPId];
 
     // Notify UX to display identity picker.
     // Pass the doc id to UX so it can pass it back to us later.
     let options = makeMessageObject(rp);
     objectCopy(aOptions, options);
     Services.obs.notifyObservers({wrappedJSObject: options}, "identity-controller-request", null);
   },
@@ -177,269 +152,74 @@ IDService.prototype = {
   /**
    * Invoked when a user wishes to logout of a site (for instance, when clicking
    * on an in-content logout button).
    *
    * @param aRpCallerId
    *        (integer)  the id of the doc object obtained in .watch()
    *
    */
-  logout: function logout(aRpCallerId) {
+  logout: function IDService_logout(aRpCallerId) {
     let rp = this._rpFlows[aRpCallerId];
 
     let options = makeMessageObject(rp);
     Services.obs.notifyObservers({wrappedJSObject: options}, "identity-controller-logout", null);
   },
 
-  childProcessShutdown: function childProcessShutdown(messageManager) {
+  childProcessShutdown: function IDService_childProcessShutdown(messageManager) {
     let options = makeMessageObject({messageManager: messageManager, id: null, origin: null});
     Services.obs.notifyObservers({wrappedJSObject: options}, "identity-child-process-shutdown", null);
     Object.keys(this._rpFlows).forEach(function(key) {
       if (this._rpFlows[key]._mm === messageManager) {
-        log("child process shutdown for rp", key, "- deleting flow");
+        logger.log("child process shutdown for rp", key, "- deleting flow");
         delete this._rpFlows[key];
       }
     }, this);
   },
 
   /*
    * once the UI-and-display-logic components have received
    * notifications, they call back with direct invocation of the
    * following functions (doLogin, doLogout, or doReady)
    */
 
-  doLogin: function doLogin(aRpCallerId, aAssertion, aInternalParams) {
+  doLogin: function IDService_doLogin(aRpCallerId, aAssertion, aInternalParams) {
     let rp = this._rpFlows[aRpCallerId];
     if (!rp) {
       dump("WARNING: doLogin found no rp to go with callerId " + aRpCallerId + "\n");
       return;
     }
 
     rp.doLogin(aAssertion, aInternalParams);
   },
 
-  doLogout: function doLogout(aRpCallerId) {
+  doLogout: function IDService_doLogout(aRpCallerId) {
     let rp = this._rpFlows[aRpCallerId];
     if (!rp) {
       dump("WARNING: doLogout found no rp to go with callerId " + aRpCallerId + "\n");
       return;
     }
 
     rp.doLogout();
   },
 
-  doReady: function doReady(aRpCallerId) {
+  doReady: function IDService_doReady(aRpCallerId) {
     let rp = this._rpFlows[aRpCallerId];
     if (!rp) {
       dump("WARNING: doReady found no rp to go with callerId " + aRpCallerId + "\n");
       return;
     }
 
     rp.doReady();
   },
 
-  doCancel: function doCancel(aRpCallerId) {
+  doCancel: function IDService_doCancel(aRpCallerId) {
     let rp = this._rpFlows[aRpCallerId];
     if (!rp) {
       dump("WARNING: doCancel found no rp to go with callerId " + aRpCallerId + "\n");
       return;
     }
 
     rp.doCancel();
-  },
-
-
-  /*
-   * XXX Bug 804229: Implement Identity Provider Functions
-   *
-   * Stubs for Identity Provider functions follow
-   */
-
-  /**
-   * the provisioning iframe sandbox has called navigator.id.beginProvisioning()
-   *
-   * @param aCaller
-   *        (object)  the iframe sandbox caller with all callbacks and
-   *                  other information.  Callbacks include:
-   *                  - doBeginProvisioningCallback(id, duration_s)
-   *                  - doGenKeyPairCallback(pk)
-   */
-  beginProvisioning: function beginProvisioning(aCaller) {
-  },
-
-  /**
-   * the provisioning iframe sandbox has called
-   * navigator.id.raiseProvisioningFailure()
-   *
-   * @param aProvId
-   *        (int)  the identifier of the provisioning flow tied to that sandbox
-   * @param aReason
-   */
-  raiseProvisioningFailure: function raiseProvisioningFailure(aProvId, aReason) {
-    reportError("Provisioning failure", aReason);
-  },
-
-  /**
-   * When navigator.id.genKeyPair is called from provisioning iframe sandbox.
-   * Generates a keypair for the current user being provisioned.
-   *
-   * @param aProvId
-   *        (int)  the identifier of the provisioning caller tied to that sandbox
-   *
-   * It is an error to call genKeypair without receiving the callback for
-   * the beginProvisioning() call first.
-   */
-  genKeyPair: function genKeyPair(aProvId) {
-  },
-
-  /**
-   * When navigator.id.registerCertificate is called from provisioning iframe
-   * sandbox.
-   *
-   * Sets the certificate for the user for which a certificate was requested
-   * via a preceding call to beginProvisioning (and genKeypair).
-   *
-   * @param aProvId
-   *        (integer) the identifier of the provisioning caller tied to that
-   *                  sandbox
-   *
-   * @param aCert
-   *        (String)  A JWT representing the signed certificate for the user
-   *                  being provisioned, provided by the IdP.
-   */
-  registerCertificate: function registerCertificate(aProvId, aCert) {
-  },
-
-  /**
-   * The authentication frame has called navigator.id.beginAuthentication
-   *
-   * IMPORTANT: the aCaller is *always* non-null, even if this is called from
-   * a regular content page. We have to make sure, on every DOM call, that
-   * aCaller is an expected authentication-flow identifier. If not, we throw
-   * an error or something.
-   *
-   * @param aCaller
-   *        (object)  the authentication caller
-   *
-   */
-  beginAuthentication: function beginAuthentication(aCaller) {
-  },
-
-  /**
-   * The auth frame has called navigator.id.completeAuthentication
-   *
-   * @param aAuthId
-   *        (int)  the identifier of the authentication caller tied to that sandbox
-   *
-   */
-  completeAuthentication: function completeAuthentication(aAuthId) {
-  },
-
-  /**
-   * The auth frame has called navigator.id.cancelAuthentication
-   *
-   * @param aAuthId
-   *        (int)  the identifier of the authentication caller
-   *
-   */
-  cancelAuthentication: function cancelAuthentication(aAuthId) {
-  },
-
-  // methods for chrome and add-ons
-
-  /**
-   * Discover the IdP for an identity
-   *
-   * @param aIdentity
-   *        (string) the email we're logging in with
-   *
-   * @param aCallback
-   *        (function) callback to invoke on completion
-   *                   with first-positional parameter the error.
-   */
-  _discoverIdentityProvider: function _discoverIdentityProvider(aIdentity, aCallback) {
-    // XXX bug 767610 - validate email address call
-    // When that is available, we can remove this custom parser
-    var parsedEmail = this.parseEmail(aIdentity);
-    if (parsedEmail === null) {
-      return aCallback("Could not parse email: " + aIdentity);
-    }
-    log("_discoverIdentityProvider: identity:", aIdentity, "domain:", parsedEmail.domain);
-
-    this._fetchWellKnownFile(parsedEmail.domain, function fetchedWellKnown(err, idpParams) {
-      // idpParams includes the pk, authorization url, and
-      // provisioning url.
-
-      // XXX bug 769861 follow any authority delegations
-      // if no well-known at any point in the delegation
-      // fall back to browserid.org as IdP
-      return aCallback(err, idpParams);
-    });
-  },
-
-  /**
-   * Fetch the well-known file from the domain.
-   *
-   * @param aDomain
-   *
-   * @param aScheme
-   *        (string) (optional) Protocol to use.  Default is https.
-   *                 This is necessary because we are unable to test
-   *                 https.
-   *
-   * @param aCallback
-   *
-   */
-  _fetchWellKnownFile: function _fetchWellKnownFile(aDomain, aCallback, aScheme='https') {
-    // XXX bug 769854 make tests https and remove aScheme option
-    let url = aScheme + '://' + aDomain + "/.well-known/browserid";
-    log("_fetchWellKnownFile:", url);
-
-    // this appears to be a more successful way to get at xmlhttprequest (which supposedly will close with a window
-    let req = Cc["@mozilla.org/xmlextras/xmlhttprequest;1"]
-                .createInstance(Ci.nsIXMLHttpRequest);
-
-    // XXX bug 769865 gracefully handle being off-line
-    // XXX bug 769866 decide on how to handle redirects
-    req.open("GET", url, true);
-    req.responseType = "json";
-    req.mozBackgroundRequest = true;
-    req.onload = function _fetchWellKnownFile_onload() {
-      if (req.status < 200 || req.status >= 400) {
-        log("_fetchWellKnownFile", url, ": server returned status:", req.status);
-        return aCallback("Error");
-      }
-      try {
-        let idpParams = req.response;
-
-        // Verify that the IdP returned a valid configuration
-        if (! (idpParams.provisioning &&
-            idpParams.authentication &&
-            idpParams['public-key'])) {
-          let errStr= "Invalid well-known file from: " + aDomain;
-          log("_fetchWellKnownFile:", errStr);
-          return aCallback(errStr);
-        }
-
-        let callbackObj = {
-          domain: aDomain,
-          idpParams: idpParams,
-        };
-        log("_fetchWellKnownFile result: ", callbackObj);
-        // Yay.  Valid IdP configuration for the domain.
-        return aCallback(null, callbackObj);
-
-      } catch (err) {
-        reportError("_fetchWellKnownFile", "Bad configuration from", aDomain, err);
-        return aCallback(err.toString());
-      }
-    };
-    req.onerror = function _fetchWellKnownFile_onerror() {
-      log("_fetchWellKnownFile", "ERROR:", req.status, req.statusText);
-      log("ERROR: _fetchWellKnownFile:", err);
-      return aCallback("Error");
-    };
-    req.send(null);
-  },
-
+  }
 };
 
 this.IdentityService = new IDService();
--- a/toolkit/identity/RelyingParty.jsm
+++ b/toolkit/identity/RelyingParty.jsm
@@ -8,32 +8,29 @@
 
 const Cu = Components.utils;
 const Ci = Components.interfaces;
 const Cc = Components.classes;
 const Cr = Components.results;
 
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 Cu.import("resource://gre/modules/Services.jsm");
-Cu.import("resource://gre/modules/identity/LogUtils.jsm");
 Cu.import("resource://gre/modules/identity/IdentityUtils.jsm");
 Cu.import("resource://gre/modules/identity/IdentityStore.jsm");
 
 this.EXPORTED_SYMBOLS = ["RelyingParty"];
 
 XPCOMUtils.defineLazyModuleGetter(this,
                                   "jwcrypto",
                                   "resource://gre/modules/identity/jwcrypto.jsm");
 
-function log(...aMessageArgs) {
-  Logger.log.apply(Logger, ["RP"].concat(aMessageArgs));
-}
-function reportError(...aMessageArgs) {
-  Logger.reportError.apply(Logger, ["RP"].concat(aMessageArgs));
-}
+XPCOMUtils.defineLazyGetter(this, "logger", function() {
+  Cu.import('resource://gre/modules/identity/LogUtils.jsm');
+  return getLogger("Identity RP", "toolkit.identity.debug");
+});
 
 function IdentityRelyingParty() {
   // The store is a singleton shared among Identity, RelyingParty, and
   // IdentityProvider.  The Identity module takes care of resetting
   // state in the _store on shutdown.
   this._store = IdentityStore;
 
   this.reset();
@@ -82,17 +79,17 @@ IdentityRelyingParty.prototype = {
    *                  - doCancel()
    *
    */
   watch: function watch(aRpCaller) {
     this._rpFlows[aRpCaller.id] = aRpCaller;
     let origin = aRpCaller.origin;
     let state = this._store.getLoginState(origin) || { isLoggedIn: false, email: null };
 
-    log("watch: rpId:", aRpCaller.id,
+    logger.log("watch: rpId:", aRpCaller.id,
         "origin:", origin,
         "loggedInUser:", aRpCaller.loggedInUser,
         "loggedIn:", state.isLoggedIn,
         "email:", state.email);
 
     // If the user is already logged in, then there are three cases
     // to deal with:
     //
@@ -134,45 +131,45 @@ IdentityRelyingParty.prototype = {
 
   /**
    * A utility for watch() to set state and notify the dom
    * on login
    *
    * Note that this calls _getAssertion
    */
   _doLogin: function _doLogin(aRpCaller, aOptions, aAssertion) {
-    log("_doLogin: rpId:", aRpCaller.id, "origin:", aOptions.origin);
+    logger.log("_doLogin: rpId:", aRpCaller.id, "origin:", aOptions.origin);
 
     let loginWithAssertion = function loginWithAssertion(assertion) {
       this._store.setLoginState(aOptions.origin, true, aOptions.loggedInUser);
       this._notifyLoginStateChanged(aRpCaller.id, aOptions.loggedInUser);
       aRpCaller.doLogin(assertion);
       aRpCaller.doReady();
     }.bind(this);
 
     if (aAssertion) {
       loginWithAssertion(aAssertion);
     } else {
       this._getAssertion(aOptions, function gotAssertion(err, assertion) {
         if (err) {
-          reportError("_doLogin:", "Failed to get assertion on login attempt:", err);
+          logger.warning("_doLogin:", "Failed to get assertion on login attempt:", err);
           this._doLogout(aRpCaller);
         } else {
           loginWithAssertion(assertion);
         }
       }.bind(this));
     }
   },
 
   /**
    * A utility for watch() to set state and notify the dom
    * on logout.
    */
   _doLogout: function _doLogout(aRpCaller, aOptions) {
-    log("_doLogout: rpId:", aRpCaller.id, "origin:", aOptions.origin);
+    logger.log("_doLogout: rpId:", aRpCaller.id, "origin:", aOptions.origin);
 
     let state = this._store.getLoginState(aOptions.origin) || {};
 
     state.isLoggedIn = false;
     this._notifyLoginStateChanged(aRpCaller.id, null);
 
     aRpCaller.doLogout();
     aRpCaller.doReady();
@@ -186,17 +183,17 @@ IdentityRelyingParty.prototype = {
    *
    * @param aRpCallerId
    *        (integer) The id of the RP caller
    *
    * @param aIdentity
    *        (string) The email of the user whose login state has changed
    */
   _notifyLoginStateChanged: function _notifyLoginStateChanged(aRpCallerId, aIdentity) {
-    log("_notifyLoginStateChanged: rpId:", aRpCallerId, "identity:", aIdentity);
+    logger.log("_notifyLoginStateChanged: rpId:", aRpCallerId, "identity:", aIdentity);
 
     let options = {rpId: aRpCallerId};
     Services.obs.notifyObservers({wrappedJSObject: options},
                                  "identity-login-state-changed",
                                  aIdentity);
   },
 
   /**
@@ -205,17 +202,17 @@ IdentityRelyingParty.prototype = {
    *
    * @param aRPId
    *        (integer)  the id of the doc object obtained in .watch()
    *
    * @param aOptions
    *        (Object)  options including privacyPolicy, termsOfService
    */
   request: function request(aRPId, aOptions) {
-    log("request: rpId:", aRPId);
+    logger.log("request: rpId:", aRPId);
     let rp = this._rpFlows[aRPId];
 
     // Notify UX to display identity picker.
     // Pass the doc id to UX so it can pass it back to us later.
     let options = {rpId: aRPId, origin: rp.origin};
     objectCopy(aOptions, options);
 
     // Append URLs after resolving
@@ -233,33 +230,33 @@ IdentityRelyingParty.prototype = {
    * Invoked when a user wishes to logout of a site (for instance, when clicking
    * on an in-content logout button).
    *
    * @param aRpCallerId
    *        (integer)  the id of the doc object obtained in .watch()
    *
    */
   logout: function logout(aRpCallerId) {
-    log("logout: RP caller id:", aRpCallerId);
+    logger.log("logout: RP caller id:", aRpCallerId);
     let rp = this._rpFlows[aRpCallerId];
     if (rp && rp.origin) {
       let origin = rp.origin;
-      log("logout: origin:", origin);
+      logger.log("logout: origin:", origin);
       this._doLogout(rp, {origin: origin});
     } else {
-      log("logout: no RP found with id:", aRpCallerId);
+      logger.log("logout: no RP found with id:", aRpCallerId);
     }
     // We don't delete this._rpFlows[aRpCallerId], because
     // the user might log back in again.
   },
 
   getDefaultEmailForOrigin: function getDefaultEmailForOrigin(aOrigin) {
     let identities = this.getIdentitiesForSite(aOrigin);
     let result = identities.lastUsed || null;
-    log("getDefaultEmailForOrigin:", aOrigin, "->", result);
+    logger.log("getDefaultEmailForOrigin:", aOrigin, "->", result);
     return result;
   },
 
   /**
    * Return the list of identities a user may want to use to login to aOrigin.
    */
   getIdentitiesForSite: function getIdentitiesForSite(aOrigin) {
     let rv = { result: [] };
@@ -288,33 +285,33 @@ IdentityRelyingParty.prototype = {
    *                            issued. If this property is not set an exception
    *                            will be thrown.
    *
    *        Any properties not listed above will be ignored.
    */
   _getAssertion: function _getAssertion(aOptions, aCallback) {
     let audience = aOptions.origin;
     let email = aOptions.loggedInUser || this.getDefaultEmailForOrigin(audience);
-    log("_getAssertion: audience:", audience, "email:", email);
+    logger.log("_getAssertion: audience:", audience, "email:", email);
     if (!audience) {
       throw "audience required for _getAssertion";
     }
 
     // We might not have any identity info for this email
     if (!this._store.fetchIdentity(email)) {
       this._store.addIdentity(email, null, null);
     }
 
     let cert = this._store.fetchIdentity(email)['cert'];
     if (cert) {
       this._generateAssertion(audience, email, function generatedAssertion(err, assertion) {
         if (err) {
-          log("ERROR: _getAssertion:", err);
+          logger.warning("ERROR: _getAssertion:", err);
         }
-        log("_getAssertion: generated assertion:", assertion);
+        logger.log("_getAssertion: generated assertion:", assertion);
         return aCallback(err, assertion);
       });
     }
   },
 
   /**
    * Generate an assertion, including provisioning via IdP if necessary,
    * but no user interaction, so if provisioning fails, aCallback is invoked
@@ -326,45 +323,45 @@ IdentityRelyingParty.prototype = {
    * @param aIdentity
    *        (string) the email we're logging in with
    *
    * @param aCallback
    *        (function) callback to invoke on completion
    *                   with first-positional parameter the error.
    */
   _generateAssertion: function _generateAssertion(aAudience, aIdentity, aCallback) {
-    log("_generateAssertion: audience:", aAudience, "identity:", aIdentity);
+    logger.log("_generateAssertion: audience:", aAudience, "identity:", aIdentity);
 
     let id = this._store.fetchIdentity(aIdentity);
     if (! (id && id.cert)) {
       let errStr = "Cannot generate an assertion without a certificate";
-      log("ERROR: _generateAssertion:", errStr);
+      logger.log("ERROR: _generateAssertion:", errStr);
       aCallback(errStr);
       return;
     }
 
     let kp = id.keyPair;
 
     if (!kp) {
       let errStr = "Cannot generate an assertion without a keypair";
-      log("ERROR: _generateAssertion:", errStr);
+      logger.log("ERROR: _generateAssertion:", errStr);
       aCallback(errStr);
       return;
     }
 
     jwcrypto.generateAssertion(id.cert, kp, aAudience, aCallback);
   },
 
   /**
    * Clean up references to the provisioning flow for the specified RP.
    */
   _cleanUpProvisionFlow: function RP_cleanUpProvisionFlow(aRPId, aProvId) {
     let rp = this._rpFlows[aRPId];
     if (rp) {
       delete rp['provId'];
     } else {
-      log("Error: Couldn't delete provision flow ", aProvId, " for RP ", aRPId);
+      logger.log("Error: Couldn't delete provision flow ", aProvId, " for RP ", aRPId);
     }
   },
 
 };
 
 this.RelyingParty = new IdentityRelyingParty();
--- a/toolkit/identity/Sandbox.jsm
+++ b/toolkit/identity/Sandbox.jsm
@@ -8,19 +8,20 @@ this.EXPORTED_SYMBOLS = ["Sandbox"];
 
 const {classes: Cc, interfaces: Ci, utils: Cu} = Components;
 
 const XHTML_NS = "http://www.w3.org/1999/xhtml";
 
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 Cu.import("resource://gre/modules/Services.jsm");
 
-XPCOMUtils.defineLazyModuleGetter(this,
-                                  "Logger",
-                                  "resource://gre/modules/identity/LogUtils.jsm");
+XPCOMUtils.defineLazyGetter(this, "logger", function() {
+  Cu.import('resource://gre/modules/identity/LogUtils.jsm');
+  return getLogger("Identity", "toolkit.identity.debug");
+});
 
 /**
  * An object that represents a sandbox in an iframe loaded with aURL. The
  * callback provided to the constructor will be invoked when the sandbox is
  * ready to be used. The callback will receive this object as its only argument.
  *
  * You must call free() when you are finished with the sandbox to explicitly
  * free up all associated resources.
@@ -29,17 +30,17 @@ XPCOMUtils.defineLazyModuleGetter(this,
  *        (string) URL to load in the sandbox.
  *
  * @param aCallback
  *        (function) Callback to be invoked with a Sandbox, when ready.
  */
 this.Sandbox = function Sandbox(aURL, aCallback) {
   // Normalize the URL so the comparison in _makeSandboxContentLoaded works
   this._url = Services.io.newURI(aURL, null, null).spec;
-  this._log("Creating sandbox for:", this._url);
+  logger.log("Creating sandbox for:", this._url);
   this._createFrame();
   this._createSandbox(aCallback);
 };
 
 this.Sandbox.prototype = {
 
   /**
    * Use the outer window ID as the identifier of the sandbox.
@@ -49,28 +50,28 @@ this.Sandbox.prototype = {
                .getInterface(Ci.nsIDOMWindowUtils).outerWindowID;
   },
 
   /**
    * Reload the URL in the sandbox. This is useful to reuse a Sandbox (same
    * id and URL).
    */
   reload: function Sandbox_reload(aCallback) {
-    this._log("reload:", this.id, ":", this._url);
+    logger.log("reload:", this.id, ":", this._url);
     this._createSandbox(function createdSandbox(aSandbox) {
-      this._log("reloaded sandbox id:", aSandbox.id);
+      logger.log("reloaded sandbox id:", aSandbox.id);
       aCallback(aSandbox);
     }.bind(this));
   },
 
   /**
    * Frees the sandbox and releases the iframe created to host it.
    */
   free: function Sandbox_free() {
-    this._log("free:", this.id);
+    logger.log("free:", this.id);
     this._container.removeChild(this._frame);
     this._frame = null;
     this._container = null;
     this._url = null;
   },
 
   /**
    * Creates an empty, hidden iframe and sets it to the _frame
@@ -110,17 +111,17 @@ this.Sandbox.prototype = {
     // Set instance properties.
     this._frame = frame;
     this._container = doc.documentElement;
   },
 
   _createSandbox: function Sandbox__createSandbox(aCallback) {
     let self = this;
     function _makeSandboxContentLoaded(event) {
-      self._log("_makeSandboxContentLoaded:", self.id,
+      logger.log("_makeSandboxContentLoaded:", self.id,
                 event.target.location.toString());
       if (event.target != self._frame.contentDocument) {
         return;
       }
       self._frame.removeEventListener(
         "DOMWindowCreated", _makeSandboxContentLoaded, true
       );
 
@@ -139,15 +140,10 @@ this.Sandbox.prototype = {
     webNav.loadURI(
       this._url,
       Ci.nsIWebNavigation.LOAD_FLAGS_BYPASS_CACHE,
       null, // referrer
       null, // postData
       null  // headers
     );
 
-  },
-
-  _log: function Sandbox__log(...aMessageArgs) {
-    Logger.log.apply(Logger, ["sandbox"].concat(aMessageArgs));
-  },
-
+  }
 };
--- a/toolkit/identity/jwcrypto.jsm
+++ b/toolkit/identity/jwcrypto.jsm
@@ -9,33 +9,33 @@
 
 const Cu = Components.utils;
 const Ci = Components.interfaces;
 const Cc = Components.classes;
 const Cr = Components.results;
 
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 Cu.import("resource://gre/modules/Services.jsm");
-Cu.import("resource://gre/modules/identity/LogUtils.jsm");
+
+XPCOMUtils.defineLazyGetter(this, "logger", function() {
+  Cu.import('resource://gre/modules/identity/LogUtils.jsm');
+  return getLogger("Identity test", "toolkit.identity.debug");
+});
 
 XPCOMUtils.defineLazyServiceGetter(this,
                                    "IdentityCryptoService",
                                    "@mozilla.org/identity/crypto-service;1",
                                    "nsIIdentityCryptoService");
 
 this.EXPORTED_SYMBOLS = ["jwcrypto"];
 
 const ALGORITHMS = { RS256: "RS256", DS160: "DS160" };
 
-function log(...aMessageArgs) {
-  Logger.log.apply(Logger, ["jwcrypto"].concat(aMessageArgs));
-}
-
 function generateKeyPair(aAlgorithmName, aCallback) {
-  log("Generate key pair; alg =", aAlgorithmName);
+  logger.log("Generate key pair; alg =", aAlgorithmName);
 
   IdentityCryptoService.generateKeyPair(aAlgorithmName, function(rv, aKeyPair) {
     if (!Components.isSuccessCode(rv)) {
       return aCallback("key generation failed");
     }
 
     var publicKey;
 
@@ -69,36 +69,36 @@ function generateKeyPair(aAlgorithmName,
 
     return aCallback(null, keyWrapper);
   });
 }
 
 function sign(aPayload, aKeypair, aCallback) {
   aKeypair._kp.sign(aPayload, function(rv, signature) {
     if (!Components.isSuccessCode(rv)) {
-      log("ERROR: signer.sign failed");
+      logger.warning("ERROR: signer.sign failed");
       return aCallback("Sign failed");
     }
-    log("signer.sign: success");
+    logger.log("signer.sign: success");
     return aCallback(null, signature);
   });
 }
 
 function jwcryptoClass()
 {
 }
 
 jwcryptoClass.prototype = {
   isCertValid: function(aCert, aCallback) {
     // XXX check expiration, bug 769850
     aCallback(true);
   },
 
   generateKeyPair: function(aAlgorithmName, aCallback) {
-    log("generating");
+    logger.log("generating");
     generateKeyPair(aAlgorithmName, aCallback);
   },
 
   generateAssertion: function(aCert, aKeyPair, aAudience, aCallback) {
     // for now, we hack the algorithm name
     // XXX bug 769851
     var header = {"alg": "DS128"};
     var headerBytes = IdentityCryptoService.base64UrlEncode(
@@ -108,17 +108,17 @@ jwcryptoClass.prototype = {
       // expires in 2 minutes
       // XXX clock skew needs exploration bug 769852
       exp: Date.now() + (2 * 60 * 1000),
       aud: aAudience
     };
     var payloadBytes = IdentityCryptoService.base64UrlEncode(
                           JSON.stringify(payload));
 
-    log("payload bytes", payload, payloadBytes);
+    logger.log("payload bytes", payload, payloadBytes);
     sign(headerBytes + "." + payloadBytes, aKeyPair, function(err, signature) {
       if (err)
         return aCallback(err);
 
       var signedAssertion = headerBytes + "." + payloadBytes + "." + signature;
       return aCallback(null, aCert + "~" + signedAssertion);
     });
   }
--- a/toolkit/identity/tests/unit/head_identity.js
+++ b/toolkit/identity/tests/unit/head_identity.js
@@ -20,20 +20,16 @@ XPCOMUtils.defineLazyModuleGetter(this, 
 XPCOMUtils.defineLazyModuleGetter(this, "IDService",
                                   "resource://gre/modules/identity/Identity.jsm",
                                   "IdentityService");
 
 XPCOMUtils.defineLazyModuleGetter(this,
                                   "IdentityStore",
                                   "resource://gre/modules/identity/IdentityStore.jsm");
 
-XPCOMUtils.defineLazyModuleGetter(this,
-                                  "Logger",
-                                  "resource://gre/modules/identity/LogUtils.jsm");
-
 XPCOMUtils.defineLazyServiceGetter(this,
                                    "uuidGenerator",
                                    "@mozilla.org/uuid-generator;1",
                                    "nsIUUIDGenerator");
 
 const TEST_URL = "https://myfavoritebacon.com";
 const TEST_URL2 = "https://myfavoritebaconinacan.com";
 const TEST_USER = "user@mozilla.com";
@@ -70,20 +66,23 @@ let XULAppInfoFactory = {
 };
 
 let registrar = Components.manager.QueryInterface(Ci.nsIComponentRegistrar);
 registrar.registerFactory(Components.ID("{fbfae60b-64a4-44ef-a911-08ceb70b9f31}"),
                           "XULAppInfo", "@mozilla.org/xre/app-info;1",
                           XULAppInfoFactory);
 
 // The following are utility functions for Identity testing
+//
+XPCOMUtils.defineLazyGetter(this, "logger", function() {
+  Cu.import('resource://gre/modules/identity/LogUtils.jsm');
+  return getLogger("Identity test", "toolkit.identity.debug");
+});
 
-function log(...aMessageArgs) {
-  Logger.log.apply(Logger, ["test"].concat(aMessageArgs));
-}
+var log = logger.log;
 
 function get_idstore() {
   return IdentityStore;
 }
 
 function partial(fn) {
   let args = Array.prototype.slice.call(arguments, 1);
   return function() {
--- a/toolkit/identity/tests/unit/test_crypto_service.js
+++ b/toolkit/identity/tests/unit/test_crypto_service.js
@@ -1,64 +1,68 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 "use strict";
 
 Cu.import("resource://gre/modules/Services.jsm");
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
-Cu.import('resource://gre/modules/identity/LogUtils.jsm');
+
+XPCOMUtils.defineLazyGetter(this, "logger", function() {
+  Cu.import('resource://gre/modules/identity/LogUtils.jsm');
+  return getLogger("Identity test", "toolkit.identity.debug");
+});
 
 const idService = Cc["@mozilla.org/identity/crypto-service;1"]
                     .getService(Ci.nsIIdentityCryptoService);
 
 const ALG_DSA = "DS160";
 const ALG_RSA = "RS256";
 
 // When the output of an operation is a
 function do_check_eq_or_slightly_less(x, y) {
   do_check_true(x >= y - (3 * 8));
 }
 
 function test_dsa() {
   idService.generateKeyPair(ALG_DSA, function (rv, keyPair) {
-    log("DSA generateKeyPair finished ", rv);
+    logger.log("DSA generateKeyPair finished ", rv);
     do_check_true(Components.isSuccessCode(rv));
     do_check_eq(typeof keyPair.sign, "function");
     do_check_eq(keyPair.keyType, ALG_DSA);
     do_check_eq_or_slightly_less(keyPair.hexDSAGenerator.length, 1024 / 8 * 2);
     do_check_eq_or_slightly_less(keyPair.hexDSAPrime.length, 1024 / 8 * 2);
     do_check_eq_or_slightly_less(keyPair.hexDSASubPrime.length, 160 / 8 * 2);
     do_check_eq_or_slightly_less(keyPair.hexDSAPublicValue.length, 1024 / 8 * 2);
     // XXX: test that RSA parameters throw the correct error
 
-    log("about to sign with DSA key");
+    logger.log("about to sign with DSA key");
     keyPair.sign("foo", function (rv, signature) {
-      log("DSA sign finished ", rv, signature);
+      logger.log("DSA sign finished ", rv, signature);
       do_check_true(Components.isSuccessCode(rv));
       do_check_true(signature.length > 1);
       // TODO: verify the signature with the public key
       run_next_test();
     });
   });
 }
 
 function test_rsa() {
   idService.generateKeyPair(ALG_RSA, function (rv, keyPair) {
-    log("RSA generateKeyPair finished ", rv);
+    logger.log("RSA generateKeyPair finished ", rv);
     do_check_true(Components.isSuccessCode(rv));
     do_check_eq(typeof keyPair.sign, "function");
     do_check_eq(keyPair.keyType, ALG_RSA);
     do_check_eq_or_slightly_less(keyPair.hexRSAPublicKeyModulus.length,
                                  2048 / 8);
     do_check_true(keyPair.hexRSAPublicKeyExponent.length > 1);
 
-    log("about to sign with RSA key");
+    logger.log("about to sign with RSA key");
     keyPair.sign("foo", function (rv, signature) {
-      log("RSA sign finished ", rv, signature);
+      logger.log("RSA sign finished ", rv, signature);
       do_check_true(Components.isSuccessCode(rv));
       do_check_true(signature.length > 1);
       run_next_test();
     });
   });
 }
 
 add_test(test_dsa);
--- a/toolkit/identity/tests/unit/test_jwcrypto.js
+++ b/toolkit/identity/tests/unit/test_jwcrypto.js
@@ -1,22 +1,25 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 "use strict"
 
-Cu.import('resource://gre/modules/identity/LogUtils.jsm');
-
 XPCOMUtils.defineLazyModuleGetter(this, "IDService",
                                   "resource://gre/modules/identity/Identity.jsm",
                                   "IdentityService");
 
 XPCOMUtils.defineLazyModuleGetter(this, "jwcrypto",
                                   "resource://gre/modules/identity/jwcrypto.jsm");
 
+XPCOMUtils.defineLazyGetter(this, "logger", function() {
+  Cu.import('resource://gre/modules/identity/LogUtils.jsm');
+  return getLogger("Identity test", "toolkit.identity.debug");
+});
+
 const RP_ORIGIN = "http://123done.org";
 const INTERNAL_ORIGIN = "browserid://";
 
 function test_sanity() {
   do_test_pending();
 
   jwcrypto.generateKeyPair("DS160", function(err, kp) {
     do_check_null(err);
@@ -42,29 +45,29 @@ function test_get_assertion() {
 
   jwcrypto.generateKeyPair(
     "DS160",
     function(err, kp) {
       jwcrypto.generateAssertion("fake-cert", kp, RP_ORIGIN, function(err, assertion) {
         do_check_null(err);
 
         // more checks on assertion
-        log("assertion", assertion);
+        logger.log("assertion", assertion);
 
         do_test_finished();
         run_next_test();
       });
     });
 }
 
 function test_rsa() {
   do_test_pending();
   function checkRSA(err, kpo) {
     do_check_neq(kpo, undefined);
-    log(kpo.serializedPublicKey);
+    logger.log(kpo.serializedPublicKey);
     let pk = JSON.parse(kpo.serializedPublicKey);
     do_check_eq(pk.algorithm, "RS");
 /* TODO
     do_check_neq(kpo.sign, null);
     do_check_eq(typeof kpo.sign, "function");
     do_check_neq(kpo.userID, null);
     do_check_neq(kpo.url, null);
     do_check_eq(kpo.url, INTERNAL_ORIGIN);
@@ -84,17 +87,17 @@ function test_rsa() {
 
   jwcrypto.generateKeyPair("RS256", checkRSA);
 }
 
 function test_dsa() {
   do_test_pending();
   function checkDSA(err, kpo) {
     do_check_neq(kpo, undefined);
-    log(kpo.serializedPublicKey);
+    logger.log(kpo.serializedPublicKey);
     let pk = JSON.parse(kpo.serializedPublicKey);
     do_check_eq(pk.algorithm, "DS");
 /* TODO
     do_check_neq(kpo.sign, null);
     do_check_eq(typeof kpo.sign, "function");
     do_check_neq(kpo.userID, null);
     do_check_neq(kpo.url, null);
     do_check_eq(kpo.url, INTERNAL_ORIGIN);
--- a/toolkit/identity/tests/unit/test_log_utils.js
+++ b/toolkit/identity/tests/unit/test_log_utils.js
@@ -1,74 +1,73 @@
 
 "use strict";
 
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 Cu.import('resource://gre/modules/Services.jsm');
-Cu.import('resource://gre/modules/identity/LogUtils.jsm');
+
+XPCOMUtils.defineLazyGetter(this, "logger", function() {
+  Cu.import('resource://gre/modules/identity/LogUtils.jsm');
+  return getLogger("Identity test", "toolkit.identity.debug");
+});
 
 function toggle_debug() {
   do_test_pending();
 
   function Wrapper() {
     this.init();
   }
   Wrapper.prototype = {
     QueryInterface: XPCOMUtils.generateQI([Ci.nsISupports, Ci.nsIObserver]),
 
     observe: function observe(aSubject, aTopic, aData) {
       if (aTopic === "nsPref:changed") {
         // race condition?
-        do_check_eq(Logger._debug, true);
+        do_check_eq(logger._enabled, true);
         do_test_finished();
         run_next_test();
       }
     },
 
     init: function() {
       Services.prefs.addObserver('toolkit.identity.debug', this, false);
     }
   };
 
   var wrapper = new Wrapper();
   Services.prefs.setBoolPref('toolkit.identity.debug', true);
 }
 
 // test that things don't break
 
-function logAlias(...args) {
-  Logger.log.apply(Logger, ["log alias"].concat(args));
-}
-function reportErrorAlias(...args) {
-  Logger.reportError.apply(Logger, ["report error alias"].concat(args));
-}
-
 function test_log() {
-  Logger.log("log test", "I like pie");
+  logger.log("log test", "I like pie");
   do_test_finished();
   run_next_test();
 }
 
-function test_reportError() {
-  Logger.reportError("log test", "We are out of pies!!!");
+function test_warning() {
+  logger.warning("similar log test", "We are still out of pies!!!");
   do_test_finished();
   run_next_test();
 }
 
-function test_wrappers() {
-  logAlias("I like potatoes");
+function test_error() {
+  logger.error("My head a splode");
   do_test_finished();
-  reportErrorAlias("Too much red bull");
+  run_next_test();
 }
 
+
 let TESTS = [
 // XXX fix me 
 //    toggle_debug,
     test_log,
-    test_reportError,
-    test_wrappers
+    test_warning,
+    test_error
 ];
 
 TESTS.forEach(add_test);
 
 function run_test() {
   run_next_test();
-}
\ No newline at end of file
+}
+
--- a/toolkit/identity/tests/unit/test_minimalidentity.js
+++ b/toolkit/identity/tests/unit/test_minimalidentity.js
@@ -1,20 +1,14 @@
 "use strict";
 
 XPCOMUtils.defineLazyModuleGetter(this, "MinimalIDService",
                                   "resource://gre/modules/identity/MinimalIdentity.jsm",
                                   "IdentityService");
 
-Cu.import("resource://gre/modules/identity/LogUtils.jsm");
-
-function log(...aMessageArgs) {
-  Logger.log.apply(Logger, ["test_minimalidentity"].concat(aMessageArgs));
-}
-
 function test_overall() {
   do_check_neq(MinimalIDService, null);
   run_next_test();
 }
 
 function test_mock_doc() {
   do_test_pending();
   let mockedDoc = mock_doc(null, TEST_URL, function(action, params) {