Bug 794680 - Connect gecko to gaia identity ui. r=benadida, r=cjones
authorJed Parsons <jparsons@mozilla.com>
Fri, 26 Oct 2012 07:39:38 -0400
changeset 111526 14cf08f2845d0176a9e68b722d9becf5f054dc92
parent 111525 113115cd43f83b23742896cb53b06283f2cfa1df
child 111527 1c48b82bce7f99ef8593ae59c8ddae2f61001423
push id23752
push userryanvm@gmail.com
push dateSat, 27 Oct 2012 01:22:44 +0000
treeherdermozilla-central@f9acc2e4d4e3 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbenadida, cjones
bugs794680
milestone19.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 794680 - Connect gecko to gaia identity ui. r=benadida, r=cjones
b2g/chrome/content/identity.js
b2g/components/SignInToWebsite.jsm
b2g/components/test/unit/head_identity.js
b2g/components/test/unit/test_signintowebsite.js
b2g/components/test/unit/xpcshell.ini
b2g/installer/package-manifest.in
dom/identity/DOMIdentity.jsm
dom/identity/Makefile.in
dom/identity/nsDOMIdentity.js
dom/identity/tests/Makefile.in
dom/identity/tests/head_identity.js
dom/identity/tests/test_identity_idp_auth_basics.html
dom/identity/tests/test_identity_idp_prov_basics.html
dom/identity/tests/test_identity_rp_basics.html
modules/libpref/src/init/all.js
toolkit/identity/Makefile.in
toolkit/identity/MinimalIdentity.jsm
toolkit/identity/tests/mochitest/Makefile.in
toolkit/identity/tests/mochitest/head_identity.js
toolkit/identity/tests/mochitest/test_authentication.html
toolkit/identity/tests/mochitest/test_provisioning.html
toolkit/identity/tests/mochitest/test_relying_party.html
toolkit/identity/tests/unit/test_identity.js
toolkit/identity/tests/unit/test_minimalidentity.js
toolkit/identity/tests/unit/xpcshell.ini
new file mode 100644
--- /dev/null
+++ b/b2g/chrome/content/identity.js
@@ -0,0 +1,186 @@
+/* -*- 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");
+Cu.import("resource://gre/modules/Services.jsm");
+
+XPCOMUtils.defineLazyServiceGetter(this, "cpmm",
+                                   "@mozilla.org/childprocessmessagemanager;1",
+                                   "nsIMessageSender");
+
+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, ["injected identity.js"].concat(aMessageArgs));
+}
+
+log("\n\n======================= identity.js =======================\n\n");
+
+// This script may be injected more than once into an iframe.
+// Ensure we don't redefine contstants
+if (typeof kIdentityJSLoaded === 'undefined') {
+  const kReceivedIdentityAssertion = "received-id-assertion";
+  const kIdentityDelegateWatch = "identity-delegate-watch";
+  const kIdentityDelegateRequest = "identity-delegate-request";
+  const kIdentityDelegateLogout = "identity-delegate-logout";
+  const kIdentityDelegateReady = "identity-delegate-ready";
+  const kIdentityDelegateFinished = "identity-delegate-finished";
+  const kIdentityControllerDoMethod = "identity-controller-doMethod";
+  const kIdentktyJSLoaded = true;
+}
+
+var showUI = false;
+var options = null;
+var isLoaded = false;
+var func = null;
+
+/*
+ * Message back to the SignInToWebsite pipe.  Message should be an
+ * object with the following keys:
+ *
+ *   method:             one of 'login', 'logout', 'ready'
+ *   assertion:          optional assertion
+ */
+function identityCall(message) {
+  sendAsyncMessage(kIdentityControllerDoMethod, message);
+}
+
+function identityFinished() {
+  log("identity finished.  closing dialog");
+  closeIdentityDialog(function notifySuccess() {
+    // get ready for next call with a reinit
+    func = null; options = null;
+
+    sendAsyncMessage(kIdentityDelegateFinished);
+  });
+}
+
+/*
+ * Notify the UI to close the dialog and return to the caller application
+ */
+function closeIdentityDialog(aCallback) {
+  let randomId = uuidgen.generateUUID().toString();
+  let id = kReceivedIdentityAssertion + "-" + randomId;
+  let browser = Services.wm.getMostRecentWindow("navigator:browser");
+
+  let detail = {
+    type: kReceivedIdentityAssertion,
+    id: id,
+    showUI: showUI
+  };
+
+  // In order to avoid race conditions, we wait for the UI to notify that
+  // it has successfully closed the identity flow and has recovered the
+  // caller app, before notifying the parent process.
+  content.addEventListener("mozContentEvent", function closeIdentityDialogFinished(evt) {
+    content.removeEventListener("mozContentEvent", closeIdentityDialogFinished);
+
+    if (evt.detail.id == id && aCallback) {
+      aCallback();
+    }
+  });
+
+  browser.shell.sendChromeEvent(detail);
+}
+
+/*
+ * doInternalWatch - call the internal.watch api and relay the results
+ * up to the controller.
+ */
+function doInternalWatch() {
+  log("doInternalWatch:", options, isLoaded);
+  if (options && isLoaded) {
+    log("internal watch options:", options);
+    let BrowserID = content.wrappedJSObject.BrowserID;
+    BrowserID.internal.watch(function(aParams) {
+        log("sending watch method message:", aParams.method);
+        identityCall(aParams);
+        if (aParams.method === "ready") {
+          log("watch finished.");
+          identityFinished();
+        }
+      },
+      JSON.stringify({loggedInUser: options.loggedInUser, origin: options.origin}),
+      function(...things) {
+        log("internal: ", things);
+      }
+    );
+  }
+}
+
+function doInternalRequest() {
+  log("doInternalRequest:", options && isLoaded);
+  if (options && isLoaded) {
+    content.wrappedJSObject.BrowserID.internal.get(
+      options.origin,
+      function(assertion) {
+        if (assertion) {
+          log("request -> assertion, so do login");
+          identityCall({method:'login',assertion:assertion});
+        }
+        identityFinished();
+      },
+      options);
+  }
+}
+
+function doInternalLogout(aOptions) {
+  log("doInternalLogout:", (options && isLoaded));
+  if (options && isLoaded) {
+    let BrowserID = content.wrappedJSObject.BrowserID;
+    log("logging you out of ", options.origin);
+    BrowserID.internal.logout(options.origin, function() {
+      identityCall({method:'logout'});
+      identityFinished();
+    });
+  }
+}
+
+addEventListener("DOMContentLoaded", function(e) {
+  content.addEventListener("load", function(e) {
+    isLoaded = true;
+     // bring da func
+     if (func) func();
+  });
+});
+
+// listen for request
+addMessageListener(kIdentityDelegateRequest, function(aMessage) {
+    log("\n\n* * * * injected identity.js received", kIdentityDelegateRequest, "\n\n\n");
+  options = aMessage.json;
+  showUI = true;
+  func = doInternalRequest;
+  func();
+});
+
+// listen for watch
+addMessageListener(kIdentityDelegateWatch, function(aMessage) {
+    log("\n\n* * * * injected identity.js received", kIdentityDelegateWatch, "\n\n\n");
+  options = aMessage.json;
+  showUI = false;
+  func = doInternalWatch;
+  func();
+});
+
+// listen for logout
+addMessageListener(kIdentityDelegateLogout, function(aMessage) {
+    log("\n\n* * * * injected identity.js received", kIdentityDelegateLogout, "\n\n\n");
+  options = aMessage.json;
+  showUI = false;
+  func = doInternalLogout;
+  func();
+});
new file mode 100644
--- /dev/null
+++ b/b2g/components/SignInToWebsite.jsm
@@ -0,0 +1,352 @@
+/* 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/. */
+
+/*
+ * SignInToWebsite.jsm - UX Controller and means for accessing identity
+ * cookies on behalf of relying parties.
+ *
+ * Currently, the b2g security architecture isolates web applications
+ * so that each window has access only to a local cookie jar:
+ *
+ *     To prevent Web apps from interfering with one another, each one is
+ *     hosted on a separate domain, and therefore may only access the
+ *     resources associated with its domain. These resources include
+ *     things such as IndexedDB databases, cookies, offline storage,
+ *     and so forth.
+ *
+ *     -- https://developer.mozilla.org/en-US/docs/Mozilla/Firefox_OS/Security/Security_model
+ *
+ * As a result, an authentication system like Persona cannot share its
+ * cookie jar with multiple relying parties, and so would require a
+ * fresh login request in every window.  This would not be a good
+ * experience.
+ *
+ *
+ * In order for navigator.id.request() to maintain state in a single
+ * cookie jar, we cause all Persona interactions to take place in a
+ * gaia context that is launched by the system application, with the
+ * result that Persona has a single cookie jar that all Relying
+ * Parties can use.  Since of course those Relying Parties cannot
+ * reach into the system cookie jar, the Controller in this module
+ * provides a way to get messages and data to and fro between the
+ * Relying Party in its window context, and the Persona internal api
+ * in its context.
+ *
+ * On the Relying Party's side, say a web page invokes
+ * navigator.id.watch(), to register callbacks, and then
+ * navigator.id.request() to request an assertion.  The navigator.id
+ * calls are provided by nsDOMIdentity.  nsDOMIdentity messages down
+ * to the privileged DOMIdentity code (using cpmm and ppmm message
+ * managers).  DOMIdentity stores the state of Relying Party flows
+ * using an Identity service (MinimalIdentity.jsm), and emits messages
+ * requesting Persona functions (doWatch, doReady, doLogout).
+ *
+ * The Identity service sends these observer messages to the
+ * Controller in this module, which in turn triggers gaia to open a
+ * window to host the Persona js.  If user interaction is required,
+ * gaia will open the trusty UI.  If user interaction is not required,
+ * and we only need to get to Persona functions, gaia will open a
+ * hidden iframe.  In either case, a window is opened into which the
+ * controller causes the script identity.js to be injected.  This
+ * script provides the glue between the in-page javascript and the
+ * pipe back down to the Controller, translating navigator.internal
+ * function callbacks into messages sent back to the Controller.
+ *
+ * As a result, a navigator.internal function in the hosted popup or
+ * iframe can call back to the injected identity.js (doReady, doLogin,
+ * or doLogout).  identity.js callbacks send messages back through the
+ * pipe to the Controller.  The controller invokes the corresponding
+ * function on the Identity Service (doReady, doLogin, or doLogout).
+ * The IdentityService calls the corresponding callback for the
+ * correct Relying Party, which causes DOMIdentity to send a message
+ * up to the Relying Party through nsDOMIdentity
+ * (Identity:RP:Watch:OnLogin etc.), and finally, nsDOMIdentity
+ * receives these messages and calls the original callback that the
+ * Relying Party registered (navigator.id.watch(),
+ * navigator.id.request(), or navigator.id.logout()).
+ */
+
+"use strict";
+
+const EXPORTED_SYMBOLS = ["SignInToWebsiteController"];
+
+const Ci = Components.interfaces;
+const Cu = Components.utils;
+
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(this, "IdentityService",
+                                  "resource://gre/modules/identity/MinimalIdentity.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(this, "Logger",
+                                  "resource://gre/modules/identity/LogUtils.jsm");
+
+// JS shim that contains the callback functions that
+// live within the identity UI provisioning frame.
+const kIdentityShimFile = "chrome://browser/content/identity.js";
+
+// Type of MozChromeEvents to handle id dialogs.
+const kOpenIdentityDialog = "open-id-dialog";
+const kCloseIdentityDialog = "close-id-dialog";
+
+// Observer messages to communicate to shim
+const kIdentityDelegateWatch = "identity-delegate-watch";
+const kIdentityDelegateRequest = "identity-delegate-request";
+const kIdentityDelegateLogout = "identity-delegate-logout";
+const kIdentityDelegateFinished = "identity-delegate-finished";
+const kIdentityDelegateReady = "identity-delegate-ready";
+
+const kIdentityControllerDoMethod = "identity-controller-doMethod";
+
+XPCOMUtils.defineLazyServiceGetter(this, "uuidgen",
+                                   "@mozilla.org/uuid-generator;1",
+                                   "nsIUUIDGenerator");
+
+function log(...aMessageArgs) {
+  Logger.log.apply(Logger, ["SignInToWebsiteController"].concat(aMessageArgs));
+}
+
+function getRandomId() {
+  return uuidgen.generateUUID().toString();
+}
+
+/*
+ * GaiaInterface encapsulates the our gaia functions.  There are only two:
+ *
+ * getContent       - return the current content window
+ * sendChromeEvent  - send a chromeEvent from the browser shell
+ */
+let GaiaInterface = {
+  _getBrowser: function SignInToWebsiteController__getBrowser() {
+    return Services.wm.getMostRecentWindow("navigator:browser");
+  },
+
+  getContent: function SignInToWebsiteController_getContent() {
+    return this._getBrowser().getContentWindow();
+  },
+
+  sendChromeEvent: function SignInToWebsiteController_sendChromeEvent(detail) {
+    this._getBrowser().shell.sendChromeEvent(detail);
+  }
+};
+
+/*
+ * The Pipe abstracts the communcation channel between the Controller
+ * and the identity.js code running in the browser window.
+ */
+let Pipe = {
+
+  /*
+   * communicate - launch a gaia window with certain options and
+   * provide a callback for handling messages.
+   *
+   * @param aRpOptions        options describing the Relying Party's
+   *        (dicitonary)      call, such as origin and loggedInEmail.
+   *
+   * @param aGaiaOptions      showUI:   boolean
+   *        (dictionary)      message:  name of the message to emit
+   *                                    (request, logout, watch)
+   *
+   * @param aMessageCallback  function to call on receipt of a
+   *        (function)        do-method message.  These messages name
+   *                          a method ('login', 'logout', etc.) and
+   *                          carry optional parameters.  The Pipe does
+   *                          not know what the messages mean; it is
+   *                          up to the caller to interpret them and
+   *                          act on them.
+   */
+  communicate: function(aRpOptions, aGaiaOptions, aMessageCallback) {
+    log("open gaia dialog with options:", aGaiaOptions);
+
+    // This content variable is injected into the scope of
+    // kIdentityShimFile, where it is used to access the BrowserID object
+    // and its internal API.
+    let content = GaiaInterface.getContent();
+
+    if (!content) {
+      log("ERROR: what the what? no content window?");
+      // aErrorCb.onresult("NO_CONTENT_WINDOW");
+      return;
+    }
+
+    // Prepare a message for gaia.  The parameter showUI signals
+    // whether user interaction is needed.  If it is, gaia will open a
+    // dialog; if not, a hidden iframe.  In each case, BrowserID is
+    // available in the context.
+    let id = kOpenIdentityDialog + "-" + getRandomId();
+    let detail = {
+      type: kOpenIdentityDialog,
+      showUI: aGaiaOptions.showUI || false,
+      id: id
+    };
+
+    // When gaia signals back with a mozContentEvent containing the
+    // unique id we created, we know the window is ready.  We then inject
+    // the magic javascript (kIdentityShimFile) that will give the content
+    // the superpowers it needs to communicate back with this code.
+    content.addEventListener("mozContentEvent", function getAssertion(evt) {
+
+      // Make sure the message is really for us
+      let msg = evt.detail;
+      if (msg.id != id) {
+        return;
+      }
+
+      // We only need to catch the first mozContentEvent from the
+      // iframe or popup, so we remove the listener right away.
+      content.removeEventListener("mozContentEvent", getAssertion);
+
+      // Try to load the identity shim file containing the callbacks
+      // in the content script.  This could be either the visible
+      // popup that the user interacts with, or it could be an invisible
+      // frame.
+      let frame = evt.detail.frame;
+      let frameLoader = frame.QueryInterface(Ci.nsIFrameLoaderOwner).frameLoader;
+      let mm = frameLoader.messageManager;
+      try {
+        log("about to load frame script");
+        mm.loadFrameScript(kIdentityShimFile, true);
+      } catch (e) {
+        log("Error loading ", kIdentityShimFile, " as a frame script: ", e);
+      }
+
+      // There are two messages that the delegate can send back: a "do
+      // method" event, and a "finished" event.  We pass the do-method
+      // events straight to the caller for interpretation and handling.
+      // If we receive a "finished" event, then the delegate is done, so
+      // we shut down the pipe and clean up.
+      mm.addMessageListener(kIdentityControllerDoMethod, aMessageCallback);
+      mm.addMessageListener(kIdentityDelegateFinished, function identityDelegateFinished(message) {
+        mm.removeMessageListener(kIdentityDelegateFinished, identityDelegateFinished);
+        mm.removeMessageListener(kIdentityControllerDoMethod, aMessageCallback);
+      });
+
+      mm.sendAsyncMessage(aGaiaOptions.message, aRpOptions);
+    });
+
+    // Tell gaia to open the identity iframe or trusty popup
+    GaiaInterface.sendChromeEvent(detail);
+  }
+
+};
+
+/*
+ * The controller sits between the IdentityService used by DOMIdentity
+ * and a gaia process launches an (invisible) iframe or (visible)
+ * trusty UI.  Using an injected js script (identity.js), the
+ * controller enables the gaia window to access the persona identity
+ * storage in the system cookie jar and send events back via the
+ * controller into IdentityService and DOM, and ultimately up to the
+ * Relying Party, which is open in a different window context.
+ */
+let SignInToWebsiteController = {
+
+  /*
+   * Initialize the controller.  To use a different gaia communication pipe,
+   * such as when mocking it in tests, pass aOptions.pipe.
+   */
+  init: function SignInToWebsiteController_init(aOptions) {
+    aOptions = aOptions || {};
+    this.pipe = aOptions.pipe || Pipe;
+    Services.obs.addObserver(this, "identity-controller-watch", false);
+    Services.obs.addObserver(this, "identity-controller-request", false);
+    Services.obs.addObserver(this, "identity-controller-logout", false);
+  },
+
+  uninit: function SignInToWebsiteController_uninit() {
+    Services.obs.removeObserver(this, "identity-controller-watch");
+    Services.obs.removeObserver(this, "identity-controller-request");
+    Services.obs.removeObserver(this, "identity-controller-logout");
+  },
+
+  observe: function SignInToWebsiteController_observe(aSubject, aTopic, aData) {
+    log("observe: received", aTopic, "with", aData, "for", aSubject);
+    let options = null;
+    if (aSubject) {
+      options = aSubject.wrappedJSObject;
+    }
+    switch(aTopic) {
+      case "identity-controller-watch":
+        this.doWatch(options);
+        break;
+      case "identity-controller-request":
+        this.doRequest(options);
+        break;
+      case "identity-controller-logout":
+        this.doLogout(options);
+        break;
+      default:
+        Logger.reportError("SignInToWebsiteController", "Unknown observer notification:", aTopic);
+        break;
+    }
+  },
+
+  /*
+   * options:    method          required - name of method to invoke
+   *             assertion       optional
+   */
+  _makeDoMethodCallback: function SignInToWebsiteController__makeDoMethodCallback(aRpId) {
+    return function SignInToWebsiteController_methodCallback(aOptions) {
+      log("doMethod:", aOptions);
+      let message = aOptions.json;
+      if (typeof message === 'string') {
+        message = JSON.parse(message);
+      }
+      switch(message.method) {
+        case "ready":
+          IdentityService.doReady(aRpId);
+          break;
+
+        case "login":
+           IdentityService.doLogin(aRpId, message.assertion);
+          break;
+
+        case "logout":
+          IdentityService.doLogout(aRpId);
+          break;
+
+        default:
+          log("WARNING: wonky method call:", message.method);
+          break;
+      }
+    };
+  },
+
+  doWatch: function SignInToWebsiteController_doWatch(aOptions) {
+    // dom prevents watch from  being called twice
+    log("doWatch:", aOptions);
+    var gaiaOptions = {
+      message: kIdentityDelegateWatch,
+      showUI: false
+    };
+    this.pipe.communicate(aOptions, gaiaOptions, this._makeDoMethodCallback(aOptions.rpId));
+  },
+
+  /**
+   * The website is requesting login so the user must choose an identity to use.
+   */
+  doRequest: function SignInToWebsiteController_doRequest(aOptions) {
+    log("doRequest", aOptions);
+    // tell gaia to open the identity popup
+    var gaiaOptions = {
+      message: kIdentityDelegateRequest,
+      showUI: true
+    };
+    this.pipe.communicate(aOptions, gaiaOptions, this._makeDoMethodCallback(aOptions.rpId));
+  },
+
+  /*
+   *
+   */
+  doLogout: function SignInToWebsiteController_doLogout(aOptions) {
+    log("doLogout", aOptions);
+    var gaiaOptions = {
+      message: kIdentityDelegateLogout,
+      showUI: false
+    };
+    this.pipe.communicate(aOptions, gaiaOptions, this._makeDoMethodCallback(aOptions.rpId));
+  }
+
+};
new file mode 100644
--- /dev/null
+++ b/b2g/components/test/unit/head_identity.js
@@ -0,0 +1,134 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+const Ci = Components.interfaces;
+const Cu = Components.utils;
+
+// The following boilerplate makes sure that XPCom calls
+// that use the profile directory work.
+
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource://gre/modules/Services.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(this, "MinimalIDService",
+                                  "resource://gre/modules/identity/MinimalIdentity.jsm",
+                                  "IdentityService");
+
+XPCOMUtils.defineLazyModuleGetter(this,
+                                  "Logger",
+                                  "resource://gre/modules/identity/LogUtils.jsm");
+
+XPCOMUtils.defineLazyServiceGetter(this,
+                                   "uuidGenerator",
+                                   "@mozilla.org/uuid-generator;1",
+                                   "nsIUUIDGenerator");
+
+const TEST_URL = "https://myfavoriteflan.com";
+const TEST_USER = "uumellmahaye1969@hotmail.com";
+const TEST_PRIVKEY = "i-am-a-secret";
+const TEST_CERT = "i~like~pie";
+
+// The following are utility functions for Identity testing
+
+function log(...aMessageArgs) {
+  Logger.log.apply(Logger, ["test"].concat(aMessageArgs));
+}
+
+function partial(fn) {
+  let args = Array.prototype.slice.call(arguments, 1);
+  return function() {
+    return fn.apply(this, args.concat(Array.prototype.slice.call(arguments)));
+  };
+}
+
+function uuid() {
+  return uuidGenerator.generateUUID().toString();
+}
+
+// create a mock "doc" object, which the Identity Service
+// uses as a pointer back into the doc object
+function mockDoc(aIdentity, aOrigin, aDoFunc) {
+  let mockedDoc = {};
+  mockedDoc.id = uuid();
+  mockedDoc.loggedInEmail = aIdentity;
+  mockedDoc.origin = aOrigin;
+  mockedDoc['do'] = aDoFunc;
+  mockedDoc.doReady = partial(aDoFunc, 'ready');
+  mockedDoc.doLogin = partial(aDoFunc, 'login');
+  mockedDoc.doLogout = partial(aDoFunc, 'logout');
+  mockedDoc.doError = partial(aDoFunc, 'error');
+  mockedDoc.doCancel = partial(aDoFunc, 'cancel');
+  mockedDoc.doCoffee = partial(aDoFunc, 'coffee');
+
+  return mockedDoc;
+}
+
+// create a mock "pipe" object that would normally communicate
+// messages up to gaia (either the trusty ui or the hidden iframe),
+// and convey messages back down from gaia to the controller through
+// the message callback.
+function mockPipe() {
+  let MockedPipe = {
+    communicate: function(aRpOptions, aGaiaOptions, aMessageCallback) {
+      switch (aGaiaOptions.message) {
+        case "identity-delegate-watch":
+          aMessageCallback({json: {method: "ready"}});
+          break;
+        case "identity-delegate-request":
+          aMessageCallback({json: {method: "login", assertion: TEST_CERT}});
+          break;
+        case "identity-delegate-logout":
+          aMessageCallback({json: {method: "logout"}});
+          break;
+        default:
+          throw("what the what?? " + aGaiaOptions.message);
+          break;
+      }
+    }
+  };
+  return MockedPipe;
+}
+
+// mimicking callback funtionality for ease of testing
+// this observer auto-removes itself after the observe function
+// is called, so this is meant to observe only ONE event.
+function makeObserver(aObserveTopic, aObserveFunc) {
+  let observer = {
+    // nsISupports provides type management in C++
+    // nsIObserver is to be an observer
+    QueryInterface: XPCOMUtils.generateQI([Ci.nsISupports, Ci.nsIObserver]),
+
+    observe: function (aSubject, aTopic, aData) {
+      if (aTopic == aObserveTopic) {
+        Services.obs.removeObserver(observer, aObserveTopic);
+        aObserveFunc(aSubject, aTopic, aData);
+      }
+    }
+  };
+
+  Services.obs.addObserver(observer, aObserveTopic, false);
+}
+
+// a hook to set up the ID service with an identity with keypair and all
+// when ready, invoke callback with the identity.  It's there if we need it.
+function setup_test_identity(identity, cert, cb) {
+  cb();
+}
+
+// takes a list of functions and returns a function that
+// when called the first time, calls the first func,
+// then the next time the second, etc.
+function call_sequentially() {
+  let numCalls = 0;
+  let funcs = arguments;
+
+  return function() {
+    if (!funcs[numCalls]) {
+      let argString = Array.prototype.slice.call(arguments).join(",");
+      do_throw("Too many calls: " + argString);
+      return;
+    }
+    funcs[numCalls].apply(funcs[numCalls],arguments);
+    numCalls += 1;
+  };
+}
new file mode 100644
--- /dev/null
+++ b/b2g/components/test/unit/test_signintowebsite.js
@@ -0,0 +1,162 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// Tests for b2g/components/SignInToWebsite.jsm
+
+"use strict";
+
+XPCOMUtils.defineLazyModuleGetter(this, "MinimalIDService",
+                                  "resource://gre/modules/identity/MinimalIdentity.jsm",
+                                  "IdentityService");
+
+XPCOMUtils.defineLazyModuleGetter(this, "SignInToWebsiteController",
+                                  "resource://gre/modules/SignInToWebsite.jsm",
+                                  "SignInToWebsiteController");
+
+Cu.import("resource://gre/modules/identity/LogUtils.jsm");
+
+function log(...aMessageArgs) {
+  Logger.log.apply(Logger, ["test_signintowebsite"].concat(aMessageArgs));
+}
+
+function test_overall() {
+  do_check_neq(MinimalIDService, null);
+  run_next_test();
+}
+
+function test_mock_doc() {
+  do_test_pending();
+  let mockedDoc = mockDoc(null, TEST_URL, function(action, params) {
+    do_check_eq(action, 'coffee');
+    do_test_finished();
+    run_next_test();
+  });
+
+  // A smoke test to ensure that mockedDoc is functioning correctly.
+  // There is presently no doCoffee method in Persona.
+  mockedDoc.doCoffee();
+}
+
+function test_watch() {
+  do_test_pending();
+
+  setup_test_identity("pie@food.gov", TEST_CERT, function() {
+    let controller = SignInToWebsiteController;
+
+    let mockedDoc = mockDoc(null, TEST_URL, function(action, params) {
+      do_check_eq(action, 'ready');
+      controller.uninit();
+      do_test_finished();
+      run_next_test();
+    });
+
+    controller.init({pipe: mockPipe()});
+
+    MinimalIDService.RP.watch(mockedDoc, {});
+  });
+}
+
+function test_request_login() {
+  do_test_pending();
+
+  setup_test_identity("flan@food.gov", TEST_CERT, function() {
+    let controller = SignInToWebsiteController;
+
+    let mockedDoc = mockDoc(null, TEST_URL, call_sequentially(
+      function(action, params) {
+        do_check_eq(action, 'ready');
+        do_check_eq(params, undefined);
+      },
+      function(action, params) {
+        do_check_eq(action, 'login');
+        do_check_eq(params, TEST_CERT);
+        controller.uninit();
+        do_test_finished();
+        run_next_test();
+      }
+    ));
+
+    controller.init({pipe: mockPipe()});
+    MinimalIDService.RP.watch(mockedDoc, {});
+    MinimalIDService.RP.request(mockedDoc.id, {});
+  });
+}
+
+function test_request_logout() {
+  do_test_pending();
+
+  setup_test_identity("flan@food.gov", TEST_CERT, function() {
+    let controller = SignInToWebsiteController;
+
+    let mockedDoc = mockDoc(null, TEST_URL, call_sequentially(
+      function(action, params) {
+        do_check_eq(action, 'ready');
+        do_check_eq(params, undefined);
+      },
+      function(action, params) {
+        do_check_eq(action, 'logout');
+        do_check_eq(params, undefined);
+        controller.uninit();
+        do_test_finished();
+        run_next_test();
+      }
+    ));
+
+    controller.init({pipe: mockPipe()});
+    MinimalIDService.RP.watch(mockedDoc, {});
+    MinimalIDService.RP.logout(mockedDoc.id, {});
+  });
+}
+
+function test_request_login_logout() {
+  do_test_pending();
+
+  setup_test_identity("unagi@food.gov", TEST_CERT, function() {
+    let controller = SignInToWebsiteController;
+
+    let mockedDoc = mockDoc(null, TEST_URL, call_sequentially(
+      function(action, params) {
+        do_check_eq(action, 'ready');
+        do_check_eq(params, undefined);
+      },
+      function(action, params) {
+        do_check_eq(action, 'login');
+        do_check_eq(params, TEST_CERT);
+      },
+      function(action, params) {
+        do_check_eq(action, 'logout');
+        do_check_eq(params, undefined);
+        controller.uninit();
+        do_test_finished();
+        run_next_test();
+      }
+      /*
+      ,function(action, params) {
+        do_check_eq(action, 'ready');
+        do_test_finished();
+        run_next_test();
+      }
+       */
+    ));
+
+    controller.init({pipe: mockPipe()});
+    MinimalIDService.RP.watch(mockedDoc, {});
+    MinimalIDService.RP.request(mockedDoc.id, {});
+    MinimalIDService.RP.logout(mockedDoc.id, {});
+  });
+}
+
+let TESTS = [
+  test_overall,
+  test_mock_doc,
+  test_watch,
+  test_request_login,
+  test_request_logout,
+  test_request_login_logout
+];
+
+TESTS.forEach(add_test);
+
+function run_test() {
+  run_next_test();
+}
--- a/b2g/components/test/unit/xpcshell.ini
+++ b/b2g/components/test/unit/xpcshell.ini
@@ -1,5 +1,11 @@
 [DEFAULT]
 head =
 tail =
 
 [test_bug793310.js]
