Bug 1114445 - update sync migration flows to reflect latest requirements. r=adw
authorMark Hammond <mhammond@skippinet.com.au>
Wed, 07 Jan 2015 13:57:38 +1100
changeset 222404 7a472c24f6aa62de58b54cb7db904709add4e62c
parent 222403 336ead0f496546d23dd5bad6bd1da08190a3f169
child 222405 5d3be0c407a84f4460f52b352806891fe993f0d4
push id28064
push usercbook@mozilla.com
push dateWed, 07 Jan 2015 13:13:08 +0000
treeherdermozilla-central@206205dd8bd1 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersadw
bugs1114445
milestone37.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 1114445 - update sync migration flows to reflect latest requirements. r=adw
browser/base/content/browser-fxaccounts.js
browser/components/preferences/in-content/sync.js
browser/components/preferences/in-content/sync.xul
browser/locales/en-US/chrome/browser/accounts.properties
browser/locales/en-US/chrome/browser/preferences/sync.dtd
browser/themes/shared/incontentprefs/preferences.inc.css
services/sync/modules/FxaMigrator.jsm
--- a/browser/base/content/browser-fxaccounts.js
+++ b/browser/base/content/browser-fxaccounts.js
@@ -269,21 +269,32 @@ let gFxAccounts = {
     if (gBrowser.currentURI.spec.split("?")[0] == "about:accounts") {
       // If the current tab is about:accounts, assume the user just completed a
       // migration step and don't bother them with a redundant notification.
       return;
     }
     let note = null;
     switch (this._migrationInfo.state) {
       case this.fxaMigrator.STATE_USER_FXA: {
-        let msg = this.strings.GetStringFromName("needUserLong");
-        let upgradeLabel =
-          this.strings.GetStringFromName("upgradeToFxA.label");
-        let upgradeAccessKey =
-          this.strings.GetStringFromName("upgradeToFxA.accessKey");
+        // There are 2 cases here - no email address means it is an offer on
+        // the first device (so the user is prompted to create an account).
+        // If there is an email address it is the "join the party" flow, so the
+        // user is prompted to sign in with the address they previously used.
+        let msg, upgradeLabel, upgradeAccessKey;
+        if (this._migrationInfo.email) {
+          msg = this.strings.formatStringFromName("signInAfterUpgradeOnOtherDevice.description",
+                                                  [this._migrationInfo.email],
+                                                  1);
+          upgradeLabel = this.strings.GetStringFromName("signInAfterUpgradeOnOtherDevice.label");
+          upgradeAccessKey = this.strings.GetStringFromName("signInAfterUpgradeOnOtherDevice.accessKey");
+        } else {
+          msg = this.strings.GetStringFromName("needUserLong");
+          upgradeLabel = this.strings.GetStringFromName("upgradeToFxA.label");
+          upgradeAccessKey = this.strings.GetStringFromName("upgradeToFxA.accessKey");
+        }
         note = new Weave.Notification(
           undefined, msg, undefined, Weave.Notifications.PRIORITY_WARNING, [
             new Weave.NotificationButton(upgradeLabel, upgradeAccessKey, () => {
               this.fxaMigrator.createFxAccount(window);
             }),
           ]
         );
         break;
--- a/browser/components/preferences/in-content/sync.js
+++ b/browser/components/preferences/in-content/sync.js
@@ -109,22 +109,16 @@ let gSyncPane = {
 
     window.addEventListener("unload", function() {
       topics.forEach(function (topic) {
         Weave.Svc.Obs.remove(topic, this.updateWeavePrefs, this);
       }, gSyncPane);
       Weave.Svc.Obs.remove(migrateTopic, gSyncPane.updateMigrationState, gSyncPane);
     }, false);
 
-    // ask the migration module to broadcast its current state (and nothing will
-    // happen if it's not loaded - which is good, as that means no migration
-    // is pending/necessary) - we don't want to suck that module in just to
-    // find there's nothing to do.
-    Services.obs.notifyObservers(null, "fxa-migration:state-request", null);
-
     XPCOMUtils.defineLazyGetter(this, '_stringBundle', () => {
       return Services.strings.createBundle("chrome://browser/locale/preferences/preferences.properties");
     }),
 
     XPCOMUtils.defineLazyGetter(this, '_accountsStringBundle', () => {
       return Services.strings.createBundle("chrome://browser/locale/accounts.properties");
     }),
 
@@ -213,26 +207,35 @@ let gSyncPane = {
       gSyncUtils.changeName(this);
     });
     setEventListener("tosPP-small-ToS", "click", gSyncPane.openToS);
     setEventListener("tosPP-small-PP", "click", gSyncPane.openPrivacyPolicy);
     setEventListener("sync-migrate-upgrade", "click", function () {
       let win = Services.wm.getMostRecentWindow("navigator:browser");
       fxaMigrator.createFxAccount(win);
     });
+    setEventListener("sync-migrate-unlink", "click", function () {
+      gSyncPane.startOverMigration();
+    });
     setEventListener("sync-migrate-forget", "click", function () {
       fxaMigrator.forgetFxAccount();
     });
     setEventListener("sync-migrate-resend", "click", function () {
       let win = Services.wm.getMostRecentWindow("navigator:browser");
       fxaMigrator.resendVerificationMail(win);
     });
   },
 
   updateWeavePrefs: function () {
+    // ask the migration module to broadcast its current state (and nothing will
+    // happen if it's not loaded - which is good, as that means no migration
+    // is pending/necessary) - we don't want to suck that module in just to
+    // find there's nothing to do.
+    Services.obs.notifyObservers(null, "fxa-migration:state-request", null);
+
     let service = Components.classes["@mozilla.org/weave/service;1"]
                   .getService(Components.interfaces.nsISupports)
                   .wrappedJSObject;
     // service.fxAccountsEnabled is false iff sync is already configured for
     // the legacy provider.
     if (service.fxAccountsEnabled) {
       // determine the fxa status...
       this.page = PAGE_PLEASE_WAIT;
@@ -294,19 +297,45 @@ let gSyncPane = {
     }
   },
 
   updateMigrationState: function(subject, state) {
     let selIndex;
     switch (state) {
       case fxaMigrator.STATE_USER_FXA: {
         let sb = this._accountsStringBundle;
+        // There are 2 cases here - no email address means it is an offer on
+        // the first device (so the user is prompted to create an account).
+        // If there is an email address it is the "join the party" flow, so the
+        // user is prompted to sign in with the address they previously used.
+        let email = subject ? subject.QueryInterface(Components.interfaces.nsISupportsString).data : null;
+        let elt = document.getElementById("sync-migrate-upgrade-description");
+        elt.textContent = email ?
+                          sb.formatStringFromName("signInAfterUpgradeOnOtherDevice.description",
+                                                  [email], 1) :
+                          sb.GetStringFromName("needUserLong");
+
+        // The "upgrade" button.
         let button = document.getElementById("sync-migrate-upgrade");
-        button.setAttribute("label", sb.GetStringFromName("upgradeToFxA.label"));
-        button.setAttribute("accesskey", sb.GetStringFromName("upgradeToFxA.accessKey"));
+        button.setAttribute("label",
+                            sb.GetStringFromName(email
+                                                 ? "signInAfterUpgradeOnOtherDevice.label"
+                                                 : "upgradeToFxA.label"));
+        button.setAttribute("accesskey",
+                            sb.GetStringFromName(email
+                                                 ? "signInAfterUpgradeOnOtherDevice.accessKey"
+                                                 : "upgradeToFxA.accessKey"));
+        // The "unlink" button - this is only shown for first migration
+        button = document.getElementById("sync-migrate-unlink");
+        if (email) {
+          button.hidden = true;
+        } else {
+          button.setAttribute("label", sb.GetStringFromName("unlinkMigration.label"));
+          button.setAttribute("accesskey", sb.GetStringFromName("unlinkMigration.accessKey"));
+        }
         selIndex = 0;
         break;
       }
       case fxaMigrator.STATE_USER_FXA_VERIFIED: {
         let sb = this._accountsStringBundle;
         let email = subject.QueryInterface(Components.interfaces.nsISupportsString).data;
         let label = sb.formatStringFromName("needVerifiedUserLong", [email], 1);
         let elt = document.getElementById("sync-migrate-verify-label");
@@ -350,16 +379,39 @@ let gSyncPane = {
       if (buttonChoice == 1)
         return;
     }
 
     Weave.Service.startOver();
     this.updateWeavePrefs();
   },
 
+  // When the "Unlink" button in the migration header is selected we display
+  // a slightly different message.
+  startOverMigration: function () {
+    let flags = Services.prompt.BUTTON_POS_0 * Services.prompt.BUTTON_TITLE_IS_STRING +
+                Services.prompt.BUTTON_POS_1 * Services.prompt.BUTTON_TITLE_CANCEL +
+                Services.prompt.BUTTON_POS_1_DEFAULT;
+    let sb = this._accountsStringBundle;
+    let buttonChoice =
+      Services.prompt.confirmEx(window,
+                                sb.GetStringFromName("unlinkVerificationTitle"),
+                                sb.GetStringFromName("unlinkVerificationDescription"),
+                                flags,
+                                sb.GetStringFromName("unlinkVerificationConfirm"),
+                                null, null, null, {});
+
+    // If the user selects cancel, just bail
+    if (buttonChoice == 1)
+      return;
+
+    Weave.Service.startOver();
+    this.updateWeavePrefs();
+  },
+
   updatePass: function () {
     if (Weave.Status.login == Weave.LOGIN_FAILED_LOGIN_REJECTED)
       gSyncUtils.changePassword();
     else
       gSyncUtils.updatePassphrase();
   },
 
   resetPass: function () {
--- a/browser/components/preferences/in-content/sync.xul
+++ b/browser/components/preferences/in-content/sync.xul
@@ -41,18 +41,19 @@
        data-category="paneSync"
        hidden="true">
 
   <vbox id="sync-migration" flex="1" hidden="true">
 
     <deck id="sync-migration-deck">
       <!-- When we are in the "need FxA user" state -->
       <hbox align="center">
-        <label>&migrate.upgradeNeeded;</label>
+        <description id="sync-migrate-upgrade-description" flex="1"/>
         <spacer flex="1"/>
+        <button id="sync-migrate-unlink"/>
         <button id="sync-migrate-upgrade"/>
       </hbox>
 
       <!-- When we are in the "need the user to be verified" state -->
       <hbox align="center">
         <label id="sync-migrate-verify-label"/>
         <spacer flex="1"/>
         <button id="sync-migrate-forget"/>
--- a/browser/locales/en-US/chrome/browser/accounts.properties
+++ b/browser/locales/en-US/chrome/browser/accounts.properties
@@ -5,27 +5,40 @@
 # LOCALIZATION NOTE (needUserShort)
 # %S = Firefox Accounts brand name from syncBrand.dtd
 needUserShort = %S required for sync
 needUserLong = We've rebuilt Sync to make it easier for everyone. Please upgrade to a Firefox Account to continue syncing.
 
 upgradeToFxA.label = Upgrade
 upgradeToFxA.accessKey = U
 
+# LOCALIZATION NOTE (signInAfterUpgradeOnOtherDevice.description)
+# %S = Email address of user's Firefox Account
+signInAfterUpgradeOnOtherDevice.description = Sync was upgraded on another device by %S. Resume syncing?
+signInAfterUpgradeOnOtherDevice.label = Sign In
+signInAfterUpgradeOnOtherDevice.accessKey = S
+
 # LOCALIZATION NOTE (needVerifiedUserShort, needVerifiedUserLong)
 # %S = Email address of user's Firefox Account
 needVerifiedUserShort = %S not verified
 needVerifiedUserLong = Please click the verification link in the email sent to %S
 
 resendVerificationEmail.label = Resend
 resendVerificationEmail.accessKey = R
 
 forgetMigration.label = Forget
 forgetMigration.accessKey = F
 
+unlinkMigration.label = Unlink Sync
+unlinkMigration.accessKey = L
+
+unlinkVerificationTitle = Unlink old version of Sync?
+unlinkVerificationDescription = If you no longer want to be reminded about upgrading Sync, you can unlink your old Sync account to remove it.
+unlinkVerificationConfirm = Unlink
+
 # These strings are used in a dialog we display after the user requests we resend
 # a verification email.
 verificationSentTitle = Verification Sent
 # LOCALIZATION NOTE (verificationSentHeading) - %S = Email address of user's Firefox Account
 verificationSentHeading = A verification link has been sent to %S
 verificationSentDescription = Please check your email and click the link to begin syncing.
 
 verificationNotSentTitle = Unable to Send Verification
--- a/browser/locales/en-US/chrome/browser/preferences/sync.dtd
+++ b/browser/locales/en-US/chrome/browser/preferences/sync.dtd
@@ -73,11 +73,8 @@ both, to better adapt this sentence to t
 <!ENTITY verify.label                "Verify Email">
 <!ENTITY forget.label                "Forget this Email">
 
 <!ENTITY welcome.description "Access your tabs, bookmarks, passwords and more wherever you use &brandShortName;.">
 <!ENTITY welcome.signIn.label "Sign In">
 <!ENTITY welcome.createAccount.label "Create Account">
 
 <!ENTITY welcome.useOldSync.label "Using an older version of Sync?">
-
-<!-- Sync Migration -->
-<!ENTITY migrate.upgradeNeeded      "The sync account system is being discontinued. A new Firefox Account is required to sync.">
--- a/browser/themes/shared/incontentprefs/preferences.inc.css
+++ b/browser/themes/shared/incontentprefs/preferences.inc.css
@@ -286,16 +286,21 @@ description > html|a {
 
 /**
  * End Dialog
  */
 
 /**
  * Sync migration
  */
+#sync-migrate-upgrade-description {
+  /* description elts need a min-width to wrap correctly - bug 630864? */
+  min-width: 100px
+}
+
 #sync-migration {
   border: 1px solid rgba(0, 0, 0, 0.32);
   background-color: InfoBackground;
   color: InfoText;
   text-shadow: none;
   margin: 5px 0 0 0;
   animation: fadein 3000ms;
 }
--- a/services/sync/modules/FxaMigrator.jsm
+++ b/services/sync/modules/FxaMigrator.jsm
@@ -133,21 +133,24 @@ Migrator.prototype = {
         this.log.error(msg);
         return this._promiseCurrentUserState(forceObserver)
       }
     );
   },
 
   _promiseCurrentUserState: Task.async(function* (forceObserver) {
     this.log.trace("starting _promiseCurrentUserState");
-    let update = (newState, subject=null) => {
+    let update = (newState, email=null) => {
       this.log.info("Migration state: '${state}' => '${newState}'",
                     {state: this._state, newState: newState});
       if (forceObserver || newState !== this._state) {
         this._state = newState;
+        let subject = Cc["@mozilla.org/supports-string;1"]
+                      .createInstance(Ci.nsISupportsString);
+        subject.data = email || "";
         Services.obs.notifyObservers(subject, OBSERVER_STATE_CHANGE_TOPIC, newState);
       }
       return newState;
     }
 
     // If we have no sync user, or are already using an FxA account we must
     // be done.
     if (WeaveService.fxAccountsEnabled) {
@@ -168,23 +171,24 @@ Migrator.prototype = {
 
     if (!isEOL) {
       return update(null);
     }
 
     // So we are in EOL mode - have we a user?
     let fxauser = yield fxAccounts.getSignedInUser();
     if (!fxauser) {
-      return update(this.STATE_USER_FXA);
+      // See if there is a migration sentinel so we can send the email
+      // address that was used on a different device for this account (ie, if
+      // this is a "join the party" migration rather than the first)
+      let sentinel = yield this._getSyncMigrationSentinel();
+      return update(this.STATE_USER_FXA, sentinel && sentinel.email);
     }
     if (!fxauser.verified) {
-      let email = Cc["@mozilla.org/supports-string;1"].
-                  createInstance(Ci.nsISupportsString);
-      email.data = fxauser.email || "";
-      return update(this.STATE_USER_FXA_VERIFIED, email);
+      return update(this.STATE_USER_FXA_VERIFIED, fxauser.email);
     }
 
     // So we just have housekeeping to do - we aren't blocked on a user, so
     // reflect that.
     this.log.info("No next user state - doing some housekeeping");
     update(null);
 
     // We need to disable sync from automatically starting,
@@ -383,17 +387,17 @@ Migrator.prototype = {
     // See if we can find a default account name to use.
     let email = yield this._getDefaultAccountName(sentinel);
     let tail = email ? "&email=" + encodeURIComponent(email) : "";
     // We want to ask FxA to offer a "Customize Sync" checkbox iff any engines
     // are disabled.
     let customize = !this._allEnginesEnabled();
     tail += "&customizeSync=" + customize;
 
-    win.switchToTabHavingURI("about:accounts?" + action + tail, true,
+    win.switchToTabHavingURI("about:accounts?action=" + action + tail, true,
                              {ignoreFragment: true, replaceQueryString: true});
     // An FxA observer will fire when the user completes this, which will
     // cause us to move to the next "user blocked" state and notify via our
     // observer notification.
   }),
 
   // Ask the FxA servers to re-send a verification mail for the currently
   // logged in user. This should only be called while we are in the