Bug 1149975 - Part 1 of 2 - Handle visibility of the login fill doorhanger anchor. r=MattN

This commit is contained in:
Paolo Amadini 2015-05-13 15:34:14 +01:00
parent 4d39d29264
commit 0c89dfb625
13 changed files with 376 additions and 52 deletions

View File

@ -773,8 +773,8 @@ window[chromehidden~="toolbar"] toolbar:not(#nav-bar):not(#TabsToolbar):not(#pri
-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-notification {
-moz-binding: url("chrome://browser/content/urlbarBindings.xml#login-fill-notification");
}
.login-fill-item {

View File

@ -783,6 +783,7 @@
<image id="push-notification-icon" class="notification-anchor-icon" role="button"/>
<image id="addons-notification-icon" class="notification-anchor-icon" role="button"/>
<image id="indexedDB-notification-icon" class="notification-anchor-icon" role="button"/>
<image id="login-fill-notification-icon" class="notification-anchor-icon" role="button"/>
<image id="password-notification-icon" class="notification-anchor-icon" role="button"/>
<image id="webapps-notification-icon" class="notification-anchor-icon" role="button"/>
<image id="plugins-notification-icon" class="notification-anchor-icon" role="button"/>

View File

@ -49,9 +49,15 @@ addMessageListener("ContextMenu:DoCustomCommand", function(message) {
PageMenuChild.executeMenu(message.data);
});
addMessageListener("RemoteLogins:fillForm", function(message) {
LoginManagerContent.receiveMessage(message, content);
});
addEventListener("DOMFormHasPassword", function(event) {
LoginManagerContent.onDOMFormHasPassword(event, content);
InsecurePasswordUtils.checkForInsecurePasswords(event.target);
LoginManagerContent.onFormPassword(event);
});
addEventListener("pageshow", function(event) {
LoginManagerContent.onPageShow(event, content);
});
addEventListener("DOMAutoComplete", function(event) {
LoginManagerContent.onUsernameInput(event);

View File

@ -2781,7 +2781,7 @@ file, You can obtain one at http://mozilla.org/MPL/2.0/.
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">
<binding id="login-fill-notification" extends="chrome://global/content/bindings/notification.xml#popup-notification">
<content>
<children/>
</content>

View File

@ -8,11 +8,25 @@
max-height: 20em;
}
.login-fill-item[disabled] {
color: #888;
background-color: #fff;
}
.login-fill-item[disabled][selected] {
background-color: #eef;
}
.login-hostname {
margin: 4px;
font-weight: bold;
}
.login-fill-item.different-hostname > .login-hostname {
color: #888;
font-style: italic;
}
.login-username {
margin: 4px;
color: #888;

View File

@ -140,6 +140,12 @@
list-style-image: url(chrome://mozapps/skin/passwordmgr/key-16.png);
}
#login-fill-notification-icon {
/* Temporary icon until the capture and fill doorhangers are unified. */
list-style-image: url(chrome://mozapps/skin/passwordmgr/key-16.png);
transform: scaleX(-1);
}
.webapps-notification-icon,
#webapps-notification-icon {
list-style-image: url(chrome://global/skin/icons/webapps-16.png);
@ -311,6 +317,7 @@
list-style-image: url(chrome://mozapps/skin/extensions/extensionGeneric.png);
}
#login-fill-notification-icon,
#password-notification-icon {
list-style-image: url(chrome://mozapps/skin/passwordmgr/key-16@2x.png);
}

View File

@ -4223,7 +4223,8 @@ Tab.prototype = {
}
case "DOMFormHasPassword": {
LoginManagerContent.onFormPassword(aEvent);
LoginManagerContent.onDOMFormHasPassword(aEvent,
this.browser.contentWindow);
break;
}
@ -4365,7 +4366,9 @@ Tab.prototype = {
}
case "pageshow": {
// only send pageshow for the top-level document
LoginManagerContent.onPageShow(aEvent, this.browser.contentWindow);
// The rest of this only handles pageshow for the top-level document.
if (aEvent.originalTarget.defaultView != this.browser.contentWindow)
return;

View File

@ -10,6 +10,7 @@ Cu.import("resource://gre/modules/XPCOMUtils.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "AboutReader", "resource://gre/modules/AboutReader.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "ReaderMode", "resource://gre/modules/ReaderMode.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "LoginManagerContent", "resource://gre/modules/LoginManagerContent.jsm");
let dump = Cu.import("resource://gre/modules/AndroidLog.jsm", {}).AndroidLog.d.bind(null, "Content");
@ -87,3 +88,7 @@ let AboutReaderListener = {
}
};
AboutReaderListener.init();
addMessageListener("RemoteLogins:fillForm", function(message) {
LoginManagerContent.receiveMessage(message, content);
});

View File

@ -3812,6 +3812,7 @@ pref("signon.rememberSignons", true);
pref("signon.autofillForms", true);
pref("signon.autologin.proxy", false);
pref("signon.storeWhenAutocompleteOff", true);
pref("signon.ui.experimental", false);
pref("signon.debug", false);
// Satchel (Form Manager) prefs

View File

@ -11,6 +11,7 @@ this.EXPORTED_SYMBOLS = [
const { classes: Cc, interfaces: Ci, utils: Cu, results: Cr } = Components;
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/LoginManagerParent.jsm");
const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
@ -27,10 +28,8 @@ this.LoginDoorhangers.FillDoorhanger = function (properties) {
this.onListDblClick = this.onListDblClick.bind(this);
this.onListKeyPress = this.onListKeyPress.bind(this);
this.filterString = properties.filterString;
if (properties.browser) {
this.browser = properties.browser;
for (let name of Object.getOwnPropertyNames(properties)) {
this[name] = properties[name];
}
};
@ -48,20 +47,25 @@ this.LoginDoorhangers.FillDoorhanger.prototype = {
* web page is moved to a different chrome window by the swapDocShells method.
*/
set browser(browser) {
const MAX_DATE_VALUE = new Date(8640000000000000);
this._browser = browser;
let doorhanger = this;
let PopupNotifications = this.chomeDocument.defaultView.PopupNotifications;
let notification = PopupNotifications.show(
browser,
"password-fill",
"login-fill",
"",
"password-notification-icon",
"login-fill-notification-icon",
null,
null,
{
dismissed: true,
persistWhileVisible: true,
// This will make the anchor persist forever even if the popup is not
// visible. We'll remove the notification manually when the page
// changes, after we had time to check its final state asynchronously.
timeout: MAX_DATE_VALUE,
eventCallback: function (topic, otherBrowser) {
switch (topic) {
case "shown":
@ -69,6 +73,8 @@ this.LoginDoorhangers.FillDoorhanger.prototype = {
// be called after the "show" method returns, so the reference to
// "this.notification" will be available at this point.
doorhanger.bound = true;
doorhanger.promiseHidden =
new Promise(resolve => doorhanger.onUnbind = resolve);
doorhanger.bind();
break;
@ -76,11 +82,12 @@ this.LoginDoorhangers.FillDoorhanger.prototype = {
case "removed":
if (doorhanger.bound) {
doorhanger.unbind();
doorhanger.onUnbind();
}
break;
case "swapping":
this._browser = otherBrowser;
doorhanger._browser = otherBrowser;
return true;
}
return false;
@ -116,6 +123,11 @@ this.LoginDoorhangers.FillDoorhanger.prototype = {
}
},
/**
* Promise resolved as soon as the notification is hidden.
*/
promiseHidden: Promise.resolve(),
/**
* Removes the doorhanger from the browser.
*/
@ -159,6 +171,18 @@ this.LoginDoorhangers.FillDoorhanger.prototype = {
this.chomeDocument.getElementById("mainPopupSet").appendChild(this.element);
},
/**
* Origin for which the manual fill UI should be displayed, for example
* "http://www.example.com".
*/
loginFormOrigin: "",
/**
* When no login form is present on the page, we may still display a list of
* logins, but we cannot offer manual filling.
*/
loginFormPresent: false,
/**
* User-editable string used to filter the list of all logins.
*/
@ -192,6 +216,12 @@ this.LoginDoorhangers.FillDoorhanger.prototype = {
item.classList.add("login-fill-item");
item.setAttribute("hostname", hostname);
item.setAttribute("username", username);
if (hostname != this.loginFormOrigin) {
item.classList.add("different-hostname");
}
if (!this.loginFormPresent) {
item.setAttribute("disabled", "true");
}
this.list.appendChild(item);
}
},
@ -222,6 +252,23 @@ this.LoginDoorhangers.FillDoorhanger.prototype = {
this.fillLogin();
},
fillLogin() {
if (this.list.selectedItem.hasAttribute("disabled")) {
return;
}
let formLogins = Services.logins.findLogins({}, "", "", null);
let login = formLogins.find(login => {
return login.hostname == this.list.selectedItem.getAttribute("hostname") &&
login.username == this.list.selectedItem.getAttribute("username");
});
if (login) {
LoginManagerParent.fillForm({
browser: this.browser,
loginFormOrigin: this.loginFormOrigin,
login,
}).catch(Cu.reportError);
} else {
Cu.reportError("The selected login has been removed in the meantime.");
}
this.hide();
},
};
@ -238,7 +285,7 @@ this.LoginDoorhangers.FillDoorhanger.prototype = {
*/
this.LoginDoorhangers.FillDoorhanger.find = function ({ browser }) {
let PopupNotifications = browser.ownerDocument.defaultView.PopupNotifications;
let notification = PopupNotifications.getNotification("password-fill",
let notification = PopupNotifications.getNotification("login-fill",
browser);
return notification && notification.doorhanger;
};

View File

@ -164,7 +164,7 @@ var LoginManagerContent = {
return deferred.promise;
},
receiveMessage: function (msg) {
receiveMessage: function (msg, window) {
// Convert an array of logins in simple JS-object form to an array of
// nsILoginInfo objects.
function jsLoginsToXPCOM(logins) {
@ -179,6 +179,16 @@ var LoginManagerContent = {
});
}
if (msg.name == "RemoteLogins:fillForm") {
this.fillForm({
topDocument: window.document,
loginFormOrigin: msg.data.loginFormOrigin,
loginsFound: jsLoginsToXPCOM(msg.data.logins),
recipes: msg.data.recipes,
});
return;
}
let request = this._takeRequest(msg);
switch (msg.name) {
case "RemoteLogins:loginsFound": {
@ -253,35 +263,145 @@ var LoginManagerContent = {
messageData);
},
/*
* onFormPassword
*
* Called when an <input type="password"> element is added to the page
*/
onFormPassword: function (event) {
if (!event.isTrusted)
onDOMFormHasPassword(event, window) {
if (!event.isTrusted) {
return;
}
let form = event.target;
let doc = form.ownerDocument;
let win = doc.defaultView;
let messageManager = messageManagerFromWindow(win);
// Always record the most recently added form with a password field.
this.stateForDocument(form.ownerDocument).loginForm = form;
this._updateLoginFormPresence(window);
let messageManager = messageManagerFromWindow(window);
messageManager.sendAsyncMessage("LoginStats:LoginEncountered");
if (!gEnabled)
if (!gEnabled) {
return;
}
log("onFormPassword for", form.ownerDocument.documentURI);
log("onDOMFormHasPassword for", form.ownerDocument.documentURI);
this._getLoginDataFromParent(form, { showMasterPassword: true })
.then(this.loginsFound.bind(this))
.then(null, Cu.reportError);
},
onPageShow(event, window) {
this._updateLoginFormPresence(window);
},
/**
* Maps all DOM content documents in this content process, including those in
* frames, to the current state used by the Login Manager.
*/
loginFormStateByDocument: new WeakMap(),
/**
* Retrieves a reference to the state object associated with the given
* document. This is initialized to an empty object.
*/
stateForDocument(document) {
let loginFormState = this.loginFormStateByDocument.get(document);
if (!loginFormState) {
loginFormState = {};
this.loginFormStateByDocument.set(document, loginFormState);
}
return loginFormState;
},
/**
* Compute whether there is a login form on any frame of the current page, and
* notify the parent process. This is one of the factors used to control the
* visibility of the password fill doorhanger anchor.
*/
_updateLoginFormPresence(topWindow) {
// For the login form presence notification, we currently support only one
// origin for each browser, so the form origin will always match the origin
// of the top level document.
let loginFormOrigin =
LoginUtils._getPasswordOrigin(topWindow.document.documentURI);
// Returns the first known loginForm present in this window or in any
// same-origin subframes. Returns null if no loginForm is currently present.
let getFirstLoginForm = thisWindow => {
let loginForm = this.stateForDocument(thisWindow.document).loginForm;
if (loginForm) {
return loginForm;
}
for (let i = 0; i < thisWindow.frames.length; i++) {
let frame = thisWindow.frames[i];
if (LoginUtils._getPasswordOrigin(frame.document.documentURI) !=
loginFormOrigin) {
continue;
}
let loginForm = getFirstLoginForm(frame);
if (loginForm) {
return loginForm;
}
}
return null;
};
// Store the actual form to use on the state for the top-level document.
let topState = this.stateForDocument(topWindow.document);
topState.loginFormForFill = getFirstLoginForm(topWindow);
// Determine whether to show the anchor icon for the current tab.
let messageManager = messageManagerFromWindow(topWindow);
messageManager.sendAsyncMessage("RemoteLogins:updateLoginFormPresence", {
loginFormOrigin,
loginFormPresent: !!topState.loginFormForFill,
});
},
/**
* Perform a password fill upon user request coming from the parent process.
* The fill will be in the form previously identified during page navigation.
*
* @param An object with the following properties:
* {
* topDocument:
* DOM document currently associated to the the top-level window
* for which the fill is requested. This may be different from the
* document that originally caused the login UI to be displayed.
* loginFormOrigin:
* String with the origin for which the login UI was displayed.
* This must match the origin of the form used for the fill.
* loginsFound:
* Array containing the login to fill. While other messages may
* have more logins, for this use case this is expected to have
* exactly one element. The origin of the login may be different
* from the origin of the form used for the fill.
* recipes:
* Fill recipes transmitted together with the original message.
* }
*/
fillForm({ topDocument, loginFormOrigin, loginsFound, recipes }) {
let topState = this.stateForDocument(topDocument);
if (!topState.loginFormForFill) {
log("fillForm: There is no login form anymore. The form may have been",
"removed or the document may have changed.");
return;
}
if (LoginUtils._getPasswordOrigin(topDocument.documentURI) !=
loginFormOrigin) {
log("fillForm: The requested origin doesn't match the one form the",
"document. This may mean we navigated to a document from a different",
"site before we had a chance to indicate this change in the user",
"interface.");
return;
}
this._fillForm(topState.loginFormForFill, true, true, true, true,
loginsFound, recipes);
},
loginsFound: function({ form, loginsFound, recipes }) {
let doc = form.ownerDocument;
let autofillForm = gAutofillForms && !PrivateBrowsingUtils.isContentWindowPrivate(doc.defaultView);
this._fillForm(form, autofillForm, false, false, loginsFound, recipes);
this._fillForm(form, autofillForm, false, false, false, loginsFound, recipes);
},
/*
@ -324,7 +444,7 @@ var LoginManagerContent = {
if (usernameField == acInputField && passwordField) {
this._getLoginDataFromParent(acForm, { showMasterPassword: false })
.then(({ form, loginsFound, recipes }) => {
this._fillForm(form, true, true, true, loginsFound, recipes);
this._fillForm(form, true, false, true, true, loginsFound, recipes);
})
.then(null, Cu.reportError);
} else {
@ -626,6 +746,8 @@ var LoginManagerContent = {
*
* @param {HTMLFormElement} form
* @param {bool} autofillForm denotes if we should fill the form in automatically
* @param {bool} clobberUsername controls if an existing username can be
* overwritten
* @param {bool} clobberPassword controls if an existing password value can be
* overwritten
* @param {bool} userTriggered is an indication of whether this filling was triggered by
@ -633,7 +755,7 @@ var LoginManagerContent = {
* @param {nsILoginInfo[]} foundLogins is an array of nsILoginInfo that could be used for the form
* @param {Set} recipes that could be used to affect how the form is filled
*/
_fillForm : function (form, autofillForm, clobberPassword,
_fillForm : function (form, autofillForm, clobberUsername, clobberPassword,
userTriggered, foundLogins, recipes) {
let ignoreAutocomplete = true;
const AUTOFILL_RESULT = {
@ -737,7 +859,9 @@ var LoginManagerContent = {
// Select a login to use for filling in the form.
var selectedLogin;
if (usernameField && (usernameField.value || usernameField.disabled || usernameField.readOnly)) {
if (!clobberUsername && usernameField && (usernameField.value ||
usernameField.disabled ||
usernameField.readOnly)) {
// If username was specified in the field, it's disabled or it's readOnly, only fill in the
// password if we find a matching login.
var username = usernameField.value.toLowerCase();

View File

@ -15,6 +15,10 @@ XPCOMUtils.defineLazyModuleGetter(this, "UserAutoCompleteResult",
"resource://gre/modules/LoginManagerContent.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "AutoCompleteE10S",
"resource://gre/modules/AutoCompleteE10S.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "DeferredTask",
"resource://gre/modules/DeferredTask.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "LoginDoorhangers",
"resource://gre/modules/LoginDoorhangers.jsm");
this.EXPORTED_SYMBOLS = [ "LoginManagerParent", "PasswordsMetricsProvider" ];
@ -168,6 +172,7 @@ var LoginManagerParent = {
mm.addMessageListener("RemoteLogins:findLogins", this);
mm.addMessageListener("RemoteLogins:onFormSubmit", this);
mm.addMessageListener("RemoteLogins:autoCompleteLogins", this);
mm.addMessageListener("RemoteLogins:updateLoginFormPresence", this);
mm.addMessageListener("LoginStats:LoginEncountered", this);
mm.addMessageListener("LoginStats:LoginFillSuccessful", this);
Services.obs.addObserver(this, "LoginStats:NewSavedPassword", false);
@ -216,6 +221,11 @@ var LoginManagerParent = {
break;
}
case "RemoteLogins:updateLoginFormPresence": {
this.updateLoginFormPresence(msg.target, data);
break;
}
case "RemoteLogins:autoCompleteLogins": {
this.doAutocompleteSearch(data, msg.target);
break;
@ -241,6 +251,33 @@ var LoginManagerParent = {
}
},
/**
* Trigger a login form fill and send relevant data (e.g. logins and recipes)
* to the child process (LoginManagerContent).
*/
fillForm: Task.async(function* ({ browser, loginFormOrigin, login }) {
let recipes = [];
if (loginFormOrigin) {
let formHost;
try {
formHost = (new URL(loginFormOrigin)).host;
let recipeManager = yield this.recipeParentPromise;
recipes = recipeManager.getRecipesForHost(formHost);
} catch (ex) {
// Some schemes e.g. chrome aren't supported by URL
}
}
// Convert the array of nsILoginInfo to vanilla JS objects since nsILoginInfo
// doesn't support structured cloning.
let jsLogins = JSON.parse(JSON.stringify([login]));
browser.messageManager.sendAsyncMessage("RemoteLogins:fillForm", {
loginFormOrigin,
logins: jsLogins,
recipes,
});
}),
/**
* Send relevant data (e.g. logins and recipes) to the child process (LoginManagerContent).
*/
@ -281,14 +318,14 @@ var LoginManagerParent = {
// If we're currently displaying a master password prompt, defer
// processing this form until the user handles the prompt.
if (Services.logins.uiBusy) {
log("deferring onFormPassword for", formOrigin);
log("deferring sendLoginDataToChild for", formOrigin);
let self = this;
let observer = {
QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver,
Ci.nsISupportsWeakReference]),
observe: function (subject, topic, data) {
log("Got deferred onFormPassword notification:", topic);
log("Got deferred sendLoginDataToChild notification:", topic);
// Only run observer once.
Services.obs.removeObserver(this, "passwordmgr-crypto-login");
Services.obs.removeObserver(this, "passwordmgr-crypto-loginCanceled");
@ -507,5 +544,101 @@ var LoginManagerParent = {
// Prompt user to save login (via dialog or notification bar)
prompter = getPrompter();
prompter.promptToSavePassword(formLogin);
}
},
/**
* Maps all the <browser> elements for tabs in the parent process to the
* current state used to display tab-specific UI.
*
* This mapping is not updated in case a web page is moved to a different
* chrome window by the swapDocShells method. In this case, it is possible
* that a UI update just requested for the login fill doorhanger and then
* delayed by a few hundred milliseconds will be lost. Later requests would
* use the new browser reference instead.
*
* Given that the case above is rare, and it would not cause any origin
* mismatch at the time of filling because the origin is checked later in the
* content process, this case is left unhandled.
*/
loginFormStateByBrowser: new WeakMap(),
/**
* Retrieves a reference to the state object associated with the given
* browser. This is initialized to an empty object.
*/
stateForBrowser(browser) {
let loginFormState = this.loginFormStateByBrowser.get(browser);
if (!loginFormState) {
loginFormState = {};
this.loginFormStateByBrowser.set(browser, loginFormState);
}
return loginFormState;
},
/**
* Called to indicate whether a login form on the currently loaded page is
* present or not. This is one of the factors used to control the visibility
* of the password fill doorhanger.
*/
updateLoginFormPresence(browser, { loginFormOrigin, loginFormPresent }) {
const ANCHOR_DELAY_MS = 200;
let state = this.stateForBrowser(browser);
// Update the data to use to the latest known values. Since messages are
// processed in order, this will always be the latest version to use.
state.loginFormOrigin = loginFormOrigin;
state.loginFormPresent = loginFormPresent;
// Apply the data to the currently displayed icon later.
if (!state.anchorDeferredTask) {
state.anchorDeferredTask = new DeferredTask(
() => this.updateLoginAnchor(browser),
ANCHOR_DELAY_MS
);
}
state.anchorDeferredTask.arm();
},
updateLoginAnchor: Task.async(function* (browser) {
// Copy the state to use for this execution of the task. These will not
// change during this execution of the asynchronous function, but in case a
// change happens in the state, the function will be retriggered.
let { loginFormOrigin, loginFormPresent } = this.stateForBrowser(browser);
yield Services.logins.initializationPromise;
// Check if there are form logins for the site, ignoring formSubmitURL.
let hasLogins = loginFormOrigin &&
Services.logins.countLogins(loginFormOrigin, "", null) > 0;
// Once this preference is removed, this version of the fill doorhanger
// should be enabled for Desktop only, and not for Android or B2G.
if (!Services.prefs.getBoolPref("signon.ui.experimental")) {
return;
}
let showLoginAnchor = loginFormPresent || hasLogins;
let fillDoorhanger = LoginDoorhangers.FillDoorhanger.find({ browser });
if (fillDoorhanger) {
if (!showLoginAnchor) {
fillDoorhanger.remove();
return;
}
// We should only update the state of the doorhanger while it is hidden.
yield fillDoorhanger.promiseHidden;
fillDoorhanger.loginFormPresent = loginFormPresent;
fillDoorhanger.loginFormOrigin = loginFormOrigin;
fillDoorhanger.filterString = loginFormOrigin;
return;
}
if (showLoginAnchor) {
fillDoorhanger = new LoginDoorhangers.FillDoorhanger({
browser,
loginFormPresent,
loginFormOrigin,
filterString: loginFormOrigin,
});
}
}),
};

View File

@ -120,8 +120,6 @@ LoginManager.prototype = {
// Form submit observer checks forms for new logins and pw changes.
Services.obs.addObserver(this._observer, "xpcom-shutdown", false);
// TODO: Make this class useful in the child process (in addition to
// autoCompleteSearchAsync and fillForm).
if (Services.appinfo.processType ===
Services.appinfo.PROCESS_TYPE_DEFAULT) {
Services.obs.addObserver(this._observer, "passwordmgr-storage-replace",
@ -577,21 +575,6 @@ LoginManager.prototype = {
return this._getPasswordOrigin(uriString, true);
},
/*
* fillForm
*
* Fill the form with login information if we can find it.
*/
fillForm : function (form) {
log("fillForm processing form[ id:", form.id, "]");
return LoginManagerContent._asyncFindLogins(form, { showMasterPassword: true })
.then(function({ form, loginsFound }) {
return LoginManagerContent._fillForm(form, true, false, false, loginsFound)[0];
});
},
}; // end of LoginManager implementation
this.NSGetFactory = XPCOMUtils.generateNSGetFactory([LoginManager]);