Bug 1148026 - Add a skeleton of the login fill doorhanger. r=MattN
authorPaolo Amadini <paolo.mozmail@amadzone.org>
Fri, 08 May 2015 13:13:54 +0100
changeset 274330 201c323dcf16f47ae0d35bfd5ca50bb07ac04b46
parent 274329 1683f5135a1f4ee5e046d8446ffd02b95591411d
child 274331 54fc944ba4a4f616fc8d5920abc57e9d33205a98
push id863
push userraliiev@mozilla.com
push dateMon, 03 Aug 2015 13:22:43 +0000
treeherdermozilla-release@f6321b14228d [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersMattN
bugs1148026
milestone40.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 1148026 - Add a skeleton of the login fill doorhanger. r=MattN
browser/base/content/browser.css
browser/base/content/popup-notifications.inc
browser/base/content/urlbarBindings.xml
browser/themes/linux/browser.css
browser/themes/osx/browser.css
browser/themes/shared/login-doorhanger.inc.css
browser/themes/windows/browser.css
toolkit/components/passwordmgr/LoginDoorhangers.jsm
toolkit/components/passwordmgr/content/login.xml
toolkit/components/passwordmgr/jar.mn
toolkit/components/passwordmgr/moz.build
--- a/browser/base/content/browser.css
+++ b/browser/base/content/browser.css
@@ -768,16 +768,24 @@ window[chromehidden~="toolbar"] toolbar:
 #bad-content-notification {
   -moz-binding: url("chrome://browser/content/urlbarBindings.xml#bad-content-notification");
 }
 
 #click-to-play-plugins-notification {
   -moz-binding: url("chrome://browser/content/urlbarBindings.xml#click-to-play-plugins-notification");
 }
 
+#password-fill-notification {
+  -moz-binding: url("chrome://browser/content/urlbarBindings.xml#password-fill-notification");
+}
+
+.login-fill-item {
+  -moz-binding: url("chrome://passwordmgr/content/login.xml#login");
+}
+
 .plugin-popupnotification-centeritem {
   -moz-binding: url("chrome://browser/content/urlbarBindings.xml#plugin-popupnotification-center-item");
 }
 
 browser[tabmodalPromptShowing] {
   -moz-user-focus: none !important;
 }
 
--- a/browser/base/content/popup-notifications.inc
+++ b/browser/base/content/popup-notifications.inc
@@ -57,16 +57,23 @@
     <popupnotification id="password-notification" hidden="true">
       <popupnotificationcontent orient="vertical">
         <textbox id="password-notification-username"/>
         <textbox id="password-notification-password" type="password"
                  disabled="true"/>
       </popupnotificationcontent>
     </popupnotification>
 
+    <vbox id="login-fill-doorhanger" hidden="true">
+      <description id="login-fill-testing"
+                   value="Thanks for testing the login fill doorhanger!"/>
+      <textbox id="login-fill-filter"/>
+      <richlistbox id="login-fill-list"/>
+    </vbox>
+
 #ifdef E10S_TESTING_ONLY
     <popupnotification id="enable-e10s-notification" hidden="true">
       <popupnotificationcontent orient="vertical"/>
     </popupnotification>
 #endif
 
     <popupnotification id="addon-progress-notification" hidden="true">
       <popupnotificationcontent orient="vertical">
--- a/browser/base/content/urlbarBindings.xml
+++ b/browser/base/content/urlbarBindings.xml
@@ -2768,16 +2768,26 @@ file, You can obtain one at http://mozil
     </implementation>
     <handlers>
       <!-- The _accept method checks for .defaultPrevented so that if focus is in a button,
            enter activates the button and not this default action -->
       <handler event="keypress" keycode="VK_RETURN" group="system" action="this._accept(event);"/>
     </handlers>
   </binding>
 
+  <!-- This is the XBL notification definition for the login fill doorhanger,
+       which is empty because the actual panel is not implemented inside an XBL
+       binding, but made of elements added to the notification panel. This
+       allows accessing the full structure while the panel is hidden. -->
+  <binding id="password-fill-notification" extends="chrome://global/content/bindings/notification.xml#popup-notification">
+    <content>
+      <children/>
+    </content>
+  </binding>
+
   <binding id="splitmenu">
     <content>
       <xul:hbox anonid="menuitem" flex="1"
                 class="splitmenu-menuitem"
                 xbl:inherits="iconic,label,disabled,onclick=oncommand,_moz-menuactive=active"/>
       <xul:menu anonid="menu" class="splitmenu-menu"
                 xbl:inherits="disabled,_moz-menuactive=active"
                 oncommand="event.stopPropagation();">