+
+[test_signintowebsite.js]
+head = head_identity.js
+tail =
+
+
--- a/b2g/installer/package-manifest.in
+++ b/b2g/installer/package-manifest.in
@@ -220,16 +220,17 @@
 @BINPATH@/components/filepicker.xpt
 #endif
 @BINPATH@/components/find.xpt
 @BINPATH@/components/fuel.xpt
 @BINPATH@/components/gfx.xpt
 @BINPATH@/components/hal.xpt
 @BINPATH@/components/html5.xpt
 @BINPATH@/components/htmlparser.xpt
+@BINPATH@/components/identity.xpt
 @BINPATH@/components/imglib2.xpt
 @BINPATH@/components/imgicon.xpt
 @BINPATH@/components/inspector.xpt
 @BINPATH@/components/intl.xpt
 @BINPATH@/components/jar.xpt
 @BINPATH@/components/jetpack.xpt
 @BINPATH@/components/jsdebugger.xpt
 @BINPATH@/components/jsdservice.xpt
@@ -487,16 +488,20 @@
 #endif
 @BINPATH@/components/TelemetryPing.js
 @BINPATH@/components/TelemetryPing.manifest
 @BINPATH@/components/Webapps.js
 @BINPATH@/components/Webapps.manifest
 @BINPATH@/components/AppsService.js
 @BINPATH@/components/AppsService.manifest
 
