Bug 1147231 - Part 2 - Add a skeleton of the login fill doorhanger.
MozReview-Commit-ID: 9lRqNoOsuOh
--- a/browser/base/content/browser.css
+++ b/browser/base/content/browser.css
@@ -896,16 +896,22 @@ window[chromehidden~="toolbar"] toolbar:
center left / 64px 128px no-repeat transparent;
}
#historySwipeAnimationNextArrow {
background: url("chrome://browser/content/history-swipe-arrow.svg")
center left / 64px 128px no-repeat transparent;
transform: rotate(180deg);
}
+/* Logins UI */
+
+.login-fill-item {
+ -moz-binding: url("chrome://passwordmgr/content/login.xml#login");
+}
+
/* Full Screen UI */
#fullscr-toggler {
height: 1px;
background: black;
}
html|*.pointerlockfswarning {
--- a/browser/base/content/popup-notifications.inc
+++ b/browser/base/content/popup-notifications.inc
@@ -88,8 +88,13 @@
</popupnotificationcontent>
</popupnotification>
<popupnotification id="addon-installed-notification" hidden="true">
<popupnotificationcontent class="addon-installed-notification-content" orient="vertical">
<description>&addonPostInstallMessage.label;</description>
</popupnotificationcontent>
</popupnotification>
+
+ <vbox id="login-fill-doorhanger" hidden="true">
+ <textbox id="login-fill-filter"/>
+ <richlistbox id="login-fill-list"/>
+ </vbox>
--- a/browser/themes/shared/notification-icons.inc.css
+++ b/browser/themes/shared/notification-icons.inc.css
@@ -65,16 +65,17 @@
.autoplay-media-icon.blocked-permission-icon {
list-style-image: url(chrome://browser/skin/notification-icons/autoplay-media-blocked.svg);
}
.popup-notification-icon[popupid="geolocation"] {
list-style-image: url(chrome://browser/skin/notification-icons/geo-detailed.svg);
}
+#login-fill-notification-icon,
.popup-notification-icon[popupid="indexedDB-permissions-prompt"],
.indexedDB-icon {
list-style-image: url(chrome://browser/skin/notification-icons/indexedDB.svg);
}
.login-icon {
list-style-image: url(chrome://browser/skin/notification-icons/login.svg);
}
new file mode 100755
--- /dev/null
+++ b/toolkit/components/passwordmgr/LoginDoorhangers.jsm
@@ -0,0 +1,155 @@
+/* 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/. */
+
+/*
+ * Doorhanger-style user interfaces to work with logins.
+ */
+
+"use strict";
+
+this.EXPORTED_SYMBOLS = [
+ "LoginDoorhangers",
+];
+
+const { classes: Cc, interfaces: Ci, utils: Cu, results: Cr } = Components;
+
+Cu.import("resource://gre/modules/Doorhangers.jsm");
+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);
+ Doorhangers.DoorhangerBase.call(this, properties);
+};
+
+this.LoginDoorhangers.FillDoorhanger.prototype = {
+ __proto__: Doorhangers.DoorhangerBase.prototype,
+
+ // DoorhangerBase
+ anchorType: "login-fill-notification-icon",
+
+ /**
+ * 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();
+ },
+
+ /**
+ * 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() {
+ // Remove the doorhanger for testing.
+ this.remove();
+ },
+
+ /**
+ * 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);
+ }
+ },
+
+ // DoorhangerBase
+ 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);
+
+ this.element.hidden = false;
+ },
+
+ // DoorhangerBase
+ 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);
+ Doorhangers.DoorhangerBase.prototype.unbind.call(this);
+ },
+};
+
+/**
+ * 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 }) {
+ return Doorhangers.find({
+ browser,
+ anchorType: "login-fill-notification-icon",
+ });
+};
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,9 +1,10 @@
# 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/recipes.json (content/recipes.json)
--- a/toolkit/components/passwordmgr/moz.build
+++ b/toolkit/components/passwordmgr/moz.build
@@ -37,16 +37,17 @@ EXTRA_COMPONENTS += [
]
EXTRA_PP_COMPONENTS += [
'passwordmgr.manifest',
]
EXTRA_JS_MODULES += [
'InsecurePasswordUtils.jsm',
+ 'LoginDoorhangers.jsm',
'LoginHelper.jsm',
'LoginManagerContent.jsm',
'LoginManagerParent.jsm',
'LoginRecipes.jsm',
'OSCrypto.jsm',
]
if CONFIG['OS_TARGET'] == 'Android':