Bug 1091161 - MozLoopService: Separate gInitializeTimerFunc from the actual initialize callback so we can retry initialization on demand. r=pkerr
authorMatthew Noorenberghe <mozilla@noorenberghe.ca>
Wed, 29 Oct 2014 16:20:31 -0700
changeset 213110 0b4f5a605288fddb2d79e6237cc75c14b917efd3
parent 213109 1ca37db5609f9d511791b9100ac9c7511cd80843
child 213111 fbe6dcd6b14a6dfe8da70c256666d8e68ed62d7e
push id51143
push usercbook@mozilla.com
push dateThu, 30 Oct 2014 14:14:04 +0000
treeherdermozilla-inbound@e80345c5bf6f [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerspkerr
bugs1091161
milestone36.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 1091161 - MozLoopService: Separate gInitializeTimerFunc from the actual initialize callback so we can retry initialization on demand. r=pkerr
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
@@ -957,23 +932,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.
@@ -1076,35 +1098,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.throttled")) {
-      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
@@ -26,16 +26,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;