Bug 992388 - Restyle FxA verification emails errors/success alert and migration prompt in sync preferences. r=markh
authorEdouard Oger <edouard.oger@gmail.com>
Tue, 08 Sep 2015 09:21:29 +1000
changeset 261294 fdd344bc5aef11c56c1387279d77dac87d9c01d6
parent 261293 d9b60a2eab1041782574d1b7859b993746f8798c
child 261295 d65b4a24e69325a6bf3f8b7600974cc10db85b3d
push id64705
push usercbook@mozilla.com
push dateTue, 08 Sep 2015 14:02:43 +0000
treeherdermozilla-inbound@7fa38a962661 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmarkh
bugs992388
milestone43.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 992388 - Restyle FxA verification emails errors/success alert and migration prompt in sync preferences. r=markh
browser/components/preferences/in-content/sync.js
browser/components/preferences/in-content/sync.xul
browser/components/preferences/in-content/tests/browser_bug731866.js
browser/locales/en-US/chrome/browser/accounts.properties
browser/themes/shared/incontentprefs/preferences.inc.css
--- a/browser/components/preferences/in-content/sync.js
+++ b/browser/components/preferences/in-content/sync.js
@@ -181,23 +181,37 @@ let gSyncPane = {
     if (save) {
       Weave.Service.clientsEngine.localName = textbox.value;
     }
     else {
       textbox.value = Weave.Service.clientsEngine.localName;
     }
   },
 
+  _closeSyncStatusMessageBox: function() {
+    document.getElementById("syncStatusMessage").removeAttribute("message-type");
+    document.getElementById("syncStatusMessageTitle").textContent = "";
+    document.getElementById("syncStatusMessageDescription").textContent = "";
+    let learnMoreLink = document.getElementById("learnMoreLink");
+    if (learnMoreLink) {
+      learnMoreLink.parentNode.removeChild(learnMoreLink);
+    }
+    document.getElementById("sync-migration-buttons-deck").hidden = true;
+  },
+
   _setupEventListeners: function() {
     function setEventListener(aId, aEventType, aCallback)
     {
       document.getElementById(aId)
               .addEventListener(aEventType, aCallback.bind(gSyncPane));
     }
 
+    setEventListener("syncStatusMessageClose", "command", function () {
+      gSyncPane._closeSyncStatusMessageBox();
+    });
     setEventListener("noAccountSetup", "click", function (aEvent) {
       aEvent.stopPropagation();
       gSyncPane.openSetup(null);
     });
     setEventListener("noAccountPair", "click", function (aEvent) {
       aEvent.stopPropagation();
       gSyncPane.openSetup('pair');
     });
@@ -426,39 +440,41 @@ let gSyncPane = {
       this.page = PAGE_HAS_ACCOUNT;
       document.getElementById("accountName").textContent = Weave.Service.identity.account;
       document.getElementById("syncComputerName").value = Weave.Service.clientsEngine.localName;
       document.getElementById("tosPP-normal").hidden = this._usingCustomServer;
     }
   },
 
   updateMigrationState: function(subject, state) {
+    this._closeSyncStatusMessageBox();
     let selIndex;
+    let sb = this._accountsStringBundle;
     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");
+        let elt = document.getElementById("syncStatusMessageDescription");
         elt.textContent = email ?
                           sb.formatStringFromName("signInAfterUpgradeOnOtherDevice.description",
                                                   [email], 1) :
                           sb.GetStringFromName("needUserLong");
 
         // The "Learn more" link.
         if (!email) {
           let learnMoreLink = document.createElement("label");
+          learnMoreLink.id = "learnMoreLink";
           learnMoreLink.className = "text-link";
           let { text, href } = fxaMigrator.learnMoreLink;
           learnMoreLink.setAttribute("value", text);
           learnMoreLink.href = href;
-          elt.appendChild(learnMoreLink);
+          elt.parentNode.insertBefore(learnMoreLink, elt.nextSibling);
         }
 
         // The "upgrade" button.
         let button = document.getElementById("sync-migrate-upgrade");
         button.setAttribute("label",
                             sb.GetStringFromName(email
                                                  ? "signInAfterUpgradeOnOtherDevice.label"
                                                  : "upgradeToFxA.label"));
@@ -476,17 +492,17 @@ let gSyncPane = {
         }
         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");
+        let elt = document.getElementById("syncStatusMessageDescription");
         elt.setAttribute("value", label);
         // The "resend" button.
         let button = document.getElementById("sync-migrate-resend");
         button.setAttribute("label", sb.GetStringFromName("resendVerificationEmail.label"));
         button.setAttribute("accesskey", sb.GetStringFromName("resendVerificationEmail.accessKey"));
         // The "forget" button.
         button = document.getElementById("sync-migrate-forget");
         button.setAttribute("label", sb.GetStringFromName("forgetMigration.label"));
@@ -496,18 +512,18 @@ let gSyncPane = {
       }
       default:
         if (state) { // |null| is expected, but everything else is not.
           Cu.reportError("updateMigrationState has unknown state: " + state);
         }
         document.getElementById("sync-migration").hidden = true;
         return;
     }
