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 222258 7a472c24f6aa62de58b54cb7db904709add4e62c
parent 222257 336ead0f496546d23dd5bad6bd1da08190a3f169
child 222259 5d3be0c407a84f4460f52b352806891fe993f0d4
push id10679
push usermhammond@skippinet.com.au
push dateWed, 07 Jan 2015 02:57:51 +0000
treeherderfx-team@5d3be0c407a8 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersadw
bugs1114445
milestone37.0a1
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