+@BINPATH@/components/nsDOMIdentity.js
+@BINPATH@/components/nsIDService.js
+@BINPATH@/components/Identity.manifest
+
 @BINPATH@/components/SystemMessageInternal.js
 @BINPATH@/components/SystemMessageManager.js
 @BINPATH@/components/SystemMessageManager.manifest
 
 @BINPATH@/components/Activities.manifest
 @BINPATH@/components/ActivityOptions.js
 @BINPATH@/components/ActivityProxy.js
 @BINPATH@/components/ActivityRequestHandler.js
--- a/dom/identity/DOMIdentity.jsm
+++ b/dom/identity/DOMIdentity.jsm
@@ -8,22 +8,30 @@ const {classes: Cc, interfaces: Ci, util
 
 // This is the parent process corresponding to nsDOMIdentity.
 let EXPORTED_SYMBOLS = ["DOMIdentity"];
 
 Cu.import("resource://gre/modules/Services.jsm");
 Cu.import("resource://gre/modules/XPCOMUtils.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));
 }
 
 function IDDOMMessage(aID) {
   this.id = aID;
 }
 
@@ -117,19 +125,17 @@ RPWatchContext.prototype = {
 
 let 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
-                           .QueryInterface(Ci.nsIFrameLoaderOwner)
-                           .frameLoader.messageManager;
+    let targetMM = aMessage.target;
 
     switch (aMessage.name) {
       // RP
       case "Identity:RP:Watch":
         this._watch(msg, targetMM);
         break;
       case "Identity:RP:Request":
         this._request(msg);
@@ -160,53 +166,50 @@ let DOMIdentity = {
         this._authenticationFailure(msg);
         break;
     }
   },
 
   // nsIObserver
   observe: function DOMIdentity_observe(aSubject, aTopic, aData) {
     switch (aTopic) {
-      case "domwindowopened":
-      case "domwindowclosed":
-        let win = aSubject.QueryInterface(Ci.nsIInterfaceRequestor)
-                          .getInterface(Ci.nsIDOMWindow);
-        this._configureMessages(win, aTopic == "domwindowopened");
-        break;
-
       case "xpcom-shutdown":
+        this._unsubscribeListeners();
+        Services.obs.removeObserver(this, "xpcom-shutdown");
         Services.ww.unregisterNotification(this);
-        Services.obs.removeObserver(this, "xpcom-shutdown");
         break;
     }
   },
 
   messages: ["Identity:RP:Watch", "Identity:RP:Request", "Identity:RP:Logout",
              "Identity:IDP:BeginProvisioning", "Identity:IDP:ProvisioningFailure",
              "Identity:IDP:RegisterCertificate", "Identity:IDP:GenKeyPair",
              "Identity:IDP:BeginAuthentication",
              "Identity:IDP:CompleteAuthentication",
              "Identity:IDP:AuthenticationFailure"],
 
   // Private.
   _init: function DOMIdentity__init() {
     Services.ww.registerNotification(this);
     Services.obs.addObserver(this, "xpcom-shutdown", false);
+    this._subscribeListeners();
   },
 
-  _configureMessages: function DOMIdentity__configureMessages(aWindow, aRegister) {
-    if (!aWindow.messageManager)
-      return;
+  _subscribeListeners: function DOMIdentity__subscribeListeners() {
+    if (!ppmm) return;
+    for (let message of this.messages) {
+      ppmm.addMessageListener(message, this);
+    }
+  },
 
-    let func = aWindow.messageManager[aRegister ? "addMessageListener"
-                                                : "removeMessageListener"];
-
+  _unsubscribeListeners: function DOMIdentity__unsubscribeListeners() {
     for (let message of this.messages) {
-      func.call(aWindow.messageManager, message, this);
+      ppmm.removeMessageListener(message, this);
     }
+    ppmm = null;
   },
 
   _resetFrameState: function(aContext) {
     log("_resetFrameState: ", aContext.id);
     if (!aContext._mm) {
       throw new Error("ERROR: Trying to reset an invalid context");
     }
     let message = new IDDOMMessage(aContext.id);
--- a/dom/identity/Makefile.in
+++ b/dom/identity/Makefile.in
@@ -12,17 +12,20 @@ relativesrcdir   = @relativesrcdir@
 include $(DEPTH)/config/autoconf.mk
 
 EXTRA_COMPONENTS = \
     nsDOMIdentity.js \
     nsIDService.js \
     Identity.manifest \
     $(NULL)
 
+EXTRA_PP_JS_MODULES = \
+    DOMIdentity.jsm \
+    $(NULL)
+
 EXTRA_JS_MODULES = \
-    DOMIdentity.jsm \
     $(NULL)
 
 ifdef ENABLE_TESTS
 DIRS += tests
 endif
 
 include $(topsrcdir)/config/rules.mk
--- a/dom/identity/nsDOMIdentity.js
+++ b/dom/identity/nsDOMIdentity.js
@@ -12,16 +12,20 @@ const PREF_ENABLED = "dom.identity.enabl
 // Maximum length of a string that will go through IPC
 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");
 
+XPCOMUtils.defineLazyServiceGetter(this, "cpmm",
+                                   "@mozilla.org/childprocessmessagemanager;1",
+                                   "nsIMessageSender");
+
 // This is the child process corresponding to nsIDOMIdentity.
 
 
 function nsDOMIdentity(aIdentityInternal) {
   this._identityInternal = aIdentityInternal;
 }
 nsDOMIdentity.prototype = {
   __exposedProps__: {
@@ -478,20 +482,17 @@ nsDOMIdentityInternal.prototype = {
 
     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);
 
-    this._mm = aWindow.QueryInterface(Ci.nsIInterfaceRequestor)
-                      .getInterface(Ci.nsIWebNavigation)
-                      .QueryInterface(Ci.nsIInterfaceRequestor)
-                      .getInterface(Ci.nsIContentFrameMessageManager);
+    this._mm = cpmm;
 
     // Setup listeners for messages from parent process.
     this._messages = [
       "Identity:ResetState",
       "Identity:RP:Watch:OnLogin",
       "Identity:RP:Watch:OnLogout",
       "Identity:RP:Watch:OnReady",
       "Identity:RP:Request:OnCancel",
--- a/dom/identity/tests/Makefile.in
+++ b/dom/identity/tests/Makefile.in
@@ -6,16 +6,11 @@ DEPTH            = @DEPTH@
 topsrcdir        = @top_srcdir@
 srcdir           = @srcdir@
 VPATH            = @srcdir@
 
 relativesrcdir   = @relativesrcdir@
 
 include $(DEPTH)/config/autoconf.mk
 
-MOCHITEST_FILES = \
-  head_identity.js \
-  test_identity_idp_auth_basics.html \
-  test_identity_idp_prov_basics.html \
-  test_identity_rp_basics.html \
-  $(NULL)
+# XXX bug 805602 update and restore mochitests
 
 include $(topsrcdir)/config/rules.mk
deleted file mode 100644
--- a/dom/identity/tests/head_identity.js
+++ /dev/null
@@ -1,82 +0,0 @@
-/* Any copyright is dedicated to the Public Domain.
- * http://creativecommons.org/publicdomain/zero/1.0/ */
-
-"use strict";
-
-const Ci = SpecialPowers.Ci;
-const Cu = SpecialPowers.Cu;
-
-SpecialPowers.setBoolPref("toolkit.identity.debug", true);
-SpecialPowers.setBoolPref("dom.identity.enabled", true);
-
-const Services = Cu.import("resource://gre/modules/Services.jsm").Services;
-const DOMIdentity = Cu.import("resource://gre/modules/DOMIdentity.jsm")
-                      .DOMIdentity;
-
-let util = SpecialPowers.getDOMWindowUtils(window);
-let outerWinId = util.outerWindowID;
-
-const identity = navigator.id || navigator.mozId;
-
-let index = 0;
-
-// mimicking callback funtionality for ease of testing
-// this observer auto-removes itself after the observe function
-// is called, so this is meant to observe only ONE event.
-function makeObserver(aObserveTopic, aObserveFunc) {
-  function observe(aSubject, aTopic, aData) {
-    if (aTopic == aObserveTopic) {
-      aObserveFunc(aSubject, aTopic, aData);
-      Services.obs.removeObserver(this, aObserveTopic);
-    }
-  }
-
-  Services.obs.addObserver(observe, aObserveTopic, false);
-}
-
-function expectException(aFunc, msg, aErrorType="Error") {
-  info("Expecting an exception: " + msg);
-  msg = msg || "";
-  let caughtEx = null;
-  try {
-    aFunc();
-  } catch (ex) {
-    let exProto = Object.getPrototypeOf(ex);
-    // Don't count NS_* exceptions since they shouldn't be exposed to content
-    if (exProto.toString() == aErrorType
-        && ex.toString().indexOf("NS_ERROR_FAILURE") == -1) {
-      caughtEx = ex;
-    } else {
-      ok(false, ex);
-      return;
-    }
-  }
-  isnot(caughtEx, null, "Check for thrown exception.");
-}
-
-function next() {
-  if (!identity) {
-    todo(false, "DOM API is not available. Skipping tests.");
-    finish_tests();
-    return;
-  }
-  if (index >= steps.length) {
-    ok(false, "Shouldn't get here!");
-    return;
-  }
-  try {
-    let fn = steps[index];
-    info("Begin test " + index + " '" + steps[index].name + "'!");
-    fn();
-  } catch(ex) {
-    ok(false, "Caught exception", ex);
-  }
-  index += 1;
-}
-
-function finish_tests() {
-  info("all done");
-  SpecialPowers.clearUserPref("toolkit.identity.debug");
-  SpecialPowers.clearUserPref("dom.identity.enabled");
-  SimpleTest.finish();
-}
deleted file mode 100644
--- a/dom/identity/tests/test_identity_idp_auth_basics.html
+++ /dev/null
@@ -1,87 +0,0 @@
-<!DOCTYPE html>
-<!-- Any copyright is dedicated to the Public Domain.
-   - http://creativecommons.org/publicdomain/zero/1.0/ -->
-<html>
-<head>
-  <meta charset="utf-8">
-  <title>Test for navigator.id identity provider (IDP) authentication basics</title>
-  <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
-  <script type="application/javascript;version=1.8" src="head_identity.js"></script>
-  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
-</head>
-<body>
-<a target="_blank">navigator.id identity provider (IDP) authentication basics</a>
-<p id="display"></p>
-<div id="content" style="display: none">
-
-</div>
-<pre id="test">
-<script type="application/javascript;version=1.8">
-
-"use strict"
-
-let steps = [
-  // completeAuthentication tests
-  function completeAuthenticationExists() {
-    is(typeof(identity.completeAuthentication), "function",
-       "Check completeAuthentication is a function");
-    SimpleTest.executeSoon(next);
-  },
-  function completeAuthenticationOutsideFlow() {
-    expectException(function() {
-      identity.completeAuthentication();
-    }, "Check completeAuthentication outside of an auth. flow");
-    SimpleTest.executeSoon(next);
-  },
-
-  // raiseAuthenticationFailure tests
-  function raiseAuthenticationFailureExists() {
-    is(typeof(identity.raiseAuthenticationFailure), "function",
-       "Check raiseAuthenticationFailure is a function");
-    SimpleTest.executeSoon(next);
-  },
-  function raiseAuthenticationFailureNoArgs() {
-    expectException(function() {
-      identity.raiseAuthenticationFailure();
-    }, "raiseAuthenticationFailure with no arguments");
-    SimpleTest.executeSoon(next);
-  },
-
-  // beginAuthentication tests
-  function beginAuthenticationExists() {
-    is(typeof(identity.beginAuthentication), "function",
-       "Check beginAuthentication is a function");
-    SimpleTest.executeSoon(next);
-  },
-  function beginAuthenticationNoArgs() {
-    expectException(function() {
-      identity.beginAuthentication();
-    }, "beginAuthentication with no arguments");
-    SimpleTest.executeSoon(next);
-  },
-  function beginAuthenticationInvalidArg() {
-    expectException(function() {
-      identity.beginAuthentication(999);
-    }, "beginAuthentication with a non-function argument");
-    SimpleTest.executeSoon(next);
-  },
-  function beginAuthenticationArgs() {
-    function beginAuthenticationCb() {
-      throw "beginAuthentication callback shouldn't have been called outside of an "
-              + "auth flow";
-    }
-    is(identity.beginAuthentication(beginAuthenticationCb), undefined,
-       "Check minimum beginAuthentication arguments");
-    SimpleTest.executeSoon(next);
-  },
-
-  finish_tests,
-];
-
-SimpleTest.waitForExplicitFinish();
-addLoadEvent(next);
-
-</script>
-</pre>
-</body>
-</html>
deleted file mode 100644
--- a/dom/identity/tests/test_identity_idp_prov_basics.html
+++ /dev/null
@@ -1,160 +0,0 @@
-<!DOCTYPE html>
-<!-- Any copyright is dedicated to the Public Domain.
-   - http://creativecommons.org/publicdomain/zero/1.0/ -->
-<html>
-<head>
-  <meta charset="utf-8">
-  <title>Test for navigator.id identity provider (IDP) provisioning basics</title>
-  <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
-  <script type="application/javascript;version=1.8" src="head_identity.js"></script>
-  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
-</head>
-<body>
-<a target="_blank">navigator.id identity provider (IDP) provisioning basics</a>
-<p id="display"></p>
-<div id="content" style="display: none">
-
-</div>
-<pre id="test">
-<script type="application/javascript;version=1.8">
-
-"use strict"
-
-let IDP = Cu.import("resource://gre/modules/identity/IdentityProvider.jsm").IdentityProvider;
-
-function setupProv() {
-  info("setupProv");
-  // Add a provisioning flow so the DOM calls succeed
-  IDP._provisionFlows[outerWinId] = {
-    sandbox: {},
-    callback: function doCallback(aErr) {
-      info("provisioning callback: " + aErr);
-    },
-  }
-}
-
-function resetAndNext() {
-  info("resetAndNext");
-  // reset DOM state for the next test
-  // Give the flow some time to cross the IPC boundary
-  setTimeout(function() {
-    let provContext = IDP._provisionFlows[outerWinId];
-    if (!provContext) {
-      SimpleTest.executeSoon(next);
-      return;
-    }
-    makeObserver("identity-DOM-state-reset", function() {
-      info("reset done");
-      SimpleTest.executeSoon(next);
-    });
-    DOMIdentity._resetFrameState(provContext.caller);
-  }, 700);
-}
-
-let steps = [
-  // genKeyPair tests
-  function genKeyPairExists() {
-    is(typeof(identity.genKeyPair), "function",
-       "Check genKeyPair is a function");
-    SimpleTest.executeSoon(next);
-  },
-  function genKeyPairOutsideProv() {
-    expectException(function(){
-      identity.genKeyPair(function(){});
-    }, "Check genKeyPair outside of a prov. flow");
-    SimpleTest.executeSoon(next);
-  },
-  function genKeyPairNoArgs() {
-    setupProv();
-    identity.beginProvisioning(function() {
-      expectException(function() {
-        identity.genKeyPair();
-      }, "genKeyPair with no arguments");
-      SimpleTest.executeSoon(resetAndNext);
-    });
-  },
-  function genKeyPairInvalidArg() {
-    setupProv();
-    identity.beginProvisioning(function() {
-      expectException(function() {
-        identity.genKeyPair(999);
-      }, "Check genKeyPair with non-function object argument");
-      SimpleTest.executeSoon(resetAndNext);
-    });
-  },
-
-  // registerCertificate tests
-  function registerCertificateExists() {
-    is(typeof(identity.registerCertificate), "function",
-       "Check registerCertificate is a function");
-    SimpleTest.executeSoon(next);
-  },
-  function registerCertificateNoArgs() {
-    setupProv();
-    identity.beginProvisioning(function() {
-      expectException(function() {
-        identity.registerCertificate();
-      }, "Check registerCertificate with no arguments");
-    });
-    SimpleTest.executeSoon(resetAndNext);
-  },
-  function registerCertificateOutsideProv() {
-    expectException(function(){
-      identity.registerCertificate("foo");
-    }, "Check registerCertificate outside of a prov. flow");
-    SimpleTest.executeSoon(next);
-  },
-
-  // raiseProvisioningFailure tests
-  function raiseProvisioningFailureExists() {
-    is(typeof(identity.raiseProvisioningFailure), "function",
-       "Check raiseProvisioningFailure is a function");
-    SimpleTest.executeSoon(next);
-  },
-  function raiseProvisioningFailureNoArgs() {
-    expectException(function() {
-      identity.raiseProvisioningFailure();
-    }, "raiseProvisioningFailure with no arguments");
-    SimpleTest.executeSoon(next);
-  },
-  function raiseProvisioningFailureWithReason() {
-    identity.raiseProvisioningFailure("my test reason");
-    SimpleTest.executeSoon(next);
-  },
-
-  // beginProvisioning tests
-  function beginProvisioningExists() {
-    is(typeof(identity.beginProvisioning), "function",
-       "Check beginProvisioning is a function");
-    SimpleTest.executeSoon(next);
-  },
-  function beginProvisioningNoArgs() {
-    expectException(function() {
-      identity.beginProvisioning();
-    }, "beginProvisioning with no arguments");
-    SimpleTest.executeSoon(next);
-  },
-  function beginProvisioningInvalidArg() {
-    expectException(function() {
-      identity.beginProvisioning(999);
-    }, "beginProvisioning with a non-function argument");
-    SimpleTest.executeSoon(next);
-  },
-  function beginProvisioningArgs() {
-    function beginProvisioningCb() {
-      SimpleTest.executeSoon(resetAndNext);
-    }
-    is(identity.beginProvisioning(beginProvisioningCb), undefined,
-       "Check minimum beginProvisioning arguments");
-  },
-
-  finish_tests,
-];
-
-SimpleTest.waitForExplicitFinish();
-addLoadEvent(next);
-
-</script>
-</pre>
-</body>
-</html>
deleted file mode 100644
--- a/dom/identity/tests/test_identity_rp_basics.html
+++ /dev/null
@@ -1,177 +0,0 @@
-<!DOCTYPE html>
-<!-- Any copyright is dedicated to the Public Domain.
-   - http://creativecommons.org/publicdomain/zero/1.0/ -->
-<html>
-<head>
-  <meta charset="utf-8">
-  <title>Test for navigator.id relying party (RP) basics</title>
-  <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
-  <script type="application/javascript;version=1.8" src="head_identity.js"></script>
-  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
-</head>
-<body>
-<a target="_blank">navigator.id RP basics</a>
-<p id="display"></p>
-<div id="content">
-  <button id='request'>request</button>
-</div>
-<pre id="test">
-<script type="application/javascript;version=1.8">
-
-"use strict";
-
-const RP = Cu.import("resource://gre/modules/identity/RelyingParty.jsm").RelyingParty;
-
-function resetAndNext() {
-  // reset DOM state for the next test
-  makeObserver("identity-DOM-state-reset", function() {
-    SimpleTest.executeSoon(next);
-  });
-  // Give the flow some time to cross the IPC boundary
-  setTimeout(function() {
-    let rpContext = RP._rpFlows[outerWinId];
-    if (!rpContext) {
-      SimpleTest.executeSoon(next);
-      return;
-    }
-    DOMIdentity._resetFrameState(rpContext);
-  }, 700);
-}
-
-let steps = [
-  function nonExistentProp() {
-    is(identity.foobarbaz, undefined, "Check that foobarbaz does not exist");
-    expectException(function() {
-      identity.foobarbaz()
-    }, "Check for exception calling non-existent method", "TypeError");
-    SimpleTest.executeSoon(next);
-  },
-
-  // test request before watch throws an exception
-  function requestBeforeWatch() {
-    var button = document.getElementById('request');
-    button.addEventListener('click', function requestHandler() {
-      button.removeEventListener('click', requestHandler);
-
-      expectException(function() {
-        identity.request();
-      });
-    });
-    SimpleTest.executeSoon(next);
-  },
-
-  // watch tests
-  function watchExists() {
-    is(typeof(identity.watch), "function", "Check watch is a function");
-    SimpleTest.executeSoon(next);
-  },
-  function watchNoArgs() {
-    expectException(function() {
-      identity.watch();
-    }, "watch with no arguments");
-    SimpleTest.executeSoon(next);
-  },
-  function watchEmptyObj() {
-    expectException(function() {
-      identity.watch({});
-    }, "watch with empty object argument");
-    SimpleTest.executeSoon(next);
-  },
-  function watchOnLoginBool() {
-    expectException(function() {
-      identity.watch({onlogin: true});
-    }, "watch with invalid onlogin member");
-    SimpleTest.executeSoon(next);
-  },
-  function watchOnLoginLogoutBool() {
-    expectException(function() {
-      identity.watch({onlogin: true, onlogout: false});
-    }, "watch with invalid onlogin and onlogout members");
-    SimpleTest.executeSoon(next);
-  },
-  function watchMinimumArgs() {
-    function onLoginLogoutCb() {
-      throw "onlogin/onlogout callback shouldn't have been called";
-    }
-    is(identity.watch({onlogin: onLoginLogoutCb, onlogout: onLoginLogoutCb}),
-       undefined, "Check minimum watch argument members");
-    resetAndNext();
-  },
-  function watchOnReadyType() {
-    function onLoginLogoutCb() {
-      throw "onlogin/onlogout callback shouldn't have been called";
-    }
-    let options = {
-      onlogin: onLoginLogoutCb,
-      onlogout: onLoginLogoutCb,
-      onready: 999,
-    }
-    expectException(function() {
-      identity.watch(options)
-    }, "Check onready type");
-    resetAndNext();
-  },
-  function watchLoggedInEmailType() {
-    function onLoginLogoutCb() {
-      throw "onlogin/onlogout callback shouldn't have been called";
-    }
-    let options = {
-      onlogin: onLoginLogoutCb,
-      onlogout: onLoginLogoutCb,
-      loggedInEmail: {},
-    }
-    expectException(function() {
-      identity.watch(options)
-    }, "Check loggedInEmail type");
-    resetAndNext();
-  },
-  function watchOnReadyCalled() {
-    let onLogoutCalled = false;
-    let options = {
-      loggedInEmail: "loggedOut@user.com",
-      onlogin: function onLoginCb(assertion) {
-        throw "onlogin/onlogout callback shouldn't have been called";
-      },
-      onlogout: function onLogoutCb() {
-        is(arguments.length, 0, "Check onlogout argument length");
-        onLogoutCalled = true;
-      },
-      onready: function onReady() {
-        is(arguments.length, 0, "Check onready argument length");
-        ok(onLogoutCalled, "onlogout callback should be called before onready");
-        SimpleTest.executeSoon(next);
-      },
-    }
-    is(identity.watch(options), undefined, "Check onready is called");
-  },
-
-  // request tests
-  function requestExists() {
-    is(typeof(identity.request), "function", "Check request is a function");
-    SimpleTest.executeSoon(next);
-  },
-  function requestNoArgs() {
-    is(identity.request(), undefined, "Check request with no arguments");
-    SimpleTest.executeSoon(next);
-  },
-  function requestEmptyObj() {
-    is(identity.request({}), undefined, "Check request with empty object argument");
-    SimpleTest.executeSoon(next);
-  },
-
-  // logout tests
-  function logoutExists() {
-    is(typeof(identity.logout), "function", "Check logout is a function");
-    SimpleTest.executeSoon(next);
-  },
-
-  finish_tests,
-];
-
-SimpleTest.waitForExplicitFinish();
-addLoadEvent(next);
-
-</script>
-</pre>
-</body>
-</html>
--- a/modules/libpref/src/init/all.js
+++ b/modules/libpref/src/init/all.js
@@ -353,16 +353,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("toolkit.identity.debug", false);
 
 // Disable remote debugging protocol logging
 pref("devtools.debugger.log", false);
 // Disable remote debugging connections
 pref("devtools.debugger.remote-enabled", false);
 pref("devtools.debugger.remote-port", 6000);
 // Force debugger server binding on the loopback interface
@@ -3792,18 +3793,16 @@ pref("memory.low_memory_notification_int
 pref("memory.ghost_window_timeout_seconds", 60);
 
 pref("social.enabled", false);
 
 // Disable idle observer fuzz, because only privileged content can access idle
 // observers (bug 780507).
 pref("dom.idle-observers-api.fuzz_time.disabled", true);
 
-pref("toolkit.identity.debug", false);
-
 // Setting that to true grant elevated privileges to apps that ask
 // for them in their manifest.
 pref("dom.mozApps.dev_mode", false);
 
 // Lowest localId for apps.
 pref("dom.mozApps.maxLocalId", 1000);
 
 // Minimum delay in milliseconds between network activity notifications (0 means
--- a/toolkit/identity/Makefile.in
+++ b/toolkit/identity/Makefile.in
@@ -30,16 +30,17 @@ CPPSRCS = \
   $(NULL)
 
 EXTRA_JS_MODULES = \
 	Identity.jsm \
 	IdentityProvider.jsm \
 	IdentityStore.jsm \
 	jwcrypto.jsm \
 	LogUtils.jsm \
+	MinimalIdentity.jsm \
 	RelyingParty.jsm \
 	Sandbox.jsm \
 	$(NULL)
 
 ifdef ENABLE_TESTS
     DIRS += tests
 endif
 
new file mode 100644
--- /dev/null
+++ b/toolkit/identity/MinimalIdentity.jsm
@@ -0,0 +1,376 @@
+/* -*- 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/. */
+
+/*
+ * This alternate implementation of IdentityService provides just the
+ * channels for navigator.id, leaving the certificate storage to a
+ * server-provided app.
+ *
+ * On b2g, the messages identity-controller-watch, -request, and
+ * -logout, are observed by the component SignInToWebsite.jsm.
+ */
+
+"use strict";
+
+const 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");
+
+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));
+}
+
+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) {
+    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)
+   *                  - loggedInEmail (string or null)
+   *                  - origin (string)
+   *
+   *                  and a bunch of callbacks
+   *                  - doReady()
+   *                  - doLogin()
+   *                  - doLogout()
+   *                  - doError()
+   *                  - doCancel()
+   *
+   */
+  watch: function watch(aRpCaller) {
+    log("watch: caller keys:", Object.keys(aRpCaller));
+    log("watch: rpcaller:", aRpCaller);
+    // store the caller structure and notify the UI observers
+
+    // note that, unfortunately, what here is loggedInEmail is called
+    // loggedInUser in the native API.
+    this._rpFlows[aRpCaller.id] = aRpCaller;
+    let options = {rpId: aRpCaller.id,
+                   origin: aRpCaller.origin,
+                   loggedInUser: aRpCaller.loggedInEmail || null};
+    log("sending identity-controller-watch:", options);
+    Services.obs.notifyObservers({wrappedJSObject: options},"identity-controller-watch", 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) {
+    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};
+    Services.obs.notifyObservers({wrappedJSObject: options}, "identity-controller-request", null);
+  },
+
+  /**
+   * 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);
+    let rp = this._rpFlows[aRpCallerId];
+
+    let options = {rpId: aRpCallerId, origin: rp.origin};
+    Services.obs.notifyObservers({wrappedJSObject: options}, "identity-controller-logout", null);
+  },
+
+  /*
+   * 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) {
+    let rp = this._rpFlows[aRpCallerId];
+    if (!rp)
+      return;
+
+    rp.doLogin(aAssertion);
+  },
+
+  doLogout: function doLogout(aRpCallerId) {
+    let rp = this._rpFlows[aRpCallerId];
+    if (!rp)
+      return;
+
+    rp.doLogout();
+  },
+
+  doReady: function doReady(aRpCallerId) {
+    let rp = this._rpFlows[aRpCallerId];
+    if (!rp)
+      return;
+
+    rp.doReady();
+  },
+
+
+  /*
+   * 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);
+  },
+
+};
+
+let IdentityService = new IDService();
--- a/toolkit/identity/tests/mochitest/Makefile.in
+++ b/toolkit/identity/tests/mochitest/Makefile.in
@@ -5,16 +5,11 @@
 DEPTH          = @DEPTH@
 topsrcdir      = @top_srcdir@
 srcdir         = @srcdir@
 VPATH          = @srcdir@
 relativesrcdir = @relativesrcdir@
 
 include $(DEPTH)/config/autoconf.mk
 
-MOCHITEST_FILES = \
-		head_identity.js \
-		test_authentication.html \
-		test_provisioning.html \
-		test_relying_party.html \
-		$(NULL)
+# XXX bug 805602 update and restore mochitests
 
 include $(topsrcdir)/config/rules.mk
deleted file mode 100644
--- a/toolkit/identity/tests/mochitest/head_identity.js
+++ /dev/null
@@ -1,221 +0,0 @@
-/* Any copyright is dedicated to the Public Domain.
- * http://creativecommons.org/publicdomain/zero/1.0/ */
-
-/** Helper functions for identity mochitests **/
-/** Please keep functions in-sync with unit/head_identity.js **/
-
-"use strict";
-
-const Cc = SpecialPowers.Cc;
-const Ci = SpecialPowers.Ci;
-const Cu = SpecialPowers.Cu;
-
-const TEST_URL = "http://mochi.test:8888";
-const TEST_URL2 = "https://myfavoritebaconinacan.com";
-const TEST_USER = "user@example.com";
-const TEST_PRIVKEY = "fake-privkey";
-const TEST_CERT = "fake-cert";
-const TEST_IDPPARAMS = {
-  domain: "example.com",
-  idpParams: {
-    authentication: "/foo/authenticate.html",
-    provisioning: "/foo/provision.html"
-  },
-};
-
-const Services = Cu.import("resource://gre/modules/Services.jsm").Services;
-
-// Set the debug pref before loading other modules
-SpecialPowers.setBoolPref("toolkit.identity.debug", true);
-SpecialPowers.setBoolPref("dom.identity.enabled", true);
-
-// Shutdown the UX if it exists so that it won't interfere with tests by also responding to
-// observer notifications.
-try {
-  const SignInToWebsiteUX = Cu.import("resource:///modules/SignInToWebsite.jsm").SignInToWebsiteUX;
-  if (SignInToWebsiteUX) {
-    SignInToWebsiteUX.uninit();
-  }
-} catch (ex) {
-  // The module doesn't exist
-}
-
-const jwcrypto = Cu.import("resource://gre/modules/identity/jwcrypto.jsm").jwcrypto;
-const IdentityStore = Cu.import("resource://gre/modules/identity/IdentityStore.jsm").IdentityStore;
-const RelyingParty = Cu.import("resource://gre/modules/identity/RelyingParty.jsm").RelyingParty;
-const XPCOMUtils = Cu.import("resource://gre/modules/XPCOMUtils.jsm").XPCOMUtils;
-const IDService = Cu.import("resource://gre/modules/identity/Identity.jsm").IdentityService;
-const IdentityProvider = Cu.import("resource://gre/modules/identity/IdentityProvider.jsm").IdentityProvider;
-
-const identity = navigator.id || navigator.mozId;
-
-function do_check_null(aVal, aMsg) {
-  is(aVal, null, aMsg);
-}
-
-function do_timeout(aDelay, aFunc) {
-  setTimeout(aFunc, aDelay);
-}
-
-function get_idstore() {
-  return IdentityStore;
-}
-
-// create a mock "watch" object, which the Identity Service
-function mock_watch(aIdentity, aDoFunc) {
-  function partial(fn) {
-    let args = Array.prototype.slice.call(arguments, 1);
-    return function() {
-      return fn.apply(this, args.concat(Array.prototype.slice.call(arguments)));
-    };
-  }
-
-  let mockedWatch = {};
-  mockedWatch.loggedInEmail = aIdentity;
-  mockedWatch['do'] = aDoFunc;
-  mockedWatch.onready = partial(aDoFunc, 'ready');
-  mockedWatch.onlogin = partial(aDoFunc, 'login');
-  mockedWatch.onlogout = partial(aDoFunc, 'logout');
-
-  return mockedWatch;
-}
-
-// mimicking callback funtionality for ease of testing
-// this observer auto-removes itself after the observe function
-// is called, so this is meant to observe only ONE event.
-function makeObserver(aObserveTopic, aObserveFunc) {
-  function observe(aSubject, aTopic, aData) {
-    if (aTopic == aObserveTopic) {
-
-      Services.obs.removeObserver(this, aObserveTopic);
-      try {
-        aObserveFunc(SpecialPowers.wrap(aSubject), aTopic, aData);
-      } catch (ex) {
-        ok(false, ex);
-      }
-    }
-  }
-
-  Services.obs.addObserver(observe, aObserveTopic, false);
-}
-
-// set up the ID service with an identity with keypair and all
-// when ready, invoke callback with the identity
-function setup_test_identity(identity, cert, cb) {
-  // set up the store so that we're supposed to be logged in
-  let store = get_idstore();
-
-  function keyGenerated(err, kpo) {
-    store.addIdentity(identity, kpo, cert);
-    cb();
-  };
-
-  jwcrypto.generateKeyPair("DS160", keyGenerated);
-}
-
-// takes a list of functions and returns a function that
-// when called the first time, calls the first func,
-// then the next time the second, etc.
-function call_sequentially() {
-  let numCalls = 0;
-  let funcs = arguments;
-
-  return function() {
-    if (!funcs[numCalls]) {
-      let argString = Array.prototype.slice.call(arguments).join(",");
-      ok(false, "Too many calls: " + argString);
-      return;
-    }
-    funcs[numCalls].apply(funcs[numCalls], arguments);
-    numCalls += 1;
-  };
-}
-
-/*
- * Setup a provisioning workflow with appropriate callbacks
- *
- * identity is the email we're provisioning.
- *
- * afterSetupCallback is required.
- *
- * doneProvisioningCallback is optional, if the caller
- * wants to be notified when the whole provisioning workflow is done
- *
- * frameCallbacks is optional, contains the callbacks that the sandbox
- * frame would provide in response to DOM calls.
- */
-function setup_provisioning(identity, afterSetupCallback, doneProvisioningCallback, callerCallbacks) {
-  IDService.reset();
-
-  let util = SpecialPowers.getDOMWindowUtils(window);
-
-  let provId = util.outerWindowID;
-  IDService.IDP._provisionFlows[provId] = {
-    identity : identity,
-    idpParams: TEST_IDPPARAMS,
-    callback: function(err) {
-      if (doneProvisioningCallback)
-        doneProvisioningCallback(err);
-    },
-    sandbox: {
-      // Emulate the free() method on the iframe sandbox
-      free: function() {}
-    }
-  };
-
-  let caller = {};
-  caller.id = callerCallbacks.id = provId;
-  caller.doBeginProvisioningCallback = function(id, duration_s) {
-    if (callerCallbacks && callerCallbacks.beginProvisioningCallback)
-      callerCallbacks.beginProvisioningCallback(id, duration_s);
-  };
-  caller.doGenKeyPairCallback = function(pk) {
-    if (callerCallbacks && callerCallbacks.genKeyPairCallback)
-      callerCallbacks.genKeyPairCallback(pk);
-  };
-
-  afterSetupCallback(callerCallbacks);
-}
-
-function resetState() {
-  IDService.reset();
-}
-
-function cleanup() {
-  resetState();
-  SpecialPowers.clearUserPref("toolkit.identity.debug");
-  SpecialPowers.clearUserPref("dom.identity.enabled");
-  // Re-init the UX that we uninit
-  try {
-    const SignInToWebsiteUX = Cu.import("resource:///modules/SignInToWebsite.jsm").SignInToWebsiteUX;
-    if (SignInToWebsiteUX) {
-      SignInToWebsiteUX.init();
-    }
-  } catch (ex) {
-    // The module doesn't exist
-  }
-}
-
-var TESTS = [];
-
-function run_next_test() {
-  if (!identity) {
-    todo(false, "DOM API is not available. Skipping tests.");
-    cleanup();
-    SimpleTest.finish();
-    return;
-  }
-  if (TESTS.length) {
-    let test = TESTS.shift();
-    info(test.name);
-    try {
-      test();
-    } catch (ex) {
-      ok(false, ex);
-    }
-  } else {
-    cleanup();
-    info("all done");
-    SimpleTest.finish();
-  }
-}
deleted file mode 100644
--- a/toolkit/identity/tests/mochitest/test_authentication.html
+++ /dev/null
@@ -1,161 +0,0 @@
-<!DOCTYPE html>
-<html>
-<!--
-Test of Identity Provider (IDP) Authentication using the DOM APIs
--->
-<head>
-  <meta charset="utf-8">
-  <title>Test of Identity Provider (IDP) Authentication using the DOM APIs</title>
-  <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
-  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
-  <script type="application/javascript;version=1.8" src="head_identity.js"></script>
-</head>
-<body onload="run_next_test()">
-<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=753238">Test of Identity Provider (IDP) Authentication using the DOM APIs</a>
-<p id="display"></p>
-<div id="content" style="display: none">
-
-</div>
-<pre id="test">
-<script type="application/javascript;version=1.8">
-
-/** Test of Identity Provider (IDP) Authentication using the DOM APIs **/
-/** Most tests are ported from test_authentication.js */
-
-"use strict";
-
-SimpleTest.waitForExplicitFinish();
-
-const DOMIdentity = Cu.import("resource://gre/modules/DOMIdentity.jsm")
-                      .DOMIdentity;
-let outerWinId = SpecialPowers.getDOMWindowUtils(window).outerWindowID;
-
-function run_next_auth_test() {
-  // Reset the DOM state then run the next test
-  let provContext = IdentityProvider._provisionFlows[outerWinId];
-  if (provContext && provContext.caller) {
-    makeObserver("identity-DOM-state-reset", function() {
-      SimpleTest.executeSoon(run_next_test);
-    });
-    DOMIdentity._resetFrameState(provContext.caller);
-  } else {
-    SimpleTest.executeSoon(run_next_test);
-  }
-}
-
-function test_begin_authentication_flow() {
-  let _provId = null;
-
-  // set up a watch, to be consistent
-  let mockedDoc = mock_watch(null, function(action, params) {});
-  identity.watch(mockedDoc);
-
-  // The identity-auth notification is sent up to the UX from the
-  // _doAuthentication function.  Be ready to receive it and call
-  // beginAuthentication
-  makeObserver("identity-auth", function (aSubject, aTopic, aData) {
-    isnot(aSubject, null);
-
-    is(aSubject.wrappedJSObject.provId, _provId);
-
-    run_next_auth_test();
-  });
-  setup_provisioning(
-    TEST_USER,
-    function(caller) {
-      _provId = caller.id;
-      identity.beginProvisioning(caller.beginProvisioningCallback);
-    }, function() {},
-    {
-      beginProvisioningCallback: function(email, duration_s) {
-        // let's say this user needs to authenticate
-        IDService.IDP._doAuthentication(_provId, TEST_IDPPARAMS);
-      }
-    });
-}
-
-function test_complete_authentication_flow() {
-  let _provId = null;
-  let _authId = null;
-  let id = TEST_USER;
-
-  let callbacksFired = false;
-  let topicObserved = false;
-
-  // The result of authentication should be a successful login
-  IDService.reset();
-
-  let mockedDoc = mock_watch(null, call_sequentially(
-    function(action, params) {
-      is(action, 'ready');
-      is(params, undefined);
-    }
-  ));
-
-  identity.watch(mockedDoc);
-
-  // A mock calling context
-  let authCaller = {
-    doBeginAuthenticationCallback: function doBeginAuthCallback(aIdentity) {
-      is(aIdentity, TEST_USER);
-      identity.completeAuthentication();
-    },
-
-    doError: function(err) {
-      ok(false, "doError called: " + err);
-    },
-  };
-
-  makeObserver("identity-auth-complete", function (aSubject, aTopic, aData) {
-    info("identity-auth-complete");
-    is(aSubject.wrappedJSObject.identity, TEST_USER);
-
-    run_next_test();
-  });
-
-  // Create a provisioning flow for our auth flow to attach to
-  setup_provisioning(
-    TEST_USER,
-    function(provFlow) {
-      _provId = provFlow.id;
-      info("after setup: " + _provId);
-      identity.beginProvisioning(provFlow.beginProvisioningCallback);
-    },
-    function() {
-      info("doneProvisioningCallback");
-
-      // let's say this user needs to authenticate
-      IDService.IDP._doAuthentication(_provId, TEST_IDPPARAMS);
-
-      // test_begin_authentication_flow verifies that the right
-      // message is sent to the UI.  So that works.  Moving on,
-      // the UI calls setAuthenticationFlow ...
-      _authId = outerWinId;
-      IDService.IDP.setAuthenticationFlow(_authId, _provId);
-      IDService.IDP._provisionFlows[_provId].rpId = outerWinId;
-
-      // ... then the UI calls beginAuthentication ...
-      authCaller.id = _authId;
-      IDService.IDP._provisionFlows[_provId].caller = authCaller;
-      identity.beginAuthentication(function bac() {
-        info("beginAuthentication callback");
-        identity.completeAuthentication();
-      });
-    },
-    {
-      beginProvisioningCallback: function(email, duration_s) {
-        info("beginProvisioningCallback");
-
-        identity.raiseProvisioningFailure("Prov. failed");
-      }
-    }
-  );
-}
-
-TESTS.push(test_begin_authentication_flow);
-TESTS.push(test_complete_authentication_flow);
-
-</script>
-</pre>
-</body>
-</html>
deleted file mode 100644
--- a/toolkit/identity/tests/mochitest/test_provisioning.html
+++ /dev/null
@@ -1,285 +0,0 @@
-<!DOCTYPE html>
-<html>
-<!--
-Test of Identity Provider (IDP) Provisioning using the DOM APIs
--->
-<head>
-  <meta charset="utf-8">
-  <title>Test of Identity Provider (IDP) Provisioning using the DOM APIs</title>
-  <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
-  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
-  <script type="application/javascript;version=1.8" src="head_identity.js"></script>
-</head>
-<body onload="run_next_test()">
-<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=753238">Test of Identity Provider (IDP) Provisioning using the DOM APIs</a>
-<p id="display"></p>
-<div id="content" style="display: none">
-
-</div>
-<pre id="test">
-<script type="application/javascript;version=1.8">
-
-/** Test of Identity Provider (IDP) Provisioning using the DOM APIs **/
-/** Most tests are ported from test_provisioning.js */
-
-"use strict";
-
-SimpleTest.waitForExplicitFinish();
-
-const DOMIdentity = Cu.import("resource://gre/modules/DOMIdentity.jsm")
-                      .DOMIdentity;
-let outerWinId = SpecialPowers.getDOMWindowUtils(window).outerWindowID;
-
-function check_provision_flow_done(provId) {
-  do_check_null(IdentityProvider._provisionFlows[provId]);
-}
-
-/**
- * Allow specifying aProvFlow so we can reset after the _provisionFlows is cleaned up.
- */
-function run_next_prov_test(aProvFlow) {
-  // Reset the DOM state then run the next test
-  let provContext = aProvFlow || IdentityProvider._provisionFlows[outerWinId];
-  if (provContext && provContext.caller) {
-    makeObserver("identity-DOM-state-reset", function() {
-      SimpleTest.executeSoon(run_next_test);
-    });
-    DOMIdentity._resetFrameState(provContext.caller);
-  } else {
-    SimpleTest.executeSoon(run_next_test);
-  }
-}
-
-function test_begin_provisioning() {
-  setup_provisioning(
-    TEST_USER,
-    function(caller) {
-      // call .beginProvisioning()
-      // TODO: should probably throw outside of a prov. sandbox?
-      identity.beginProvisioning(caller.beginProvisioningCallback);
-    }, function() {},
-    {
-      beginProvisioningCallback: function(email, duration_s) {
-        is(email, TEST_USER);
-        ok(duration_s > 0);
-        ok(duration_s <= (24 * 3600));
-
-        run_next_prov_test();
-      }
-    });
-}
-
-function test_raise_provisioning_failure() {
-  let _callerId = null;
-
-  setup_provisioning(
-    TEST_USER,
-    function(caller) {
-      // call .beginProvisioning()
-      _callerId = caller.id;
-      identity.beginProvisioning(caller.beginProvisioningCallback);
-    }, function(err) {
-      // this should be invoked with a populated error
-      isnot(err, null);
-      ok(err.indexOf("can't authenticate this email") > -1);
-
-      run_next_prov_test();
-    },
-    {
-      beginProvisioningCallback: function(email, duration_s) {
-        // raise the failure as if we can't provision this email
-        identity.raiseProvisioningFailure("can't authenticate this email");
-      }
-  });
-}
-
-function test_genkeypair_before_begin_provisioning() {
-  setup_provisioning(
-    TEST_USER,
-    function(caller) {
-      try {
-        // call genKeyPair without beginProvisioning
-        identity.genKeyPair(caller.genKeyPairCallback);
-      } catch (ex) {
-        ok(ex, "Caught exception for calling genKeyPair without beginProvisioning: " + ex);
-        run_next_prov_test();
-      }
-    },
-    // expect this to be called with an error
-    function(err) {
-      ok(false, "Shoudn't reach here as DOM code should have caught the problem");
-
-      run_next_prov_test();
-    },
-    {
-      // this should not be called at all!
-      genKeyPairCallback: function(pk) {
-        // a test that will surely fail because we shouldn't be here.
-        ok(false);
-
-        run_next_prov_test();
-      }
-    }
-  );
-}
-
-function test_genkeypair() {
-  let _callerId = null;
-
-  function gkpCallback(kp) {
-    isnot(kp, null);
-
-    // yay!
-    run_next_prov_test();
-  }
-
-  setup_provisioning(
-    TEST_USER,
-    function(caller) {
-      _callerId = caller.id;
-      identity.beginProvisioning(caller.beginProvisioningCallback);
-    },
-    function(err) {
-      // should not be called!
-      ok(false);
-
-      run_next_prov_test();
-    },
-    {
-      beginProvisioningCallback: function(email, time_s) {
-        identity.genKeyPair(gkpCallback);
-      },
-      genKeyPairCallback: gkpCallback,
-    }
-  );
-}
-
-// we've already ensured that genkeypair can't be called
-// before beginProvisioning, so this test should be enough
-// to ensure full sequential call of the 3 APIs.
-function test_register_certificate_before_genkeypair() {
-  let _callerID = null;
-
-  setup_provisioning(
-    TEST_USER,
-    function(caller) {
-      // do the right thing for beginProvisioning
-      _callerID = caller.id;
-      identity.beginProvisioning(caller.beginProvisioningCallback);
-    },
-    // expect this to be called with an error
-    function(err) {
-      ok(false, "Shoudn't reach here as DOM code should have caught the problem");
-
-      run_next_prov_test();
-    },
-    {
-      beginProvisioningCallback: function(email, duration_s) {
-        try {
-          // now we try to register cert but no keygen has been done
-          identity.registerCertificate("fake-cert");
-        } catch (ex) {
-          ok(ex, "Caught exception for calling genKeyPair without beginProvisioning: " + ex);
-          run_next_prov_test();
-        }
-
-      }
-    }
-  );
-}
-
-function test_register_certificate() {
-  let _callerId = null;
-  let provFlow = null;
-
-  function gkpCallback(pk) {
-    // Hold on to the provFlow so we have access to .caller to cleanup later
-    provFlow = IdentityProvider._provisionFlows[outerWinId];
-
-    identity.registerCertificate("fake-cert-42");
-  }
-
-  setup_provisioning(
-    TEST_USER,
-    function(caller) {
-      _callerId = caller.id;
-      identity.beginProvisioning(caller.beginProvisioningCallback);
-    },
-    function(err) {
-      // we should be cool!
-      do_check_null(err);
-
-      // check that the cert is there
-      let identity = get_idstore().fetchIdentity(TEST_USER);
-      isnot(identity,null);
-      is(identity.cert, "fake-cert-42");
-
-      SimpleTest.executeSoon(function check_done() {
-        // cleanup will happen after the callback is called
-        check_provision_flow_done(_callerId);
-
-        run_next_prov_test(provFlow);
-      });
-    },
-    {
-      beginProvisioningCallback: function(email, duration_s) {
-        identity.genKeyPair(gkpCallback);
-      },
-      genKeyPairCallback: gkpCallback,
-    }
-  );
-}
-
-function test_get_assertion_after_provision() {
-  let _callerId = null;
-  let provFlow = null;
-
-  function gkpCallback(pk) {
-    // Hold on to the provFlow so we have access to .caller to cleanup later
-    provFlow = IdentityProvider._provisionFlows[outerWinId];
-    identity.registerCertificate("fake-cert-42");
-  }
-
-  setup_provisioning(
-    TEST_USER,
-    function(caller) {
-      _callerId = caller.id;
-      identity.beginProvisioning(caller.beginProvisioningCallback);
-    },
-    function(err) {
-      // we should be cool!
-      do_check_null(err);
-
-      // check that the cert is there
-      let identity = get_idstore().fetchIdentity(TEST_USER);
-      isnot(identity,null);
-      is(identity.cert, "fake-cert-42");
-
-      SimpleTest.executeSoon(function check_done() {
-        // cleanup will happen after the callback is called
-        check_provision_flow_done(_callerId);
-
-        run_next_prov_test(provFlow);
-      });
-    },
-    {
-      beginProvisioningCallback: function(email, duration_s) {
-        identity.genKeyPair(gkpCallback);
-      },
-      genKeyPairCallback: gkpCallback,
-    }
-  );
-}
-
-TESTS.push(test_genkeypair_before_begin_provisioning);
-TESTS.push(test_begin_provisioning);
-TESTS.push(test_raise_provisioning_failure);
-TESTS.push(test_register_certificate_before_genkeypair);
-TESTS.push(test_genkeypair);
-TESTS.push(test_register_certificate);
-TESTS.push(test_get_assertion_after_provision);
-
-</script>
-</pre>
-</body>
-</html>
deleted file mode 100644
--- a/toolkit/identity/tests/mochitest/test_relying_party.html
+++ /dev/null
@@ -1,229 +0,0 @@
-<!DOCTYPE html>
-<html>
-<!--
-Test of Relying Party (RP) using the DOM APIs
--->
-<head>
-  <meta charset="utf-8">
-  <title>Test of Relying Party (RP) using the DOM APIs</title>
-  <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
-  <script type="application/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
-  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
-  <script type="application/javascript;version=1.8" src="head_identity.js"></script>
-</head>
-<body onload="run_next_test()">
-<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=753238">Test of Relying Party (RP) using the DOM APIs</a>
-<p id="display"></p>
-<div id="content">
-  <button id='request'>request</button>
-</div>
-<pre id="test">
-<script type="application/javascript;version=1.8">
-
-/** Test of Relying Party (RP) using the DOM APIs **/
-/** Most tests are ported from test_relying_party.js */
-
-"use strict";
-
-SimpleTest.waitForExplicitFinish();
-
-const DOMIdentity = Cu.import("resource://gre/modules/DOMIdentity.jsm")
-                      .DOMIdentity;
-let outerWinId = SpecialPowers.getDOMWindowUtils(window).outerWindowID;
-
-// Reset the DOM state then run the next test
-function run_next_rp_test() {
-  let rpContext = RelyingParty._rpFlows[outerWinId];
-  if (rpContext) {
-    makeObserver("identity-DOM-state-reset", function() {
-      SimpleTest.executeSoon(run_next_test);
-    });
-    DOMIdentity._resetFrameState(rpContext);
-  } else {
-    SimpleTest.executeSoon(run_next_test);
-  }
-}
-
-function test_watch_loggedin_ready() {
-  resetState();
-
-  let id = TEST_USER;
-  setup_test_identity(id, TEST_CERT, function() {
-    let store = get_idstore();
-
-    // set it up so we're supposed to be logged in to TEST_URL
-    store.setLoginState(TEST_URL, true, id);
-    identity.watch(mock_watch(id, function(action, params) {
-      is(action, 'ready');
-      is(params, undefined);
-
-      run_next_rp_test();
-    }));
-  });
-}
-
-function test_watch_loggedin_login() {
-  resetState();
-
-  let id = TEST_USER;
-  setup_test_identity(id, TEST_CERT, function() {
-    let store = get_idstore();
-
-    // set it up so we're supposed to be logged in to TEST_URL
-    store.setLoginState(TEST_URL, true, id);
-
-    // check for first a login() call, then a ready() call
-    identity.watch(mock_watch(null, call_sequentially(
-      function(action, params) {
-        is(action, 'login');
-        isnot(params, null);
-      },
-      function(action, params) {
-        is(action, 'ready');
-        do_check_null(params);
-
-        run_next_rp_test();
-      }
-    )));
-  });
-}
-
-function test_watch_loggedin_logout() {
-  resetState();
-
-  let id = TEST_USER;
-  let other_id = "otherid@foo.com";
-  setup_test_identity(other_id, TEST_CERT, function() {
-    setup_test_identity(id, TEST_CERT, function() {
-      let store = get_idstore();
-
-      // set it up so we're supposed to be logged in to TEST_URL
-      // with id, not other_id
-      store.setLoginState(TEST_URL, true, id);
-
-      // this should cause a login with an assertion for id,
-      // not for other_id
-      identity.watch(mock_watch(other_id, call_sequentially(
-        function(action, params) {
-          is(action, 'login');
-          isnot(params, null);
-        },
-        function(action, params) {
-          is(action, 'ready');
-          do_check_null(params);
-
-          run_next_rp_test();
-        }
-      )));
-    });
-  });
-}
-
-function test_watch_notloggedin_ready() {
-  resetState();
-
-  identity.watch(mock_watch(null, function(action, params) {
-    is(action, 'ready');
-    is(params, undefined);
-
-    run_next_rp_test();
-  }));
-}
-
-function test_watch_notloggedin_logout() {
-  resetState();
-
-  identity.watch(mock_watch(TEST_USER, call_sequentially(
-    function(action, params) {
-      is(action, 'logout');
-      is(params, undefined);
-
-      let store = get_idstore();
-      do_check_null(store.getLoginState(TEST_URL));
-    },
-    function(action, params) {
-      is(action, 'ready');
-      is(params, undefined);
-      run_next_rp_test();
-    }
-  )));
-}
-
-function test_request() {
-  // set up a watch, to be consistent
-  let mockedDoc = mock_watch(null, function(action, params) {
-    // We're not checking anything here at the moment.
-  });
-
-  identity.watch(mockedDoc);
-
-  // be ready for the UX identity-request notification
-  makeObserver("identity-request", function (aSubject, aTopic, aData) {
-    isnot(aSubject, null);
-
-    run_next_rp_test();
-  });
-
-  var button = document.getElementById('request');
-  button.addEventListener('click', function requestHandler() {
-    button.removeEventListener('click', requestHandler);
-    identity.request();
-  });
-
-  synthesizeMouseAtCenter(button, {});
-}
-
-function test_logout() {
-  resetState();
-
-  let id = TEST_USER;
-  setup_test_identity(id, TEST_CERT, function() {
-    let store = get_idstore();
-
-    // set it up so we're supposed to be logged in to TEST_URL
-    store.setLoginState(TEST_URL, true, id);
-
-    let doLogout;
-    let mockedDoc = mock_watch(id, call_sequentially(
-      function(action, params) {
-        is(action, 'ready');
-        is(params, undefined);
-
-        SimpleTest.executeSoon(doLogout);
-      },
-      function(action, params) {
-        is(action, 'logout');
-        is(params, undefined);
-      },
-      function(action, params) {
-        is(action, 'ready');
-        is(params, undefined);
-
-        run_next_rp_test();
-      }));
-
-    doLogout = function() {
-      makeObserver("identity-login-state-changed", function (aSubject, aTopic, aData) {
-        isnot(aSubject.wrappedJSObject.rpId, null, "Check rpId is not null");
-        is(aData, null, "Check identity changed to nobody");
-
-        ok(!store.getLoginState(TEST_URL).isLoggedIn, "Check isLoggedIn is false");
-        is(store.getLoginState(TEST_URL).email, TEST_USER, "Check notification email");
-      });
-      identity.logout();
-    };
-
-    identity.watch(mockedDoc);
-  });
-}
-
-
-TESTS = TESTS.concat([test_watch_loggedin_ready, test_watch_loggedin_login, test_watch_loggedin_logout]);
-TESTS = TESTS.concat([test_watch_notloggedin_ready, test_watch_notloggedin_logout]);
-TESTS.push(test_request);
-TESTS.push(test_logout);
-
-</script>
-</pre>
-</body>
-</html>
--- a/toolkit/identity/tests/unit/test_identity.js
+++ b/toolkit/identity/tests/unit/test_identity.js
@@ -41,18 +41,18 @@ function test_select_identity() {
   IDService.reset();
 
   let id = "ishtar@mockmyid.com";
   setup_test_identity(id, TEST_CERT, function() {
     let gotAssertion = false;
     let mockedDoc = mock_doc(null, TEST_URL, call_sequentially(
       function(action, params) {
         // ready emitted from first watch() call
-	do_check_eq(action, 'ready');
-	do_check_null(params);
+        do_check_eq(action, 'ready');
+        do_check_null(params);
       },
       // first the login call
       function(action, params) {
         do_check_eq(action, 'login');
         do_check_neq(params, null);
 
         // XXX - check that the assertion is for the right email
 
new file mode 100644
--- /dev/null
+++ b/toolkit/identity/tests/unit/test_minimalidentity.js
@@ -0,0 +1,95 @@
+"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) {
+    do_check_eq(action, 'coffee');
+    do_test_finished();
+    run_next_test();
+  });
+
+  mockedDoc.doCoffee();
+}
+
+/*
+ * Test that the "identity-controller-watch" signal is emitted correctly
+ */
+function test_watch() {
+  do_test_pending();
+
+  let mockedDoc = mock_doc(null, TEST_URL);
+  makeObserver("identity-controller-watch", function (aSubject, aTopic, aData) {
+    do_check_eq(aSubject.wrappedJSObject.rpId, mockedDoc.id);
+    do_check_eq(aSubject.wrappedJSObject.origin, TEST_URL);
+    do_test_finished();
+    run_next_test();
+   });
+
+  MinimalIDService.RP.watch(mockedDoc);
+}
+
+/*
+ * Test that the "identity-controller-request" signal is emitted correctly
+ */
+function test_request() {
+  do_test_pending();
+
+  let mockedDoc = mock_doc(null, TEST_URL);
+  makeObserver("identity-controller-request", function (aSubject, aTopic, aData) {
+    do_check_eq(aSubject.wrappedJSObject.rpId, mockedDoc.id);
+    do_check_eq(aSubject.wrappedJSObject.origin, TEST_URL);
+    do_test_finished();
+    run_next_test();
+  });
+
+  MinimalIDService.RP.watch(mockedDoc);
+  MinimalIDService.RP.request(mockedDoc.id, {});
+}
+
+/*
+ * Test that the "identity-controller-logout" signal is emitted correctly
+ */
+function test_logout() {
+  do_test_pending();
+
+  let mockedDoc = mock_doc(null, TEST_URL);
+  makeObserver("identity-controller-logout", function (aSubject, aTopic, aData) {
+    do_check_eq(aSubject.wrappedJSObject.rpId, mockedDoc.id);
+    do_test_finished();
+    run_next_test();
+  });
+
+  MinimalIDService.RP.watch(mockedDoc);
+  MinimalIDService.RP.logout(mockedDoc.id, {});
+}
+
+
+
+let TESTS = [
+  test_overall,
+  test_mock_doc,
+  test_watch,
+  test_request,
+  test_logout
+];
+
+TESTS.forEach(add_test);
+
+function run_test() {
+  run_next_test();
+}
--- a/toolkit/identity/tests/unit/xpcshell.ini
+++ b/toolkit/identity/tests/unit/xpcshell.ini
@@ -1,16 +1,17 @@
 [DEFAULT]
 head = head_identity.js
 tail = tail_identity.js
 
 # Test load modules first so syntax failures are caught early.
 [test_load_modules.js]
+[test_minimalidentity.js]
+
 [test_log_utils.js]
-
 [test_authentication.js]
 [test_crypto_service.js]
 [test_identity.js]
 [test_jwcrypto.js]
 [test_observer_topics.js]
 [test_provisioning.js]
 [test_relying_party.js]
 [test_store.js]