Bug 1217134 - Replace show password placeholder with conventional show password checkbox draft
authorMatthew Noorenberghe <mozilla@noorenberghe.ca>
Tue, 15 Mar 2016 20:58:03 -0700
changeset 340875 8b1d86f3c053025d4f77c0300dd3df1c49707668
parent 340493 5e14887312d4523ab59c3f6c6c94a679cf42b496
child 516290 84399299154918c0b7471d78d9e63f8046e6d225
push id13088
push usermozilla@noorenberghe.ca
push dateWed, 16 Mar 2016 03:58:21 +0000
bugs1217134
milestone48.0a1
Bug 1217134 - Replace show password placeholder with conventional show password checkbox WIP patch MozReview-Commit-ID: JkkO2wjwNB6
browser/base/content/browser.css
browser/base/content/popup-notifications.inc
toolkit/components/passwordmgr/nsLoginManagerPrompter.js
toolkit/components/passwordmgr/test/browser/browser_notifications.js
toolkit/locales/en-US/chrome/passwordmgr/passwordmgr.properties
--- a/browser/base/content/browser.css
+++ b/browser/base/content/browser.css
@@ -1301,32 +1301,8 @@ toolbarpaletteitem[place="palette"][hidd
 
 .popup-notification-invalid-input {
   box-shadow: 0 0 1.5px 1px red;
 }
 
 .popup-notification-invalid-input[focused] {
   box-shadow: 0 0 2px 2px rgba(255,0,0,0.4);
 }
-
-#password-notification-password::after {
-  color: hsl(0,0%,60%);
-  content: attr(show-content);
-  pointer-events: none;
-  position: absolute;
-  right: 0;
-  transition: color 250ms;
-}
-
-#password-notification-password:hover::after {
-  color: hsl(210,100%,50%);
-}
-
-#password-notification-password[focused]::after {
-  content: none;
-}
-
-/* Bug 1175941: Disable the transition on 10.10 due to flickering, possibly due to an OS X bug. */
-@media (-moz-mac-yosemite-theme) {
-  #password-notification-password::after {
-    transition: none;
-  }
-}
--- a/browser/base/content/popup-notifications.inc
+++ b/browser/base/content/popup-notifications.inc
@@ -50,16 +50,17 @@
         <label id="pointerLock-cancel">&pointerLock.notification.message;</label>
       </popupnotificationcontent>
     </popupnotification>
 
     <popupnotification id="password-notification" hidden="true">
       <popupnotificationcontent orient="vertical">
         <textbox id="password-notification-username"/>
         <textbox id="password-notification-password" type="password" show-content=""/>
+        <checkbox id="password-notification-visibilityToggle" hidden="true"/>
       </popupnotificationcontent>
     </popupnotification>
 
     <stack id="login-fill-doorhanger" hidden="true">
       <vbox id="login-fill-mainview">
         <description id="login-fill-testing"
                      value="Thanks for testing the login fill doorhanger!"/>
         <textbox id="login-fill-filter"/>
