Bug 598641 - Sync UI: Show a passphrase strength meter for custom passphrase. r=mconnor a=blocking-beta7
authorPhilipp von Weitershausen <philipp@weitershausen.de>
Fri, 24 Sep 2010 00:19:42 +0200
changeset 54545 f2ee394164cfb3ee3d5e343059232004fb913b55
parent 54544 432d9820df9e67cb8ced9ca815596d86ad1f87a8
child 54546 8f83f4b8e3b99f8894750765483c6824673c1bb6
push idunknown
push userunknown
push dateunknown
reviewersmconnor, blocking-beta7
bugs598641
milestone2.0b7pre
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 598641 - Sync UI: Show a passphrase strength meter for custom passphrase. r=mconnor a=blocking-beta7
browser/base/content/syncGenericChange.js
browser/base/content/syncGenericChange.xul
browser/base/content/syncSetup.js
browser/base/content/syncSetup.xul
browser/base/content/syncUtils.js
browser/locales/en-US/chrome/browser/syncSetup.dtd
browser/themes/gnomestripe/browser/syncCommon.css
browser/themes/pinstripe/browser/syncCommon.css
browser/themes/winstripe/browser/syncCommon.css
--- a/browser/base/content/syncGenericChange.js
+++ b/browser/base/content/syncGenericChange.js
@@ -70,16 +70,17 @@ let Change = {
     let introText2 = document.getElementById("introText2");
     let warningText = document.getElementById("warningText");
 
     // load some other elements & info from the window
     this._dialog = document.getElementById("change-dialog");
     this._dialogType = window.arguments[0];
     this._status = document.getElementById("status");
     this._statusIcon = document.getElementById("statusIcon");
+    this._statusRow = document.getElementById("statusRow");
     this._firstBox = document.getElementById("textBox1");
     this._secondBox = document.getElementById("textBox2");
 
     this._stringBundle =
       Services.strings.createBundle("chrome://browser/locale/syncGenericChange.properties");
 
     switch (this._dialogType) {
       case "UpdatePassphrase":
@@ -168,16 +169,17 @@ let Change = {
         break;
     }
   },
 
   doGeneratePassphrase: function () {
     let passphrase = gSyncUtils.generatePassphrase();
     let el = document.getElementById("passphraseBox");
     el.value = gSyncUtils.hyphenatePassphrase(passphrase);
+    document.getElementById("passphraseStrengthRow").hidden = true;
     this._dialog.getButton("accept").disabled = false;
   },
 
   doChangePassphrase: function Change_doChangePassphrase() {
     let pp = gSyncUtils.normalizePassphrase(this._passphraseBox.value);
     if (this._updatingPassphrase) {
       Weave.Service.passphrase = pp;
       if (Weave.Service.login()) {
@@ -229,26 +231,45 @@ let Change = {
 
     if (this._dialogType == "ChangePassword") {
       if (this._currentPasswordInvalid)
         [valid, errorString] = gSyncUtils.validatePassword(this._firstBox);
       else
         [valid, errorString] = gSyncUtils.validatePassword(this._firstBox, this._secondBox);
     }
     else {
-      if (this._updatingPassphrase)
+      if (this._updatingPassphrase) {
         [valid, errorString] = gSyncUtils.validatePassphrase(this._passphraseBox);
-      else
+      } else {
         [valid, errorString] = gSyncUtils.validatePassphrase(this._passphraseBox, true);
+        if (valid)
+          this.displayPassphraseStrength();
+      }
     }
 
     if (errorString == "")
       this._clearStatus();
     else
       this._updateStatusWithString(errorString, "error");
 
+    this._statusRow.hidden = valid;
     this._dialog.getButton("accept").disabled = !valid;
   },
 
+  displayPassphraseStrength: function () {
+    let bits = Weave.Utils.passphraseStrength(this._passphraseBox.value);
+    let meter = document.getElementById("passphraseStrength");
+    meter.value = bits;
+    // The generated 20 character passphrase has an entropy of 94 bits
+    // which we consider "strong".
+    if (bits > 94)
+      meter.className = "strong";
+    else if (bits > 47)
+      meter.className = "medium";
+    else
+      meter.className = "";
+    document.getElementById("passphraseStrengthRow").hidden = false;
+  },
+
   _str: function Change__string(str) {
     return this._stringBundle.GetStringFromName(str);
   }
 };
--- a/browser/base/content/syncGenericChange.xul
+++ b/browser/base/content/syncGenericChange.xul
@@ -102,27 +102,49 @@
           <label id="generatePassphraseButton"
                  value="&syncKeyGenerate.label;"
                  class="text-link inline-link"
                  onclick="event.stopPropagation();
                           Change.doGeneratePassphrase();"/>
         </row>
       </rows>
     </grid>
-    <hbox id="passphraseBackupButtons">
+
+    <vbox id="feedback" pack="center">
+      <hbox id="statusRow" align="center">
+        <image id="statusIcon" class="statusIcon"/>
+        <label id="status" class="status" value=" "/>
+      </hbox>
+
+      <vbox id="passphraseStrengthRow" hidden="true" pack="end">
+        <hbox>
+          <label id="passphraseStrengthLabel"
+                 control="passphraseStrength"
+                 value="&syncKeyStrength.label;"/>
+          <progressmeter id="passphraseStrength"
+                         aria-labelledby="passphraseStrengthLabel"
+                         max="128"
+                         value="0"
+                         flex="1"/>
+        </hbox>
+        <hbox align="right" flex="1">
+          <label class="text-link inline-link"
+                 onclick="event.stopPropagation();
+                          gSyncUtils.openSyncKeyHelp();"
+                 value="&syncKeyHelp.label;"/>
+        </hbox>
+      </vbox>
+    </vbox>
+
+    <hbox id="passphraseBackupButtons" pack="center">
       <button label="&button.syncKeyBackup.print.label;"
               accesskey="&button.syncKeyBackup.print.accesskey;"
               oncommand="gSyncUtils.passphrasePrint('passphraseBox');"/>
       <button label="&button.syncKeyBackup.save.label;"
               accesskey="&button.syncKeyBackup.save.accesskey;"
               oncommand="gSyncUtils.passphraseSave('passphraseBox');"/>
     </hbox>
 
     <description>
       <html:p class="data" id="warningText"/>
     </description>
-
-    <hbox align="center">
-      <image id="statusIcon" class="statusIcon"/>
-      <label id="status" class="status" value=" "/>
-    </hbox>
   </vbox>
 </dialog>
--- a/browser/base/content/syncSetup.js
+++ b/browser/base/content/syncSetup.js
@@ -316,16 +316,17 @@ var gSyncSetup = {
   onPassphraseGenerate: function () {
     let passphrase = gSyncUtils.generatePassphrase();
     Weave.Service.passphrase = passphrase;
     let el = document.getElementById("weavePassphrase");
     el.value = gSyncUtils.hyphenatePassphrase(passphrase);
 
     el = document.getElementById("generatePassphraseButton");
     el.hidden = true;
+    document.getElementById("passphraseStrengthRow").hidden = true;
     let feedback = document.getElementById("passphraseFeedbackRow");
     this._setFeedback(feedback, true, "");
   },
 
   afterBackup: function () {
     this._haveSyncKeyBackup = true;
     this.checkFields();
   },
@@ -339,16 +340,33 @@ var gSyncSetup = {
       str = Weave.Utils.getErrorString("change.passphrase.ppSameAsPassword");
     }
     else {
       [valid, str] = gSyncUtils.validatePassphrase(el1);
     }
 
     let feedback = document.getElementById("passphraseFeedbackRow");
     this._setFeedback(feedback, valid, str);
+    if (!valid)
+      return valid;
+
+    // Display passphrase strength
+    let pp = document.getElementById("weavePassphrase").value;
+    let bits = Weave.Utils.passphraseStrength(pp);
+    let meter = document.getElementById("passphraseStrength");
+    meter.value = bits;
+    // The generated 20 character passphrase has an entropy of 94 bits
+    // which we consider "strong".
+    if (bits > 94)
+      meter.className = "strong";
+    else if (bits > 47)
+      meter.className = "medium";
+    else
+      meter.className = "";
+    document.getElementById("passphraseStrengthRow").hidden = false;
     return valid;
   },
 
   onPageShow: function() {
     switch (this.wizard.pageIndex) {
       case INTRO_PAGE:
         this.wizard.getButton("next").hidden = true;
         this.wizard.getButton("back").hidden = true;
--- a/browser/base/content/syncSetup.xul
+++ b/browser/base/content/syncSetup.xul
@@ -202,33 +202,56 @@
     </description>
     <spacer/>
 
     <groupbox>
       <hbox>
         <label value="&syncKeyEntry.label;"
                accesskey="&syncKeyEntry.accesskey;"
                control="weavePassphrase"/>
+        <spacer flex="1" />
         <label id="generatePassphraseButton"
                value="&syncKeyGenerate.label;"
                class="text-link inline-link"
                onclick="event.stopPropagation();
                         gSyncSetup.onPassphraseGenerate();"/>
       </hbox>
       <textbox id="weavePassphrase"
                onkeyup="gSyncSetup.onPassphraseChange()"
                onchange="gSyncSetup.onPassphraseChange()"
                onfocus="this.select()"/>
-      <hbox id="passphraseFeedbackRow" align="center" hidden="true">
-        <spacer/>
-        <hbox>
-          <image class="statusIcon"/>
-          <label class="status" value=" "/>
+
+      <vbox id="passphraseFeedback" pack="center">
+        <hbox id="passphraseFeedbackRow" hidden="true" align="center">
+          <spacer/>
+          <hbox>
+            <image class="statusIcon"/>
+            <label class="status" value=" "/>
+          </hbox>
         </hbox>
-      </hbox>
+
+        <vbox id="passphraseStrengthRow" hidden="true" pack="end">
+          <hbox>
+            <label id="passphraseStrengthLabel"
+                   control="passphraseStrength"
+                   value="&syncKeyStrength.label;"/>
+            <progressmeter id="passphraseStrength"
+                           aria-labelledby="passphraseStrengthLabel"
+                           max="128"
+                           value="0"
+                           flex="1"/>
+          </hbox>
+          <hbox align="right" flex="1">
+            <label class="text-link inline-link"
+                   onclick="event.stopPropagation();
+                            gSyncUtils.openSyncKeyHelp();"
+                   value="&syncKeyHelp.label;"/>
+          </hbox>
+        </vbox>
+      </vbox>
     </groupbox>
 
     <groupbox align="center">
       <description>&syncKeyBackup.description;</description>
       <hbox>
         <button label="&button.syncKeyBackup.print.label;"
                 accesskey="&button.syncKeyBackup.print.accesskey;"
                 oncommand="gSyncUtils.passphrasePrint('weavePassphrase');
--- a/browser/base/content/syncUtils.js
+++ b/browser/base/content/syncUtils.js
@@ -49,16 +49,19 @@ let gSyncUtils = {
   _openLink: function (url) {
     let thisDocEl = document.documentElement,
         openerDocEl = window.opener && window.opener.document.documentElement;
     if (thisDocEl.id == "accountSetup" && window.opener &&
         openerDocEl.id == "BrowserPreferences" && !openerDocEl.instantApply)
       openUILinkIn(url, "window");
     else if (thisDocEl.id == "BrowserPreferences" && !thisDocEl.instantApply)
       openUILinkIn(url, "window");
+    else if (document.documentElement.id == "change-dialog")
+      Weave.Svc.WinMediator.getMostRecentWindow("navigator:browser")
+        .openUILinkIn(url, "tab");
     else
       openUILinkIn(url, "tab");
   },
 
   changeName: function changeName(input) {
     // Make sure to update to a modified name, e.g., empty-string -> default
     Weave.Clients.localName = input.value;
     input.value = Weave.Clients.localName;
@@ -97,16 +100,20 @@ let gSyncUtils = {
   openToS: function () {
     this._openLink(Weave.Svc.Prefs.get("termsURL"));
   },
 
   openPrivacyPolicy: function () {
     this._openLink(Weave.Svc.Prefs.get("privacyURL"));
   },
 
+  openSyncKeyHelp: function () {
+    this._openLink(Weave.Svc.Prefs.get("syncKeyHelpURL"));
+  },
+
   // xxxmpc - fix domain before 1.3 final (bug 583652)
   _baseURL: "http://www.mozilla.com/firefox/sync/",
 
   openFirstClientFirstrun: function () {
     let url = this._baseURL + "firstrun.html";
     this._openLink(url);
   },
 
--- a/browser/locales/en-US/chrome/browser/syncSetup.dtd
+++ b/browser/locales/en-US/chrome/browser/syncSetup.dtd
@@ -38,16 +38,18 @@
 <!ENTITY setup.tosAgree2.accesskey  "">
 
 <!-- New Account Page 2: Sync Key -->
 <!ENTITY setup.newSyncKeyPage.title.label "&brandShortName; Cares About Your Privacy">
 <!ENTITY setup.newSyncKeyPage.description.label "To ensure your total privacy, all of your data is encrypted prior to being uploaded. The Sync Key which is necessary to decrypt your data is not uploaded.">
 <!ENTITY syncKeyEntry.label        "Your Sync Key">
 <!ENTITY syncKeyEntry.accesskey    "K">
 <!ENTITY syncKeyGenerate.label     "Generate">
+<!ENTITY syncKeyStrength.label     "Strength:">
+<!ENTITY syncKeyHelp.label         "What does the strength mean?">
 <!ENTITY syncKeyBackup.description "Your Sync Key is required to access &syncBrand.fullName.label; on other machines. Please create a backup copy. We cannot help you recover your Sync Key.">
 
 <!ENTITY button.syncKeyBackup.print.label     "Print…">
 <!ENTITY button.syncKeyBackup.print.accesskey "P">
 <!ENTITY button.syncKeyBackup.save.label      "Save…">
 <!ENTITY button.syncKeyBackup.save.accesskey  "S">
 
 <!-- New Account Page 3: Captcha -->
--- a/browser/themes/gnomestripe/browser/syncCommon.css
+++ b/browser/themes/gnomestripe/browser/syncCommon.css
@@ -34,8 +34,31 @@ dialog#change-dialog {
 
 image#syncIcon {
   list-style-image: url("chrome://browser/skin/sync-32.png");
 }
 
 #introText {
   margin-top: 2px;
 }
+
+#feedback,
+#passphraseFeedback {
+  height: 4em;
+}
+
+#passphraseStrength {
+  -moz-appearance: none;
+  height: 10px;
+  margin: 4px 0;
+}
+
+#passphraseStrength > .progress-bar {
+  background-color: #ff0000;
+}
+
+#passphraseStrength.medium > .progress-bar {
+  background-color: #ffcc33;
+}
+
+#passphraseStrength.strong > .progress-bar {
+  background-color: #00ff00;
+}
--- a/browser/themes/pinstripe/browser/syncCommon.css
+++ b/browser/themes/pinstripe/browser/syncCommon.css
@@ -34,8 +34,32 @@ dialog#change-dialog {
 
 image#syncIcon {
   list-style-image: url("chrome://browser/skin/sync-32.png");
 }
 
 #introText {
   margin-top: 2px;
 }
