Bug 1091161 - MozLoopService: Separate gInitializeTimerFunc from the actual initialize callback so we can retry initialization on demand. r=pkerr a=loop-only
authorMatthew Noorenberghe <mozilla@noorenberghe.ca>
Wed, 29 Oct 2014 16:20:31 -0700
changeset 235104 989937667860a51dc7ff3d2f013905951a71ace0
parent 235103 f634a1289875070bc400531f45b1861a392beace
child 235105 5dab056377b5deb82b9d74310d4386f39d5ac03e
push id611
push userraliiev@mozilla.com
push dateMon, 05 Jan 2015 23:23:16 +0000
treeherdermozilla-release@345cd3b9c445 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerspkerr, loop-only
bugs1091161
milestone35.0a2
Bug 1091161 - MozLoopService: Separate gInitializeTimerFunc from the actual initialize callback so we can retry initialization on demand. r=pkerr a=loop-only
browser/components/loop/LoopRooms.jsm
browser/components/loop/MozLoopAPI.jsm
browser/components/loop/MozLoopService.jsm
browser/components/loop/test/mochitest/browser_fxa_login.js
browser/components/loop/test/xpcshell/head.js
browser/components/loop/test/xpcshell/test_looprooms.js
browser/components/loop/test/xpcshell/test_loopservice_busy.js
browser/components/loop/test/xpcshell/test_loopservice_dnd.js
browser/components/loop/test/xpcshell/test_loopservice_initialize.js
browser/components/loop/test/xpcshell/test_loopservice_notification.js
browser/components/loop/test/xpcshell/test_loopservice_registration.js
browser/components/loop/test/xpcshell/test_loopservice_restart.js
browser/components/loop/test/xpcshell/test_loopservice_token_invalid.js
browser/components/loop/test/xpcshell/test_loopservice_token_save.js
browser/components/loop/test/xpcshell/test_loopservice_token_send.js
browser/components/loop/test/xpcshell/test_loopservice_token_validation.js
--- a/browser/components/loop/LoopRooms.jsm
+++ b/browser/components/loop/LoopRooms.jsm
@@ -63,17 +63,17 @@ let LoopRoomsInternal = {
    */
   getAll: function(version = null, callback) {
     if (!callback) {
       callback = version;
       version = null;
     }
 
     Task.spawn(function* () {
-      yield MozLoopService.register();
+      yield MozLoopService.promiseRegisteredWithServers();
 
       if (!gDirty) {
         callback(null, [...this.rooms.values()]);
         return;
       }
 
       // Fetch the rooms from the server.
       let sessionType = MozLoopService.userProfile ? LOOP_SESSION_TYPE.FXA :
--- a/browser/components/loop/MozLoopAPI.jsm
+++ b/browser/components/loop/MozLoopAPI.jsm
@@ -364,17 +364,17 @@ function injectLoopAPI(targetWindow) {
      *                            happened.
      */
     ensureRegistered: {
       enumerable: true,
       writable: true,
       value: function(callback) {
         // We translate from a promise to a callback, as we can't pass promises from
         // Promise.jsm across the priv versus unpriv boundary.
-        MozLoopService.register().then(() => {
+        MozLoopService.promiseRegisteredWithServers().then(() => {
           callback(null);
         }, err => {
           callback(cloneValueInto(err, targetWindow));
         }).catch(Cu.reportError);
       }
     },
 
     /**
--- a/browser/components/loop/MozLoopService.jsm
+++ b/browser/components/loop/MozLoopService.jsm
@@ -25,16 +25,17 @@ const PREF_LOG_LEVEL = "loop.debug.logle
 
 const EMAIL_OR_PHONE_RE = /^(:?\S+@\S+|\+\d+)$/;
 
 Cu.import("resource://gre/modules/Services.jsm");
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 Cu.import("resource://gre/modules/Promise.jsm");
 Cu.import("resource://gre/modules/osfile.jsm", this);
 Cu.import("resource://gre/modules/Task.jsm");
+Cu.import("resource://gre/modules/Timer.jsm");
 Cu.import("resource://gre/modules/FxAccountsOAuthClient.jsm");
 
 Cu.importGlobalProperties(["URL"]);
 
 this.EXPORTED_SYMBOLS = ["MozLoopService", "LOOP_SESSION_TYPE"];
 
 XPCOMUtils.defineLazyModuleGetter(this, "injectLoopAPI",
   "resource:///modules/loop/MozLoopAPI.jsm");
@@ -106,17 +107,16 @@ function getJSONPref(aName) {
 }
 
 // The current deferred for the registration process. This is set if in progress
 // or the registration was successful. This is null if a registration attempt was
 // unsuccessful.
 let gRegisteredDeferred = null;
 let gHawkClient = null;
 let gLocalizedStrings = null;
-let gInitializeTimer = null;
 let gFxAEnabled = true;
 let gFxAOAuthClientPromise = null;
 let gFxAOAuthClient = null;
 let gErrors = new Map();
 
 /**
  * Internal helper methods and state
  *
@@ -304,17 +304,17 @@ let MozLoopServiceInternal = {
   },
 
   get errors() {
     return gErrors;
   },
 
   /**
    * Starts registration of Loop with the push server, and then will register
-   * with the Loop server. It will return early if already registered.
+   * with the Loop server as a GUEST. It will return early if already registered.
    *
    * @returns {Promise} a promise that is resolved with no params on completion, or
    *          rejected with an error code or string.
    */
   promiseRegisteredWithServers: function() {
     if (gRegisteredDeferred) {
       return gRegisteredDeferred.promise;
     }
@@ -866,52 +866,27 @@ let MozLoopServiceInternal = {
       deferred.resolve(result);
     } else {
       deferred.reject("Invalid token data");
     }
   },
 };
 Object.freeze(MozLoopServiceInternal);
 
+
 let gInitializeTimerFunc = (deferredInitialization) => {
   // Kick off the push notification service into registering after a timeout.
   // This ensures we're not doing too much straight after the browser's finished
   // starting up.
-  gInitializeTimer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
-  gInitializeTimer.initWithCallback(Task.async(function* initializationCallback() {
-    yield MozLoopService.register().then(Task.async(function*() {
-      if (!MozLoopServiceInternal.fxAOAuthTokenData) {
-        log.debug("MozLoopService: Initialized without an already logged-in account");
-        deferredInitialization.resolve("initialized to guest status");
-        return;
-      }
 
-      log.debug("MozLoopService: Initializing with already logged-in account");
-      let registeredPromise =
-            MozLoopServiceInternal.registerWithLoopServer(
-              LOOP_SESSION_TYPE.FXA, {
-                calls: MozLoopServiceInternal.pushHandler.registeredChannels[MozLoopService.channelIDs.callsFxA],
-                rooms: MozLoopServiceInternal.pushHandler.registeredChannels[MozLoopService.channelIDs.roomsFxA]
-              });
-      registeredPromise.then(() => {
-        deferredInitialization.resolve("initialized to logged-in status");
-      }, error => {
-        log.debug("MozLoopService: error logging in using cached auth token");
-        MozLoopServiceInternal.setError("login", error);
-        deferredInitialization.reject("error logging in using cached auth token");
-      });
-    }), error => {
-      log.debug("MozLoopService: Failure of initial registration", error);
-      deferredInitialization.reject(error);
-    });
-    gInitializeTimer = null;
-  }),
-  MozLoopServiceInternal.initialRegistrationDelayMilliseconds, Ci.nsITimer.TYPE_ONE_SHOT);
+  setTimeout(MozLoopService.delayedInitialize.bind(MozLoopService, deferredInitialization),
+             MozLoopServiceInternal.initialRegistrationDelayMilliseconds);
 };
 
+
 /**
  * Public API
  */
 this.MozLoopService = {
   _DNSService: gDNSService,
 
   get channelIDs() {
     // Channel ids that will be registered with the PushServer for notifications
@@ -960,23 +935,70 @@ this.MozLoopService = {
     if (!MozLoopServiceInternal.urlExpiryTimeIsInFuture() &&
         !MozLoopServiceInternal.fxAOAuthTokenData) {
       return Promise.resolve("registration not needed");
     }
 
     let deferredInitialization = Promise.defer();
     gInitializeTimerFunc(deferredInitialization);
 
-    return deferredInitialization.promise.catch(error => {
+    return deferredInitialization.promise;
+  }),
+
+  /**
+   * The core of the initialization work that happens once the browser is ready
+   * (after a timer when called during startup).
+   *
+   * Can be called more than once (e.g. if the initial setup fails at some phase).
+   * @param {Deferred} deferredInitialization
+   */
+  delayedInitialize: Task.async(function*(deferredInitialization) {
+    // Set or clear an error depending on how deferredInitialization gets resolved.
+    // We do this first so that it can handle the early returns below.
+    let completedPromise = deferredInitialization.promise.then(result => {
+      MozLoopServiceInternal.clearError("initialization");
+      return result;
+    },
+    error => {
+      // If we get a non-object then setError was already called for a different error type.
       if (typeof(error) == "object") {
-        // This never gets cleared since there is no UI to recover. Only restarting will work.
         MozLoopServiceInternal.setError("initialization", error);
       }
-      throw error;
     });
+
+    try {
+      yield this.promiseRegisteredWithServers();
+    } catch (ex) {
+      log.debug("MozLoopService: Failure of initial registration", ex);
+      deferredInitialization.reject(ex);
+      yield completedPromise;
+      return;
+    }
+
+    if (!MozLoopServiceInternal.fxAOAuthTokenData) {
+      log.debug("MozLoopService: Initialized without an already logged-in account");
+      deferredInitialization.resolve("initialized to guest status");
+      yield completedPromise;
+      return;
+    }
+
+    log.debug("MozLoopService: Initializing with already logged-in account");
+    let pushURLs = {
+      calls: MozLoopServiceInternal.pushHandler.registeredChannels[this.channelIDs.callsFxA],
+      rooms: MozLoopServiceInternal.pushHandler.registeredChannels[this.channelIDs.roomsFxA]
+    };
+
+    MozLoopServiceInternal.registerWithLoopServer(LOOP_SESSION_TYPE.FXA, pushURLs).then(() => {
+      deferredInitialization.resolve("initialized to logged-in status");
+    }, error => {
+      log.debug("MozLoopService: error logging in using cached auth token");
+      MozLoopServiceInternal.setError("login", error);
+      deferredInitialization.reject("error logging in using cached auth token");
+    });
+    yield completedPromise;
   }),
 
   /**
    * Opens the chat window
    *
    * @param {Object} contentWindow The window to open the chat window in, may
    *                               be null.
    * @param {String} title The title of the chat window.
@@ -1075,35 +1097,20 @@ this.MozLoopService = {
     // be "127" (reserved for localhost).
     let host = Services.prefs.getCharPref("loop.soft_start_hostname");
     let task = this._DNSService.asyncResolve(host,
                                              this._DNSService.RESOLVE_DISABLE_IPV6,
                                              onLookupComplete,
                                              Services.tm.mainThread);
   },
 
-
   /**
-   * Starts registration of Loop with the push server, and then will register
-   * with the Loop server. It will return early if already registered.
-   *
-   * @returns {Promise} a promise that is resolved with no params on completion, or
-   *          rejected with an error code or string.
+   * @see MozLoopServiceInternal.promiseRegisteredWithServers
    */
-  register: function() {
-    log.debug("registering");
-    // Don't do anything if loop is not enabled.
-    if (!Services.prefs.getBoolPref("loop.enabled")) {
-      throw new Error("Loop is not enabled");
-    }
-
-    if (Services.prefs.getBoolPref("loop.throttled2")) {
-      throw new Error("Loop is disabled by the soft-start mechanism");
-    }
-
+  promiseRegisteredWithServers: function() {
     return MozLoopServiceInternal.promiseRegisteredWithServers();
   },
 
   /**
    * Used to note a call url expiry time. If the time is later than the current
    * latest expiry time, then the stored expiry time is increased. For times
    * sooner, this function is a no-op; this ensures we always have the latest
    * expiry time for a url.
--- a/browser/components/loop/test/mochitest/browser_fxa_login.js
+++ b/browser/components/loop/test/mochitest/browser_fxa_login.js
@@ -248,17 +248,17 @@ add_task(function* basicAuthorizationAnd
     state: "state",
   };
   yield promiseOAuthParamsSetup(BASE_URL, params);
 
   info("registering");
   mockPushHandler.registrationPushURL = "https://localhost/pushUrl/guest";
   // Notification observed due to the error being cleared upon successful registration.
   let statusChangedPromise = promiseObserverNotified("loop-status-changed");
-  yield MozLoopService.register();
+  yield MozLoopService.promiseRegisteredWithServers();
   yield statusChangedPromise;
 
   // Normally the same pushUrl would be registered but we change it in the test
   // to be able to check for success on the second registration.
   mockPushHandler.registeredChannels[MozLoopService.channelIDs.callsFxA] = "https://localhost/pushUrl/fxa-calls"; 
   mockPushHandler.registeredChannels[MozLoopService.channelIDs.roomsFxA] = "https://localhost/pushUrl/fxa-rooms"; 
 
   statusChangedPromise = promiseObserverNotified("loop-status-changed");
@@ -313,17 +313,17 @@ add_task(function* loginWithParams401() 
     client_id: "client_id",
     content_uri: BASE_URL + "/content",
     oauth_uri: BASE_URL + "/oauth",
     profile_uri: BASE_URL + "/profile",
     state: "state",
     test_error: "params_401",
   };
   yield promiseOAuthParamsSetup(BASE_URL, params);
-  yield MozLoopService.register();
+  yield MozLoopService.promiseRegisteredWithServers();
 
   let loginPromise = MozLoopService.logInToFxA();
   yield loginPromise.then(tokenData => {
     ok(false, "Promise should have rejected");
   },
   error => {
     ise(error.code, 401, "Check error code");
     checkFxAOAuthTokenData(null);
--- a/browser/components/loop/test/xpcshell/head.js
+++ b/browser/components/loop/test/xpcshell/head.js
@@ -23,16 +23,21 @@ const kUAID = "f47ac11b-58ca-4372-9567-0
 
 // Fake loop server
 var loopServer;
 
 // Ensure loop is always enabled for tests
 Services.prefs.setBoolPref("loop.enabled", true);
 Services.prefs.setBoolPref("loop.throttled", false);
 
+// Cleanup function for all tests
+do_register_cleanup(() => {
+  MozLoopService.errors.clear();
+});
+
 function setupFakeLoopServer() {
   loopServer = new HttpServer();
   loopServer.start(-1);
 
   Services.prefs.setCharPref("services.push.serverURL", kServerPushUrl);
 
   Services.prefs.setCharPref("loop.server",
     "http://localhost:" + loopServer.identity.primaryPort);
--- a/browser/components/loop/test/xpcshell/test_looprooms.js
+++ b/browser/components/loop/test/xpcshell/test_looprooms.js
@@ -130,27 +130,27 @@ const normalizeRoom = function(room) {
   return room;
 };
 
 const compareRooms = function(room1, room2) {
   Assert.deepEqual(normalizeRoom(room1), normalizeRoom(room2));
 };
 
 add_task(function* test_getAllRooms() {
-  yield MozLoopService.register(mockPushHandler);
+  yield MozLoopService.promiseRegisteredWithServers();
 
   let rooms = yield LoopRooms.promise("getAll");
   Assert.equal(rooms.length, 3);
   for (let room of rooms) {
     compareRooms(kRooms.get(room.roomToken), room);
   }
 });
 
 add_task(function* test_getRoom() {
-  yield MozLoopService.register(mockPushHandler);
+  yield MozLoopService.promiseRegisteredWithServers();
 
   let roomToken = "_nxD4V4FflQ";
   let room = yield LoopRooms.promise("get", roomToken);
   Assert.deepEqual(room, kRooms.get(roomToken));
 });
 
 add_task(function* test_errorStates() {
   yield Assert.rejects(LoopRooms.promise("get", "error401"), /Not Found/, "Fetching a non-existent room should fail");
--- a/browser/components/loop/test/xpcshell/test_loopservice_busy.js
+++ b/browser/components/loop/test/xpcshell/test_loopservice_busy.js
@@ -19,17 +19,17 @@ let msgHandler = function(msg) {
       msg.reason === "busy") {
     actionReceived = true;
   }
 };
 
 add_test(function test_busy_2guest_calls() {
   actionReceived = false;
 
-  MozLoopService.register().then(() => {
+  MozLoopService.promiseRegisteredWithServers().then(() => {
     let opened = 0;
     Chat.open = function() {
       opened++;
     };
 
     mockPushHandler.notify(1, MozLoopService.channelIDs.callsGuest);
 
     waitForCondition(() => {return actionReceived && opened > 0}).then(() => {
@@ -42,17 +42,17 @@ add_test(function test_busy_2guest_calls
     });
 
   });
 });
 
 add_test(function test_busy_1fxa_1guest_calls() {
   actionReceived = false;
 
-  MozLoopService.register().then(() => {
+  MozLoopService.promiseRegisteredWithServers().then(() => {
     let opened = 0;
     Chat.open = function() {
       opened++;
     };
 
     mockPushHandler.notify(1, MozLoopService.channelIDs.callsFxA);
     mockPushHandler.notify(1, MozLoopService.channelIDs.callsGuest);
 
@@ -66,17 +66,17 @@ add_test(function test_busy_1fxa_1guest_
     });
 
   });
 });
 
 add_test(function test_busy_2fxa_calls() {
   actionReceived = false;
 
-  MozLoopService.register().then(() => {
+  MozLoopService.promiseRegisteredWithServers().then(() => {
     let opened = 0;
     Chat.open = function() {
       opened++;
     };
 
     mockPushHandler.notify(1, MozLoopService.channelIDs.callsFxA);
 
     waitForCondition(() => {return actionReceived && opened > 0}).then(() => {
@@ -89,17 +89,17 @@ add_test(function test_busy_2fxa_calls()
     });
 
   });
 });
 
 add_test(function test_busy_1guest_1fxa_calls() {
   actionReceived = false;
 
-  MozLoopService.register().then(() => {
+  MozLoopService.promiseRegisteredWithServers().then(() => {
     let opened = 0;
     Chat.open = function() {
       opened++;
     };
 
     mockPushHandler.notify(1, MozLoopService.channelIDs.callsGuest);
     mockPushHandler.notify(1, MozLoopService.channelIDs.callsFxA);
 
--- a/browser/components/loop/test/xpcshell/test_loopservice_dnd.js
+++ b/browser/components/loop/test/xpcshell/test_loopservice_dnd.js
@@ -26,17 +26,17 @@ add_test(function test_set_do_not_distur
   do_check_true(Services.prefs.getBoolPref("loop.do_not_disturb"));
 
   run_next_test();
 });
 
 add_test(function test_do_not_disturb_disabled_should_open_chat_window() {
   MozLoopService.doNotDisturb = false;
 
-  MozLoopService.register().then(() => {
+  MozLoopService.promiseRegisteredWithServers().then(() => {
     let opened = false;
     Chat.open = function() {
       opened = true;
     };
 
     mockPushHandler.notify(1, MozLoopService.channelIDs.callsGuest);
 
     waitForCondition(function() opened).then(() => {
--- a/browser/components/loop/test/xpcshell/test_loopservice_initialize.js
+++ b/browser/components/loop/test/xpcshell/test_loopservice_initialize.js
@@ -1,12 +1,12 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
-var startTimerCalled = false;
+let startTimerCalled = false;
 
 /**
  * Tests that registration doesn't happen when the expiry time is
  * not set.
  */
 add_task(function test_initialize_no_expiry() {
   startTimerCalled = false;
 
@@ -18,44 +18,43 @@ add_task(function test_initialize_no_exp
 });
 
 /**
  * Tests that registration doesn't happen when the expiry time is
  * in the past.
  */
 add_task(function test_initialize_expiry_past() {
   // Set time to be 2 seconds in the past.
-  var nowSeconds = Date.now() / 1000;
+  let nowSeconds = Date.now() / 1000;
   Services.prefs.setIntPref("loop.urlsExpiryTimeSeconds", nowSeconds - 2);
   startTimerCalled = false;
 
   MozLoopService.initialize();
 
   Assert.equal(startTimerCalled, false,
     "should not register when expiry time is in past");
 });
 
 /**
  * Tests that registration happens when the expiry time is in
  * the future.
  */
 add_task(function test_initialize_starts_timer() {
   // Set time to be 1 minute in the future
-  var nowSeconds = Date.now() / 1000;
+  let nowSeconds = Date.now() / 1000;
   Services.prefs.setIntPref("loop.urlsExpiryTimeSeconds", nowSeconds + 60);
   startTimerCalled = false;
 
   MozLoopService.initialize();
 
   Assert.equal(startTimerCalled, true,
     "should start the timer when expiry time is in the future");
 });
 
-function run_test()
-{
+function run_test() {
   setupFakeLoopServer();
 
   // Override MozLoopService's initializeTimer, so that we can verify the timeout is called
   // correctly.
   MozLoopService.initializeTimerFunc = function() {
     startTimerCalled = true;
   };
 
--- a/browser/components/loop/test/xpcshell/test_loopservice_notification.js
+++ b/browser/components/loop/test/xpcshell/test_loopservice_notification.js
@@ -5,17 +5,17 @@
 XPCOMUtils.defineLazyModuleGetter(this, "Chat",
                                   "resource:///modules/Chat.jsm");
 
 let openChatOrig = Chat.open;
 
 add_test(function test_openChatWindow_on_notification() {
   Services.prefs.setCharPref("loop.seenToS", "unseen");
 
-  MozLoopService.register().then(() => {
+  MozLoopService.promiseRegisteredWithServers().then(() => {
     let opened = false;
     Chat.open = function() {
       opened = true;
     };
 
     mockPushHandler.notify(1, MozLoopService.channelIDs.callsGuest);
 
     waitForCondition(function() opened).then(() => {
--- a/browser/components/loop/test/xpcshell/test_loopservice_registration.js
+++ b/browser/components/loop/test/xpcshell/test_loopservice_registration.js
@@ -12,17 +12,17 @@ Cu.import("resource://services-common/ut
 
 /**
  * Test that the websocket can be fully registered, and that a Loop server
  * failure is reported.
  */
 add_test(function test_register_websocket_success_loop_server_fail() {
   mockPushHandler.registrationResult = "404";
 
-  MozLoopService.register().then(() => {
+  MozLoopService.promiseRegisteredWithServers().then(() => {
     do_throw("should not succeed when loop server registration fails");
   }, (err) => {
     // 404 is an expected failure indicated by the lack of route being set
     // up on the Loop server mock. This is added in the next test.
     Assert.equal(err.message, "404", "Expected no errors in websocket registration");
 
     run_next_test();
   });
@@ -44,17 +44,17 @@ add_test(function test_register_success(
                  "Should send correct calls push url");
     Assert.equal(data.simplePushURLs.rooms, kEndPointUrl,
                  "Should send correct rooms push url");
 
     response.setStatusLine(null, 200, "OK");
     response.processAsync();
     response.finish();
   });
-  MozLoopService.register().then(() => {
+  MozLoopService.promiseRegisteredWithServers().then(() => {
     run_next_test();
   }, err => {
     do_throw("shouldn't error on a successful request");
   });
 });
 
 function run_test() {
   setupFakeLoopServer();
--- a/browser/components/loop/test/xpcshell/test_loopservice_restart.js
+++ b/browser/components/loop/test/xpcshell/test_loopservice_restart.js
@@ -70,31 +70,37 @@ add_task(function test_initialize_with_i
     Assert.ok(false, "Initializing with an invalid token should reject the promise");
   },
   (error) => {
     Assert.equal(MozLoopServiceInternal.pushHandler.pushUrl, kEndPointUrl, "Push URL should match");
     Assert.equal(Services.prefs.getCharPref(LOOP_FXA_TOKEN_PREF), "",
                  "FXA pref should be cleared if token was invalid");
     Assert.equal(Services.prefs.getCharPref(LOOP_FXA_PROFILE_PREF), "",
                  "FXA profile pref should be cleared if token was invalid");
+    Assert.ok(MozLoopServiceInternal.errors.has("login"),
+              "Initialization error should have been reported to UI");
   });
 });
 
 add_task(function test_initialize_with_fxa_token() {
   Services.prefs.setCharPref(LOOP_FXA_PROFILE_PREF, FAKE_FXA_PROFILE);
   Services.prefs.setCharPref(LOOP_FXA_TOKEN_PREF, FAKE_FXA_TOKEN_DATA);
+
+  MozLoopService.errors.clear();
+
   loopServer.registerPathHandler("/registration", (request, response) => {
     response.setStatusLine(null, 200, "OK");
   });
 
   yield MozLoopService.initialize().then(() => {
     Assert.equal(Services.prefs.getCharPref(LOOP_FXA_TOKEN_PREF), FAKE_FXA_TOKEN_DATA,
                  "FXA pref should still be set after initialization");
     Assert.equal(Services.prefs.getCharPref(LOOP_FXA_PROFILE_PREF), FAKE_FXA_PROFILE,
                  "FXA profile should still be set after initialization");
+    Assert.ok(!MozLoopServiceInternal.errors.has("login"), "Initialization error should not exist");
   });
 });
 
 function run_test() {
   setupFakeLoopServer();
   // Note, this is just used to speed up the test.
   Services.prefs.setIntPref(LOOP_INITIAL_DELAY_PREF, 0);
   MozLoopServiceInternal.mocks.pushHandler = mockPushHandler;
--- a/browser/components/loop/test/xpcshell/test_loopservice_token_invalid.js
+++ b/browser/components/loop/test/xpcshell/test_loopservice_token_invalid.js
@@ -26,17 +26,17 @@ add_test(function test_registration_inva
       Assert.equal(Services.prefs.prefHasUserValue(LOOP_HAWK_PREF), false);
       response.setStatusLine(null, 200, "OK");
       response.setHeader("Hawk-Session-Token", fakeSessionToken2, false);
     }
     response.processAsync();
     response.finish();
   });
 
-  MozLoopService.register().then(() => {
+  MozLoopService.promiseRegisteredWithServers().then(() => {
     // Due to the way the time stamp checking code works in hawkclient, we expect a couple
     // of authorization requests before we reset the token.
     Assert.equal(authorizationAttempts, 2);
     Assert.equal(Services.prefs.getCharPref(LOOP_HAWK_PREF), fakeSessionToken2);
     run_next_test();
   }, err => {
     do_throw("shouldn't be a failure result: " + err);
   });
--- a/browser/components/loop/test/xpcshell/test_loopservice_token_save.js
+++ b/browser/components/loop/test/xpcshell/test_loopservice_token_save.js
@@ -11,17 +11,17 @@ add_test(function test_registration_retu
 
   loopServer.registerPathHandler("/registration", (request, response) => {
     response.setStatusLine(null, 200, "OK");
     response.setHeader("Hawk-Session-Token", fakeSessionToken, false);
     response.processAsync();
     response.finish();
   });
 
-  MozLoopService.register().then(() => {
+  MozLoopService.promiseRegisteredWithServers().then(() => {
     var hawkSessionPref;
     try {
       hawkSessionPref = Services.prefs.getCharPref("loop.hawk-session-token");
     } catch (ex) {
     }
     Assert.equal(hawkSessionPref, fakeSessionToken, "Should store" +
       " Hawk-Session-Token header contents in loop.hawk-session-token pref");
 
--- a/browser/components/loop/test/xpcshell/test_loopservice_token_send.js
+++ b/browser/components/loop/test/xpcshell/test_loopservice_token_send.js
@@ -19,17 +19,17 @@ add_test(function test_registration_uses
     Assert.notEqual(header.contains("hash="), -1, "Should contain a hash");
     Assert.notEqual(header.contains("mac="), -1, "Should contain a mac");
 
     response.setStatusLine(null, 200, "OK");
     response.processAsync();
     response.finish();
   });
 
-  MozLoopService.register().then(() => {
+  MozLoopService.promiseRegisteredWithServers().then(() => {
     run_next_test();
   }, err => {
     do_throw("shouldn't error on a succesful request");
   });
 });
 
 
 function run_test() {
--- a/browser/components/loop/test/xpcshell/test_loopservice_token_validation.js
+++ b/browser/components/loop/test/xpcshell/test_loopservice_token_validation.js
@@ -11,17 +11,17 @@ add_test(function test_registration_hand
 
   loopServer.registerPathHandler("/registration", (request, response) => {
     response.setStatusLine(null, 200, "OK");
     response.setHeader("Hawk-Session-Token", wrongSizeToken, false);
     response.processAsync();
     response.finish();
   });
 
-  MozLoopService.register().then(() => {
+  MozLoopService.promiseRegisteredWithServers().then(() => {
     do_throw("should not succeed with a bogus token");
   }, err => {
 
     Assert.equal(err, "session-token-wrong-size", "Should cause an error to be" +
       " called back if the session-token is not 64 characters long");
 
     // for some reason, Assert.throw is misbehaving, so....
     var ex;