--- a/browser/themes/linux/browser.css
+++ b/browser/themes/linux/browser.css
@@ -1857,16 +1857,17 @@ toolbarbutton.chevron > .toolbarbutton-i
 #full-screen-remember-decision {
   font-size: 120%;
 }
 
 %include ../shared/devtools/responsivedesign.inc.css
 %include ../shared/devtools/commandline.inc.css
 %include ../shared/plugin-doorhanger.inc.css
 %include ../shared/badcontent-doorhanger.inc.css
+%include ../shared/login-doorhanger.inc.css
 
 %include downloads/indicator.css
 
 .gcli-panel {
   padding: 0;
 }
 
 .gclitoolbar-input-node > .textbox-input-box > html|*.textbox-input::-moz-selection {
--- a/browser/themes/osx/browser.css
+++ b/browser/themes/osx/browser.css
@@ -3890,16 +3890,17 @@ notification[value="loop-sharing-notific
 #full-screen-remember-decision {
   font-size: 120%;
 }
 
 %include ../shared/devtools/responsivedesign.inc.css
 %include ../shared/devtools/commandline.inc.css
 %include ../shared/plugin-doorhanger.inc.css
 %include ../shared/badcontent-doorhanger.inc.css
+%include ../shared/login-doorhanger.inc.css
 
 %include downloads/indicator.css
 
 /* On mac, the popup notification contents are indented by default and so
   the default closebutton margins from notification.css require adjustment */
 
 .click-to-play-plugins-notification-description-box > .popup-notification-closebutton {
   -moz-margin-end: -6px;
new file mode 100644
--- /dev/null
+++ b/browser/themes/shared/login-doorhanger.inc.css
@@ -0,0 +1,19 @@
+#login-fill-testing {
+  color: #b33;
+  font-weight: bold;
+}
+
+#login-fill-list {
+  border: 1px solid black;
+  max-height: 20em;
+}
+
+.login-hostname {
+  margin: 4px;
+  font-weight: bold;
+}
+
+.login-username {
+  margin: 4px;
+  color: #888;
+}
--- a/browser/themes/windows/browser.css
+++ b/browser/themes/windows/browser.css
@@ -2451,16 +2451,17 @@ notification[value="loop-sharing-notific
 #full-screen-remember-decision {
   font-size: 120%;
 }
 
 %include ../shared/devtools/responsivedesign.inc.css
 %include ../shared/devtools/commandline.inc.css
 %include ../shared/plugin-doorhanger.inc.css
 %include ../shared/badcontent-doorhanger.inc.css
+%include ../shared/login-doorhanger.inc.css
 
 %include downloads/indicator.css
 
 /* Error counter */
 
 #developer-toolbar-toolbox-button[error-count]:before {
   color: #FDF3DE;
   min-width: 16px;