+
+#feedback,
+#passphraseFeedback {
+  height: 4em;
+}
+
+#passphraseStrength {
+  -moz-binding: url("chrome://global/content/bindings/progressmeter.xml#progressmeter");
+  -moz-appearance: none;
+  height: 10px;
+  margin: 4px 0;
+}
+
+#passphraseStrength > .progress-bar {
+  background-color: #ff0000;
+}
+
+#passphraseStrength.medium > .progress-bar {
+  background-color: #ffcc33;
+}
+
+#passphraseStrength.strong > .progress-bar {
+  background-color: #00ff00;
+}
--- a/browser/themes/winstripe/browser/syncCommon.css
+++ b/browser/themes/winstripe/browser/syncCommon.css
@@ -34,8 +34,31 @@ dialog#change-dialog {
 
 image#syncIcon {
   list-style-image: url("chrome://browser/skin/sync-32.png");
 }
 
 #introText {
   margin-top: 2px;
 }
+
+#feedback,
+#passphraseFeedback {
+  height: 4em;
+}
+
+#passphraseStrength {
+  -moz-appearance: none;
+  height: 10px;
+  margin: 4px 0;
+}
+
+#passphraseStrength > .progress-bar {
+  background-color: #ff0000;
+}
+
+#passphraseStrength.medium > .progress-bar {
+  background-color: #ffcc33;
+}
+
+#passphraseStrength.strong > .progress-bar {
+  background-color: #00ff00;
+}