-    document.getElementById("sync-migration").hidden = false;
-    document.getElementById("sync-migration-deck").selectedIndex = selIndex;
+    document.getElementById("sync-migration-buttons-deck").selectedIndex = selIndex;
+    document.getElementById("syncStatusMessage").setAttribute("message-type", "migration");
   },
 
   // Called whenever one of the sync engine preferences is changed.
   onPreferenceChanged: function() {
     let prefElts = document.querySelectorAll("#syncEnginePrefs > preference");
     let syncEnabled = false;
     for (let elt of prefElts) {
       if (elt.name.startsWith("services.sync.") && elt.value) {
@@ -673,33 +689,49 @@ let gSyncPane = {
       .then(url => {
         this.openContentInBrowser(url, {
           replaceQueryString: true
         });
       });
   },
 
   verifyFirefoxAccount: function() {
-    fxAccounts.resendVerificationEmail().then(() => {
-      fxAccounts.getSignedInUser().then(data => {
-        let sb = this._accountsStringBundle;
-        let title = sb.GetStringFromName("verificationSentTitle");
-        let heading = sb.formatStringFromName("verificationSentHeading",
-                                              [data.email], 1);
-        let description = sb.GetStringFromName("verificationSentDescription");
+    this._closeSyncStatusMessageBox();
+    let changesyncStatusMessage = (data) => {
+      let isError = !data;
+      let syncStatusMessage = document.getElementById("syncStatusMessage");
+      let syncStatusMessageTitle = document.getElementById("syncStatusMessageTitle");
+      let syncStatusMessageDescription = document.getElementById("syncStatusMessageDescription");
+      let maybeNot = isError ? "Not" : "";
+      let sb = this._accountsStringBundle;
+      let title = sb.GetStringFromName("verification" + maybeNot + "SentTitle");
+      let email = !isError && data ? data.email : "";
+      let description = sb.formatStringFromName("verification" + maybeNot + "SentFull", [email], 1)
 
-        let factory = Cc["@mozilla.org/prompter;1"]
-                        .getService(Ci.nsIPromptFactory);
-        let prompt = factory.getPrompt(window, Ci.nsIPrompt);
-        let bag = prompt.QueryInterface(Ci.nsIWritablePropertyBag2);
-        bag.setPropertyAsBool("allowTabModal", true);
+      syncStatusMessageTitle.textContent = title;
+      syncStatusMessageDescription.textContent = description;
+      let messageType = isError ? "verify-error" : "verify-success";
+      syncStatusMessage.setAttribute("message-type", messageType);
+    }
+
+    let onError = () => {
+      changesyncStatusMessage();
+    };
 
-        prompt.alert(title, heading + "\n\n" + description);
-      });
-    });
+    let onSuccess = data => {
+      if (data) {
+        changesyncStatusMessage(data);
+      } else {
+        onError();
+      }
+    };
+
+    fxAccounts.resendVerificationEmail()
+      .then(fxAccounts.getSignedInUser, onError)
+      .then(onSuccess, onError);
   },
 
   openOldSyncSupportPage: function() {
     let url = Services.urlFormatter.formatURLPref("app.support.baseURL") + "old-sync";
     this.openContentInBrowser(url);
   },
 
   unlinkFirefoxAccount: function(confirm) {
--- a/browser/components/preferences/in-content/sync.xul
+++ b/browser/components/preferences/in-content/sync.xul
@@ -33,41 +33,39 @@
 
 <hbox id="header-sync"
       class="header"
       hidden="true"
       data-category="paneSync">
   <label class="header-name">&paneSync.title;</label>
 </hbox>
 
-<hbox id="sync-migration-container"
-       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">
-        <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"/>
-        <button id="sync-migrate-resend"/>
-      </hbox>
-    </deck>
-  </vbox>
-</hbox>
+<vbox id="syncStatusMessage-container" data-category="paneSync" hidden="true">
+  <hbox id="syncStatusMessage">
+    <vbox id="syncStatusMessageWrapper">
+      <label id="syncStatusMessageTitle"></label>
+      <description id="syncStatusMessageDescription"></description>
+      <deck id="sync-migration-buttons-deck">
+        <!-- When we are in the "need FxA user" state -->
+        <hbox>
+          <button id="sync-migrate-unlink"/>
+          <button id="sync-migrate-upgrade"/>
+        </hbox>
+        <!-- When we are in the "need the user to be verified" state -->
+        <hbox>
+          <button id="sync-migrate-forget"/>
+          <button id="sync-migrate-resend"/>
+        </hbox>
+      </deck>
+    </vbox>
+    <vbox>
+      <button id="syncStatusMessageClose" class="close-icon"/>
+    </vbox>
+  </hbox>
+</vbox>
 
 <deck id="weavePrefsDeck" data-category="paneSync" hidden="true">
   <!-- These panels are for the "legacy" sync provider -->
   <vbox id="noAccount" align="center">
     <spacer flex="1"/>
     <description id="syncDesc">
       &weaveDesc.label;
     </description>
--- a/browser/components/preferences/in-content/tests/browser_bug731866.js
+++ b/browser/components/preferences/in-content/tests/browser_bug731866.js
@@ -14,20 +14,21 @@ let gElements;
 function checkElements(expectedPane) {
   for (let element of gElements) {
     // preferences elements fail is_element_visible checks because they are never visible.
     // special-case the drmGroup item because its visibility depends on pref + OS version
     if (element.nodeName == "preferences" || element.id === "drmGroup") {
       continue;
     }
     let attributeValue = element.getAttribute("data-category");
+    let suffix = " (id=" + element.id + ")";
     if (attributeValue == "pane" + expectedPane) {
-      is_element_visible(element, expectedPane + " elements should be visible");
+      is_element_visible(element, expectedPane + " elements should be visible" + suffix);
     } else {
-      is_element_hidden(element, "Elements not in " + expectedPane + " should be hidden");
+      is_element_hidden(element, "Elements not in " + expectedPane + " should be hidden" + suffix);
     }
   }
 }
 
 function runTest(win) {
   is(gBrowser.currentURI.spec, "about:preferences", "about:preferences loaded");
 
   let tab = win.document;
--- a/browser/locales/en-US/chrome/browser/accounts.properties
+++ b/browser/locales/en-US/chrome/browser/accounts.properties
@@ -41,12 +41,15 @@ unlinkVerificationDescription = If you n
 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.
+# LOCALIZATION NOTE (verificationSentFull) - %S = Email address of user's Firefox Account
+verificationSentFull = A verification link has been sent to %S. Please check your email and click the link to begin syncing.
 
 verificationNotSentTitle = Unable to Send Verification
 verificationNotSentHeading = We are unable to send a verification mail at this time
 verificationNotSentDescription = Please try again later.
+verificationNotSentFull = We are unable to send a verification mail at this time, please try again later.
--- a/browser/themes/shared/incontentprefs/preferences.inc.css
+++ b/browser/themes/shared/incontentprefs/preferences.inc.css
@@ -410,16 +410,111 @@ description > html|a {
   padding-right: 15px;
 }
 
 #noFxaGroup > vbox,
 #fxaGroup {
   -moz-box-align: start;
 }
 
+#syncStatusMessage {
+  visibility: collapse;
+  opacity: 0;
+  transition: opacity 1s linear;
+  padding: 14px 8px 14px 14px;
+  border-radius: 2px;
+}
+
+#syncStatusMessage[message-type] {
+  visibility: visible;
+  opacity: 1;
+}
+
+#syncStatusMessage[message-type="verify-success"] {
+  background-color: #74BF43;
+}
+
+#syncStatusMessage[message-type="verify-error"] {
+  background-color: #D74345;
+}
+
+#syncStatusMessage[message-type="migration"] {
+  background-color: #FF9500;
+}
+
+#sync-migration-buttons-deck {
+  visibility: collapse;
+}
+
+#learnMoreLink {
+  margin: 0;
+  color: #FBFBFB;
+  text-decoration: underline;
+}
+
+#syncStatusMessage[message-type="migration"] #sync-migration-buttons-deck {
+  visibility: visible;
+}
+
+#sync-migration-buttons-deck {
+  margin-top: 20px;
+}
+
+#sync-migration-buttons-deck button {
+  margin: 0 10px 0 0;
+  border: 0;
+  border-radius: 2px;
+}
+
+#sync-migrate-upgrade,
+#sync-migrate-resend {
+  background-color: #0095DD;
+  color: #FBFBFB;
+}
+
+#sync-migrate-upgrade:hover,
+#sync-migrate-resend:hover {
+  background-color: #008ACB;
+}
+
+#sync-migrate-upgrade:hover:active,
+#sync-migrate-resend:hover:active {
+  background-color: #006B9D;
+}
+
+#syncStatusMessageWrapper {
+  -moz-box-flex: 1;
+  padding-right: 5px;
+}
+
+#syncStatusMessageTitle, #syncStatusMessageDescription {
+  color: #FBFBFB;
+}
+
+#syncStatusMessage[message-type="migration"] #syncStatusMessageTitle {
+  display: none;
+}
+
+#syncStatusMessageTitle {
+  font-weight: bold !important;
+  font-size: 16px;
+  line-height: 157%;
+  margin: 0 0 20px;
+}
+
+#syncStatusMessageDescription {
+  font-size: 14px;
+  line-height: 158%;
+  margin: 0 !important;
+}
+
+#syncStatusMessageClose {
+  margin: 0px;
+}
+
 #fxaSyncEngines > vbox:first-child {
   margin-right: 80px;
 }
 
 #fxaSyncComputerName {
   margin-inline-start: 0px;
   -moz-box-flex: 1;
 }