new file mode 100644
--- /dev/null
+++ b/toolkit/components/passwordmgr/LoginDoorhangers.jsm
@@ -0,0 +1,244 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+this.EXPORTED_SYMBOLS = [
+  "LoginDoorhangers",
+];
+
+const { classes: Cc, interfaces: Ci, utils: Cu, results: Cr } = Components;
+
+Cu.import("resource://gre/modules/Services.jsm");
+
+const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
+
+this.LoginDoorhangers = {};
+
+/**
+ * Doorhanger for selecting and filling logins.
+ *
+ * @param {Object} properties
+ *        Properties from this object will be applied to the new instance.
+ */
+this.LoginDoorhangers.FillDoorhanger = function (properties) {
+  this.onFilterInput = this.onFilterInput.bind(this);
+  this.onListDblClick = this.onListDblClick.bind(this);
+  this.onListKeyPress = this.onListKeyPress.bind(this);
+
+  this.filterString = properties.filterString;
+
+  if (properties.browser) {
+    this.browser = properties.browser;
+  }
+};
+
+this.LoginDoorhangers.FillDoorhanger.prototype = {
+  /**
+   * Whether the elements for this doorhanger are currently in the document.
+   */
+  bound: false,
+
+  /**
+   * Associates the doorhanger with its browser. When the tab associated to this
+   * browser is selected, the anchor icon for the doorhanger will appear.
+   *
+   * The browser may change during the lifetime of the doorhanger, in case the
+   * web page is moved to a different chrome window by the swapDocShells method.
+   */
+  set browser(browser) {
+    this._browser = browser;
+
+    let doorhanger = this;
+    let PopupNotifications = this.chomeDocument.defaultView.PopupNotifications;
+    let notification = PopupNotifications.show(
+      browser,
+      "password-fill",
+      "",
+      "password-notification-icon",
+      null,
+      null,
+      {
+        dismissed: true,
+        persistWhileVisible: true,
+        eventCallback: function (topic, otherBrowser) {
+          switch (topic) {
+            case "shown":
+              // Since we specified the "dismissed" option, this event will only
+              // be called after the "show" method returns, so the reference to
+              // "this.notification" will be available at this point.
+              doorhanger.bound = true;
+              doorhanger.bind();
+              break;
+
+            case "dismissed":
+            case "removed":
+              if (doorhanger.bound) {
+                doorhanger.unbind();
+              }
+              break;
+
+            case "swapping":
+              this._browser = otherBrowser;
+              return true;
+          }
+          return false;
+        },
+      }
+    );
+
+    this.notification = notification;
+    notification.doorhanger = this;
+  },
+  get browser() {
+    return this._browser;
+  },
+  _browser: null,
+
+  /**
+   * DOM document to which the doorhanger is currently associated.
+   *
+   * This may change during the lifetime of the doorhanger, in case the web page
+   * is moved to a different chrome window by the swapDocShells method.
+   */
+  get chomeDocument() {
+    return this.browser.ownerDocument;
+  },
+
+  /**
+   * Hides this notification, if the notification panel is currently open.
+   */
+  hide() {
+    let PopupNotifications = this.chomeDocument.defaultView.PopupNotifications;
+    if (PopupNotifications.isPanelOpen) {
+      PopupNotifications.panel.hidePopup();
+    }
+  },
+
+  /**
+   * Removes the doorhanger from the browser.
+   */
+  remove() {
+    this.notification.remove();
+  },
+
+  /**
+   * Binds this doorhanger to its UI controls.
+   */
+  bind() {
+    this.element = this.chomeDocument.getElementById("login-fill-doorhanger");
+    this.list = this.chomeDocument.getElementById("login-fill-list");
+    this.filter = this.chomeDocument.getElementById("login-fill-filter");
+
+    this.filter.setAttribute("value", this.filterString);
+
+    this.refreshList();
+
+    this.filter.addEventListener("input", this.onFilterInput);
+    this.list.addEventListener("dblclick", this.onListDblClick);
+    this.list.addEventListener("keypress", this.onListKeyPress);
+
+    // Move the main element to the notification panel for displaying.
+    this.notification.owner.panel.firstElementChild.appendChild(this.element);
+    this.element.hidden = false;
+  },
+
+  /**
+   * Unbinds this doorhanger from its UI controls.
+   */
+  unbind() {
+    this.filter.removeEventListener("input", this.onFilterInput);
+    this.list.removeEventListener("dblclick", this.onListDblClick);
+    this.list.removeEventListener("keypress", this.onListKeyPress);
+
+    this.clearList();
+
+    // Place the element back in the document for the next time we need it.
+    this.element.hidden = true;
+    this.chomeDocument.getElementById("mainPopupSet").appendChild(this.element);
+  },
+
+  /**
+   * User-editable string used to filter the list of all logins.
+   */
+  filterString: "",
+
+  /**
+   * Handles text changes in the filter textbox.
+   */
+  onFilterInput() {
+    this.filterString = this.filter.value;
+    this.refreshList();
+  },
+
+  /**
+   * Rebuilds the list of logins.
+   */
+  refreshList() {
+    this.clearList();
+
+    let formLogins = Services.logins.findLogins({}, "", "", null);
+    let filterToUse = this.filterString.trim().toLowerCase();
+    if (filterToUse) {
+      formLogins = formLogins.filter(login => {
+        return login.hostname.toLowerCase().indexOf(filterToUse) != -1 ||
+               login.username.toLowerCase().indexOf(filterToUse) != -1 ;
+      });
+    }
+
+    for (let { hostname, username } of formLogins) {
+      let item = this.chomeDocument.createElementNS(XUL_NS, "richlistitem");
+      item.classList.add("login-fill-item");
+      item.setAttribute("hostname", hostname);
+      item.setAttribute("username", username);
+      this.list.appendChild(item);
+    }
+  },
+
+  /**
+   * Clears the list of logins.
+   */
+  clearList() {
+    while (this.list.firstChild) {
+      this.list.removeChild(this.list.firstChild);
+    }
+  },
+
+  /**
+   * Handles the action associated to a login item.
+   */
+  onListDblClick(event) {
+    if (event.button != 0 || !this.list.selectedItem) {
+      return;
+    }
+    this.fillLogin();
+  },
+  onListKeyPress(event) {
+    if (event.keyCode != Ci.nsIDOMKeyEvent.DOM_VK_RETURN ||
+        !this.list.selectedItem) {
+      return;
+    }
+    this.fillLogin();
+  },
+  fillLogin() {
+    this.hide();
+  },
+};
+
+/**
+ * Retrieves an existing FillDoorhanger associated with a browser, or null if an
+ * associated doorhanger of that type cannot be found.
+ *
+ * @param An object with the following properties:
+ *        {
+ *          browser:
+ *            The <browser> element to which the doorhanger is associated.
+ *        }
+ */
+this.LoginDoorhangers.FillDoorhanger.find = function ({ browser }) {
+  let PopupNotifications = browser.ownerDocument.defaultView.PopupNotifications;
+  let notification = PopupNotifications.getNotification("password-fill",
+                                                        browser);
+  return notification && notification.doorhanger;
+};
new file mode 100644
--- /dev/null
+++ b/toolkit/components/passwordmgr/content/login.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0"?>
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+   - License, v. 2.0. If a copy of the MPL was not distributed with this
+   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<!DOCTYPE bindings SYSTEM "chrome://passwordmgr/locale/passwordManager.dtd">
+
+<bindings id="login-bindings"
+          xmlns="http://www.mozilla.org/xbl"
+          xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+          xmlns:xbl="http://www.mozilla.org/xbl">
+
+  <binding id="login"
+           extends="chrome://global/content/bindings/richlistbox.xml#richlistitem">
+    <content orient="vertical">
+      <xul:label class="login-hostname" crop="end"
+                 xbl:inherits="value=hostname"/>
+      <xul:label class="login-username" crop="end"
+                 xbl:inherits="value=username"/>
+    </content>
+  </binding>
+</bindings>
--- a/toolkit/components/passwordmgr/jar.mn
+++ b/toolkit/components/passwordmgr/jar.mn
@@ -1,11 +1,12 @@
 # This Source Code Form is subject to the terms of the Mozilla Public
 # License, v. 2.0. If a copy of the MPL was not distributed with this
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 toolkit.jar:
 %   content passwordmgr %content/passwordmgr/
+    content/passwordmgr/login.xml                      (content/login.xml)
 *   content/passwordmgr/passwordManager.xul            (content/passwordManager.xul)
 *   content/passwordmgr/passwordManager.js             (content/passwordManager.js)
 *   content/passwordmgr/passwordManagerExceptions.js   (content/passwordManagerExceptions.js)
     content/passwordmgr/passwordManagerExceptions.xul  (content/passwordManagerExceptions.xul)
     content/passwordmgr/passwordManagerCommon.js       (content/passwordManagerCommon.js)
--- a/toolkit/components/passwordmgr/moz.build
+++ b/toolkit/components/passwordmgr/moz.build
@@ -36,16 +36,17 @@ EXTRA_PP_COMPONENTS += [
 ]
 
 EXTRA_PP_JS_MODULES += [
     'LoginManagerParent.jsm',
 ]
 
 EXTRA_JS_MODULES += [
     'InsecurePasswordUtils.jsm',
+    'LoginDoorhangers.jsm',
     'LoginHelper.jsm',
     'LoginManagerContent.jsm',
     'LoginRecipes.jsm',
 ]
 
 if CONFIG['OS_TARGET'] == 'Android':
     EXTRA_COMPONENTS += [
         'storage-mozStorage.js',