--- a/toolkit/components/passwordmgr/nsLoginManagerPrompter.js
+++ b/toolkit/components/passwordmgr/nsLoginManagerPrompter.js
@@ -850,77 +850,51 @@ LoginManagerPrompter.prototype = {
 
     let writeDataToUI = () => {
       // setAttribute is used since the <textbox> binding may not be attached yet.
       chromeDoc.getElementById("password-notification-username")
                .setAttribute("placeholder", usernamePlaceholder);
       chromeDoc.getElementById("password-notification-username")
                .setAttribute("value", login.username);
 
+      let showCheckbox = chromeDoc.getElementById("password-notification-visibilityToggle");
+      showCheckbox.removeAttribute("checked");
       let passwordField = chromeDoc.getElementById("password-notification-password");
       // Ensure the type is reset so the field is masked.
       passwordField.setAttribute("type", "password");
       passwordField.setAttribute("value", login.password);
-      if (Services.prefs.getBoolPref("signon.rememberSignons.visibilityToggle")) {
-        passwordField.setAttribute("show-content", showPasswordPlaceholder);
-      } else {
-        passwordField.setAttribute("show-content", "");
-      }
       updateButtonLabel();
     };
 
     let readDataFromUI = () => {
       login.username =
         chromeDoc.getElementById("password-notification-username").value;
       login.password =
         chromeDoc.getElementById("password-notification-password").value;
     };
 
     let onInput = () => {
       readDataFromUI();
       updateButtonLabel();
     };
 
-    let onPasswordFocus = (focusEvent) => {
+    let onVisibilityToggle = (commandEvent) => {
       let passwordField = chromeDoc.getElementById("password-notification-password");
       // Gets the caret position before changing the type of the textbox
       let selectionStart = passwordField.selectionStart;
       let selectionEnd = passwordField.selectionEnd;
-      if (focusEvent.rangeParent != null) {
-        // Check for a click over the SHOW placeholder
-        selectionStart = passwordField.value.length;
-        selectionEnd = passwordField.value.length;
+      // Use setAttribute in case the <textbox> binding isn't applied.
+      passwordField.setAttribute("type", commandEvent.target.checked ? "" : "password");
+      if (!passwordField.hasAttribute("focused")) {
+        return;
       }
-      passwordField.setAttribute("type", "");
       passwordField.selectionStart = selectionStart;
       passwordField.selectionEnd = selectionEnd;
     };
 
-    let onPasswordBlur = () => {
-      // Use setAttribute in case the <textbox> binding isn't applied.
-      chromeDoc.getElementById("password-notification-password").setAttribute("type", "password");
-    };
-
-    let onNotificationClick = (clickEvent) => {
-      // Removes focus from textboxes when we click elsewhere on the doorhanger.
-      let focusedElement = Services.focus.focusedElement;
-      if (!focusedElement || focusedElement.nodeName != "html:input") {
-        // No input is focused so we don't need to blur
-        return;
-      }
-
-      let focusedBindingParent = chromeDoc.getBindingParent(focusedElement);
-      if (!focusedBindingParent || focusedBindingParent.nodeName != "textbox" ||
-          clickEvent.explicitOriginalTarget == focusedBindingParent) {
-        // The focus wasn't in a textbox or the click was in the focused textbox.
-        return;
-      }
-      focusedBindingParent.blur();
-    };
-
     let persistData = () => {
       let foundLogins = Services.logins.findLogins({}, login.hostname,
                                                    login.formSubmitURL,
                                                    login.httpRealm);
       let logins = this._filterUpdatableLogins(login, foundLogins);
 
       if (logins.length == 0) {
         // The original login we have been provided with might have its own
@@ -968,17 +942,18 @@ LoginManagerPrompter.prototype = {
       callback: () => {
         histogram.add(PROMPT_NEVER);
         Services.logins.setLoginSavingEnabled(login.hostname, false);
         browser.focus();
       }
     }] : null;
 
     let usernamePlaceholder = this._getLocalizedString("noUsernamePlaceholder");
-    let showPasswordPlaceholder = this._getLocalizedString("showPasswordPlaceholder");
+    let showPasswordLabel = this._getLocalizedString("showPasswordLabel");
+    let showPasswordAccessKey = this._getLocalizedString("showPasswordAccessKey");
     let displayHost = this._getShortDisplayHost(login.hostname);
 
     this._getPopupNote().show(
       browser,
       "password",
       promptMsg,
       "password-notification-icon",
       mainAction,
@@ -992,42 +967,40 @@ LoginManagerPrompter.prototype = {
           switch (topic) {
             case "showing":
               currentNotification = this;
               chromeDoc.getElementById("password-notification-username")
                        .addEventListener("input", onInput);
               chromeDoc.getElementById("password-notification-password")
                        .addEventListener("input", onInput);
               if (Services.prefs.getBoolPref("signon.rememberSignons.visibilityToggle")) {
-                chromeDoc.getElementById("password-notification-password")
-                         .addEventListener("focus", onPasswordFocus);
+                chromeDoc.getElementById("password-notification-visibilityToggle")
+                         .addEventListener("command", onVisibilityToggle);
+                chromeDoc.getElementById("password-notification-visibilityToggle")
+                         .setAttribute("label", showPasswordLabel);
+                chromeDoc.getElementById("password-notification-visibilityToggle")
+                         .setAttribute("accesskey", showPasswordAccessKey);
+                chromeDoc.getElementById("password-notification-visibilityToggle")
+                         .removeAttribute("hidden");
               }
-              chromeDoc.getElementById("password-notification-password")
-                       .addEventListener("blur", onPasswordBlur);
               break;
             case "shown":
-              chromeDoc.getElementById("notification-popup")
-                         .addEventListener("click", onNotificationClick);
               writeDataToUI();
               break;
             case "dismissed":
               readDataFromUI();
               // Fall through.
             case "removed":
               currentNotification = null;
-              chromeDoc.getElementById("notification-popup")
-                       .removeEventListener("click", onNotificationClick);
               chromeDoc.getElementById("password-notification-username")
                        .removeEventListener("input", onInput);
               chromeDoc.getElementById("password-notification-password")
                        .removeEventListener("input", onInput);
-              chromeDoc.getElementById("password-notification-password")
-                       .removeEventListener("focus", onPasswordFocus);
-              chromeDoc.getElementById("password-notification-password")
-                       .removeEventListener("blur", onPasswordBlur);
+              chromeDoc.getElementById("password-notification-visibilityToggle")
+                       .removeEventListener("command", onVisibilityToggle);
               break;
           }
           return false;
         },
       }
     );
   },
 
--- a/toolkit/components/passwordmgr/test/browser/browser_notifications.js
+++ b/toolkit/components/passwordmgr/test/browser/browser_notifications.js
@@ -370,24 +370,27 @@ add_task(function* test_empty_password()
           doc.getElementById("form-basic-password").value = "p";
           doc.getElementById("form-basic").submit();
         });
       yield promiseShown;
 
       let notificationElement = PopupNotifications.panel.childNodes[0];
       let passwordTextbox = notificationElement.querySelector("#password-notification-password");
 
+      let checkbox = notificationElement.querySelector("#password-notification-visibilityToggle");
+      yield EventUtils.synthesizeMouseAtCenter(checkbox, {});
+
+      // Wait for the textbox type to change
+      yield ContentTaskUtils.waitForCondition(() => passwordTextbox.type == "", "Password textbox changed type");
+
       // Focus the password textbox
       let focusPassword = BrowserTestUtils.waitForEvent(passwordTextbox, "focus");
       passwordTextbox.focus();
       yield focusPassword;
 
-      // Wait for the textbox type to change
-      yield ContentTaskUtils.waitForCondition(() => passwordTextbox.type == "", "Password textbox changed type");
-
       // Synthesize input to empty the field
       EventUtils.synthesizeKey("VK_RIGHT", {});
       yield EventUtils.synthesizeKey("VK_BACK_SPACE", {});
 
       let mainActionButton = document.getAnonymousElementByAttribute(notificationElement.button, "anonid", "button");
 
       // Wait for main button to get disabled
       yield ContentTaskUtils.waitForCondition(() => mainActionButton.disabled, "Main action button is disabled");
@@ -428,28 +431,27 @@ add_task(function* test_unfocus_click() 
           doc.getElementById("form-basic-password").value = "password";
           doc.getElementById("form-basic").submit();
         });
       yield promiseShown;
 
       let notificationElement = PopupNotifications.panel.childNodes[0];
       let passwordTextbox = notificationElement.querySelector("#password-notification-password");
 
