Bug 965464 - allow for an initial sync on new user signin. r=ckarlof
☠☠ backed out by d143c2077c0c ☠ ☠
authorMark Hammond <mhammond@skippinet.com.au>
Thu, 30 Jan 2014 19:02:46 -0800
changeset 182237 00cead8f21d4cafd2daa37e84c57f828d2d54f7d
parent 182236 0fe6a5fc9081dd199bd9bcd3b984d562b837048a
child 182238 f33d37e23b14a77bf9e42047d98d047042c5f958
push id3343
push userffxbld
push dateMon, 17 Mar 2014 21:55:32 +0000
treeherdermozilla-beta@2f7d3415f79f [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersckarlof
bugs965464
milestone29.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 965464 - allow for an initial sync on new user signin. r=ckarlof
browser/components/preferences/sync.js
services/fxaccounts/FxAccounts.jsm
services/sync/modules/browserid_identity.js
--- a/browser/components/preferences/sync.js
+++ b/browser/components/preferences/sync.js
@@ -124,31 +124,24 @@ let gSyncPane = {
         let enginesListDisabled;
         // Not Verfied implies login error state, so check that first.
         if (!data.verified) {
           fxaLoginStatus.selectedIndex = FXA_LOGIN_UNVERIFIED;
           enginesListDisabled = true;
         // So we think we are logged in, so login problems are next.
         // (Although if the Sync identity manager is still initializing, we
         // ignore login errors and assume all will eventually be good.)
+        } else if (Weave.Service.identity.readyToAuthenticate &&
+                   Weave.Status.login != Weave.LOGIN_SUCCEEDED) {
+          fxaLoginStatus.selectedIndex = FXA_LOGIN_FAILED;
+          enginesListDisabled = true;
+        // Else we must be golden!
         } else {
-          // Weave might not have got around to re-checking if auth is OK,
-          // so tell it to do that now.
-          if (Weave.Status.login != Weave.LOGIN_SUCCEEDED) {
-            Weave.Service.verifyLogin();
-          }
-          if (Weave.Service.identity.readyToAuthenticate &&
-              Weave.Status.login != Weave.LOGIN_SUCCEEDED) {
-            fxaLoginStatus.selectedIndex = FXA_LOGIN_FAILED;
-            enginesListDisabled = true;
-          // Else we must be golden!
-          } else {
-            fxaLoginStatus.selectedIndex = FXA_LOGIN_VERIFIED;
-            enginesListDisabled = false;
-          }
+          fxaLoginStatus.selectedIndex = FXA_LOGIN_VERIFIED;
+          enginesListDisabled = false;
         }
         document.getElementById("fxaEmailAddress1").textContent = data.email;
         document.getElementById("fxaEmailAddress2").textContent = data.email;
         document.getElementById("fxaEmailAddress3").textContent = data.email;
         document.getElementById("fxaSyncComputerName").value = Weave.Service.clientsEngine.localName;
         let enginesList = document.getElementById("fxaSyncEnginesList")
         enginesList.disabled = enginesListDisabled;
         // *sigh* - disabling the <richlistbox> draws each item as if it is disabled,
--- a/services/fxaccounts/FxAccounts.jsm
+++ b/services/fxaccounts/FxAccounts.jsm
@@ -138,26 +138,20 @@ InternalMethods.prototype = {
     return this.getUserAccountData().then((data) => {
       if (!data) {
         throw new Error("Can't get keys; User is not signed in");
       }
       if (data.kA && data.kB) {
         return data;
       }
       if (!this.whenKeysReadyPromise) {
-        this.whenKeysReadyPromise = Promise.defer();
-        return this.fetchAndUnwrapKeys(data.keyFetchToken)
-          .then((data) => {
-            if (this.whenKeysReadyPromise) {
-              this.whenKeysReadyPromise.resolve(data);
-            }
-          });
+        this.whenKeysReadyPromise = this.fetchAndUnwrapKeys(data.keyFetchToken);
       }
-      return this.whenKeysReadyPromise.promise;
-      });
+      return this.whenKeysReadyPromise;
+    });
    },
 
   fetchAndUnwrapKeys: function(keyFetchToken) {
     log.debug("fetchAndUnwrapKeys: token: " + keyFetchToken);
     return Task.spawn(function* task() {
       // Sign out if we don't have a key fetch token.
       if (!keyFetchToken) {
         yield internal.signOut();
--- a/services/sync/modules/browserid_identity.js
+++ b/services/sync/modules/browserid_identity.js
@@ -78,84 +78,90 @@ this.BrowserIDManager.prototype = {
     try {
       return Services.prefs.getBoolPref(PREF_SYNC_SHOW_CUSTOMIZATION);
     } catch (e) {
       return false;
     }
   },
 
   initialize: function() {
-    Services.obs.addObserver(this, fxAccountsCommon.ONVERIFIED_NOTIFICATION, false);
+    Services.obs.addObserver(this, fxAccountsCommon.ONLOGIN_NOTIFICATION, false);
     Services.obs.addObserver(this, fxAccountsCommon.ONLOGOUT_NOTIFICATION, false);
     return this.initializeWithCurrentIdentity();
   },
 
-  initializeWithCurrentIdentity: function() {
+  initializeWithCurrentIdentity: function(isInitialSync=false) {
     this._log.trace("initializeWithCurrentIdentity");
     Components.utils.import("resource://services-sync/main.js");
 
     // Reset the world before we do anything async.
     this.whenReadyToAuthenticate = Promise.defer();
     this._shouldHaveSyncKeyBundle = false;
 
     return fxAccounts.getSignedInUser().then(accountData => {
       if (!accountData) {
         this._log.info("initializeWithCurrentIdentity has no user logged in");
         this._account = null;
         return;
       }
 
-      if (this.needsCustomization) {
-        // If the user chose to "Customize sync options" when signing
-        // up with Firefox Accounts, ask them to choose what to sync.
-        const url = "chrome://browser/content/sync/customize.xul";
-        const features = "centerscreen,chrome,modal,dialog,resizable=no";
-        let win = Services.wm.getMostRecentWindow("navigator:browser");
-
-        let data = {accepted: false};
-        win.openDialog(url, "_blank", features, data);
+      this._account = accountData.email;
+      // The user must be verified before we can do anything at all; we kick
+      // this and the rest of initialization off in the background (ie, we
+      // don't return the promise)
+      this._log.info("Waiting for user to be verified.");
+      fxAccounts.whenVerified(accountData).then(accountData => {
+        // We do the background keybundle fetch...
+        this._log.info("Starting fetch for key bundle.");
+        if (this.needsCustomization) {
+          // If the user chose to "Customize sync options" when signing
+          // up with Firefox Accounts, ask them to choose what to sync.
+          const url = "chrome://browser/content/sync/customize.xul";
+          const features = "centerscreen,chrome,modal,dialog,resizable=no";
+          let win = Services.wm.getMostRecentWindow("navigator:browser");
 
-        if (data.accepted) {
-          Services.prefs.clearUserPref(PREF_SYNC_SHOW_CUSTOMIZATION);
-        } else {
-          // Log out if the user canceled the dialog.
-          return fxAccounts.signOut();
+          let data = {accepted: false};
+          win.openDialog(url, "_blank", features, data);
+
+          if (data.accepted) {
+            Services.prefs.clearUserPref(PREF_SYNC_SHOW_CUSTOMIZATION);
+          } else {
+            // Log out if the user canceled the dialog.
+            return fxAccounts.signOut();
+          }
         }
-      }
-
-      this._account = accountData.email;
-      // We start a background keybundle fetch...
-      this._log.info("Starting background fetch for key bundle.");
-      this._fetchSyncKeyBundle().then(() => {
+      }).then(() => {
+        return this._fetchSyncKeyBundle();
+      }).then(() => {
         this._shouldHaveSyncKeyBundle = true; // and we should actually have one...
         this.whenReadyToAuthenticate.resolve();
         this._log.info("Background fetch for key bundle done");
+        if (isInitialSync) {
+          this._log.info("Doing initial sync actions");
+          Weave.Service.resetClient();
+          Services.obs.notifyObservers(null, "weave:service:setup-complete", null);
+          Weave.Utils.nextTick(Weave.Service.sync, Weave.Service);
+        }
       }).then(null, err => {
         this._shouldHaveSyncKeyBundle = true; // but we probably don't have one...
         this.whenReadyToAuthenticate.reject(err);
         // report what failed...
         this._log.error("Background fetch for key bundle failed: " + err);
         throw err;
       });
       // and we are done - the fetch continues on in the background...
     }).then(null, err => {
       dump("err in processing logged in account "+err.message);
     });
   },
 
   observe: function (subject, topic, data) {
     switch (topic) {
-    case fxAccountsCommon.ONVERIFIED_NOTIFICATION:
     case fxAccountsCommon.ONLOGIN_NOTIFICATION:
-      // For now, we just assume it's the same user logging back in.
-      // Bug 958927 exists to work out what to do if that's not true.  It might
-      // be that the :onlogout observer does a .startOver (or maybe not - TBD)
-      // But for now, do nothing, and sync will just start re-synching in its
-      // own sweet time...
-      this.initializeWithCurrentIdentity();
+      this.initializeWithCurrentIdentity(true);
       break;
 
     case fxAccountsCommon.ONLOGOUT_NOTIFICATION:
       Components.utils.import("resource://services-sync/main.js");
       // Setting .username calls resetCredentials which drops the key bundle
       // and resets _shouldHaveSyncKeyBundle.
       this.username = "";
       this._account = null;
@@ -347,68 +353,35 @@ this.BrowserIDManager.prototype = {
       this._log.error("FxAccounts.getSignedInUser() failed with: " + err);
       return null;
     }
     return userData;
   },
 
   _fetchSyncKeyBundle: function() {
     // Fetch a sync token for the logged in user from the token server.
-    return this._refreshTokenForLoggedInUser(
-    ).then(token => {
-      this._token = token;
-      return this._fxaService.getKeys();
-    }).then(userData => {
+    return this._fxaService.getKeys().then(userData => {
       // unlikely, but if the logged in user somehow changed between these
       // calls we better fail.
       if (!userData || userData.email !== this.account) {
         throw new Error("The currently logged-in user has changed.");
       }
-      // Set the username to be the uid returned by the token server.
-      this.username = this._token.uid.toString();
-      // both Jelly and FxAccounts give us kA/kB as hex.
-      let kB = Utils.hexToBytes(userData.kB);
-      this._syncKeyBundle = deriveKeyBundle(kB);
+      return this._fetchTokenForUser(userData).then(token => {
+        this._token = token;
+        // Set the username to be the uid returned by the token server.
+        this.username = this._token.uid.toString();
+        // both Jelly and FxAccounts give us kA/kB as hex.
+        let kB = Utils.hexToBytes(userData.kB);
+        this._syncKeyBundle = deriveKeyBundle(kB);
+        return;
+      });
     });
   },
 
-  // Refresh the sync token for the currently logged in Firefox Accounts user.
-  // This method requires that this module has been intialized for a user.
-  _refreshTokenForLoggedInUser: function() {
-    return this._fxaService.getSignedInUser().then(function (userData) {
-      if (!userData || userData.email !== this.account) {
-        // This means the logged in user changed or the identity module
-        // wasn't properly initialized. TODO: figure out what needs to
-        // happen here.
-        this._log.error("Currently logged in FxA user differs from what was locally noted. TODO: do proper error handling.");
-        return null;
-      }
-      return this._fetchTokenForUser(userData);
-    }.bind(this));
-  },
-
-  _refreshTokenForLoggedInUserSync: function() {
-    let cb = Async.makeSpinningCallback();
-
-    this._refreshTokenForLoggedInUser().then(function (token) {
-      cb(null, token);
-    },
-    function (err) {
-      cb(err);
-    });
-
-    try {
-      return cb.wait();
-    } catch (err) {
-      this._log.info("refreshTokenForLoggedInUserSync: " + err.message);
-      return null;
-    }
-  },
-
-  // This is a helper to fetch a sync token for the given user data.
+  // Refresh the sync token for the specified Firefox Accounts user.
   _fetchTokenForUser: function(userData) {
     let tokenServerURI = Svc.Prefs.get("tokenServerURI");
     let log = this._log;
     let client = this._tokenServerClient;
 
     // Both Jelly and FxAccounts give us kB as hex
     let kBbytes = CommonUtils.hexToBytes(userData.kB);
     let headers = {"X-Client-State": this._computeXClientState(kBbytes)};
@@ -433,19 +406,41 @@ this.BrowserIDManager.prototype = {
     // wait until the account email is verified and we know that
     // getAssertion() will return a real assertion (not null).
     return this._fxaService.whenVerified(userData)
       .then(() => this._fxaService.getAssertion(audience))
       .then(assertion => getToken(tokenServerURI, assertion))
       .then(token => {
         token.expiration = this._now() + (token.duration * 1000);
         return token;
+      })
+      .then(null, err => {
+        Cu.reportError("Failed to fetch token: " + err);
+        // XXX - TODO - work out how to set sync to an error state.
       });
   },
 
+  _fetchTokenForLoggedInUserSync: function() {
+    let cb = Async.makeSpinningCallback();
+
+    this._fxaService.getSignedInUser().then(userData => {
+      this._fetchTokenForUser(userData).then(token => {
+        cb(null, token);
+      }, err => {
+        cb(err);
+      });
+    });
+    try {
+      return cb.wait();
+    } catch (err) {
+      this._log.info("_fetchTokenForLoggedInUserSync: " + err.message);
+      return null;
+    }
+  },
+
   getResourceAuthenticator: function () {
     return this._getAuthenticationHeader.bind(this);
   },
 
   /**
    * Obtain a function to be used for adding auth to RESTRequest instances.
    */
   getRESTRequestAuthenticator: function() {
@@ -454,17 +449,17 @@ this.BrowserIDManager.prototype = {
 
   /**
    * @return a Hawk HTTP Authorization Header, lightly wrapped, for the .uri
    * of a RESTRequest or AsyncResponse object.
    */
   _getAuthenticationHeader: function(httpObject, method) {
     if (!this.hasValidToken()) {
       // Refresh token for the currently logged in FxA user
-      this._token = this._refreshTokenForLoggedInUserSync();
+      this._token = this._fetchTokenForLoggedInUserSync();
       if (!this._token) {
         return null;
       }
     }
     let credentials = {algorithm: "sha256",
                        id: this._token.id,
                        key: this._token.key,
                       };