+      let checkbox = notificationElement.querySelector("#password-notification-visibilityToggle");
+      yield EventUtils.synthesizeMouseAtCenter(checkbox, {});
+
+      // Wait for the textbox type to change
+      yield ContentTaskUtils.waitForCondition(() => passwordTextbox.type == "",
+                                              "Password textbox changed type");
+
       // Focus the password textbox
       let focusPassword = BrowserTestUtils.waitForEvent(passwordTextbox, "focus");
       passwordTextbox.focus();
       yield focusPassword;
 
-      // Wait for the textbox type to change
-      yield ContentTaskUtils.waitForCondition(() => passwordTextbox.type == "",
-                                              "Password textbox changed type");
-
       let notificationIcon = document.getAnonymousElementByAttribute(notificationElement,
                                                                      "class",
                                                                      "popup-notification-icon");
 
       yield EventUtils.synthesizeMouseAtCenter(notificationIcon, {});
-
-      // Wait for the textbox type to change back
-      yield ContentTaskUtils.waitForCondition(() => passwordTextbox.type == "password",
-                                              "Password textbox changed type back to password");
     });
 });
--- a/toolkit/locales/en-US/chrome/passwordmgr/passwordmgr.properties
+++ b/toolkit/locales/en-US/chrome/passwordmgr/passwordmgr.properties
@@ -19,20 +19,18 @@ updateLoginButtonAccessKey = U
 # Note that long usernames may be truncated.
 rememberPasswordMsg = Would you like to remember the password for "%1$S" on %2$S?
 # LOCALIZATION NOTE (rememberPasswordMsgNoUsername):
 # String is the login's hostname.
 rememberPasswordMsgNoUsername = Would you like to remember the password on %S?
 # LOCALIZATION NOTE (noUsernamePlaceholder):
 # This is displayed in place of the username when it is missing.
 noUsernamePlaceholder=No username
-# LOCALIZATION NOTE (showPasswordPlaceholder):
-# This is displayed in the password field to indicate that the password will be
-# shown if focused.
-showPasswordPlaceholder=SHOW
+showPasswordLabel=Show password
+showPasswordAccessKey=S
 notNowButtonText = &Not Now
 notifyBarNotNowButtonText = Not Now
 notifyBarNotNowButtonAccessKey = N
 neverForSiteButtonText = Ne&ver for This Site
 notifyBarNeverRememberButtonText = Never Remember Password for This Site
 notifyBarNeverRememberButtonAccessKey = e
 rememberButtonText = &Remember
 notifyBarRememberPasswordButtonText = Remember Password