Bug 1188719 - Show login fill context menu on username field. r=MattN

This commit is contained in:
Bernardo P. Rittmeyer 2015-08-24 19:06:00 -07:00
parent 5b91030628
commit 9439916e5f
6 changed files with 106 additions and 17 deletions

View File

@ -419,8 +419,14 @@
oncommand="gContextMenu.switchPageDirection();"/>
<menuseparator id="fill-login-separator" hidden="true"/>
<menu id="fill-login"
label="&fillPasswordMenu.label;"
accesskey="&fillPasswordMenu.accesskey;"
label="&fillLoginMenu.label;"
label-login="&fillLoginMenu.label;"
label-password="&fillPasswordMenu.label;"
label-username="&fillUsernameMenu.label;"
accesskey="&fillLoginMenu.accesskey;"
accesskey-login="&fillLoginMenu.accesskey;"
accesskey-password="&fillPasswordMenu.accesskey;"
accesskey-username="&fillUsernameMenu.accesskey;"
hidden="true">
<menupopup id="fill-login-popup">
<menuitem id="fill-login-no-logins"

View File

@ -107,6 +107,7 @@ let handleContentContextMenu = function (event) {
let frameOuterWindowID = doc.defaultView.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIDOMWindowUtils)
.outerWindowID;
let loginFillInfo = LoginManagerContent.getFieldContext(event.target);
// get referrer attribute from clicked link and parse it
// if per element referrer is enabled, the element referrer overrules
@ -168,7 +169,8 @@ let handleContentContextMenu = function (event) {
{ editFlags, spellInfo, customMenuItems, addonInfo,
principal, docLocation, charSet, baseURI, referrer,
referrerPolicy, contentType, contentDisposition,
frameOuterWindowID, selectionInfo, disableSetDesktopBg },
frameOuterWindowID, selectionInfo, disableSetDesktopBg,
loginFillInfo, },
{ event, popupNode: event.target });
}
else {
@ -190,6 +192,7 @@ let handleContentContextMenu = function (event) {
contentDisposition: contentDisposition,
selectionInfo: selectionInfo,
disableSetDesktopBackground: disableSetDesktopBg,
loginFillInfo,
};
}
}

View File

@ -506,13 +506,35 @@ nsContextMenu.prototype = {
},
initPasswordManagerItems: function() {
let showFillPassword = this.onPassword;
let disableFillPassword = !Services.logins.isLoggedIn || this.target.disabled || this.target.readOnly;
this.showItem("fill-login-separator", showFillPassword);
this.showItem("fill-login", showFillPassword);
this.setItemAttr("fill-login", "disabled", disableFillPassword);
let loginFillInfo = gContextMenuContentData && gContextMenuContentData.loginFillInfo;
if (!showFillPassword || disableFillPassword) {
// If we could not find a password field we
// don't want to show the form fill option.
let showFill = loginFillInfo && loginFillInfo.passwordField.found;
// Disable the fill option if the user has set a master password
// or if the password field or target field are disabled.
let disableFill = !loginFillInfo ||
!Services.logins ||
!Services.logins.isLoggedIn ||
loginFillInfo.passwordField.disabled ||
(!this.onPassword && loginFillInfo.usernameField.disabled);
this.showItem("fill-login-separator", showFill);
this.showItem("fill-login", showFill);
this.setItemAttr("fill-login", "disabled", disableFill);
// Set the correct label for the fill menu
let fillMenu = document.getElementById("fill-login");
if (this.onPassword) {
fillMenu.setAttribute("label", fillMenu.getAttribute("label-password"));
fillMenu.setAttribute("accesskey", fillMenu.getAttribute("accesskey-password"));
} else {
fillMenu.setAttribute("label", fillMenu.getAttribute("label-login"));
fillMenu.setAttribute("accesskey", fillMenu.getAttribute("accesskey-login"));
}
if (!showFill || disableFill) {
return;
}
let documentURI = gContextMenuContentData.documentURIObject;

View File

@ -4056,6 +4056,7 @@
frameOuterWindowID: aMessage.data.frameOuterWindowID,
selectionInfo: aMessage.data.selectionInfo,
disableSetDesktopBackground: aMessage.data.disableSetDesktopBg,
loginFillInfo: aMessage.data.loginFillInfo,
};
let popup = browser.ownerDocument.getElementById("contentAreaContextMenu");
let event = gContextMenuContentData.event;

View File

@ -476,7 +476,9 @@ var LoginManagerContent = {
// If we have a target input, fills it's form.
if (inputElement) {
form = FormLikeFactory.createFromField(inputElement);
clobberUsername = false;
if (inputElement.type == "password") {
clobberUsername = false;
}
}
this._fillForm(form, true, clobberUsername, true, true, loginsFound, recipes, options);
},
@ -812,8 +814,11 @@ 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} clobberUsername controls if an existing username can be overwritten.
* If this is false and an inputElement of type password
* is also passed, the username field will be ignored.
* If this is false and no inputElement is passed, if the username
* field value is not found in foundLogins, it will not fill the password.
* @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
@ -867,11 +872,16 @@ var LoginManagerContent = {
// the same as the one heuristically found, use the parameter
// one instead.
if (inputElement) {
if (inputElement.type != "password") {
if (inputElement.type == "password") {
passwordField = inputElement;
if (!clobberUsername) {
usernameField = null;
}
} else if (LoginHelper.isUsernameFieldType(inputElement)) {
usernameField = inputElement;
} else {
throw new Error("Unexpected input element type.");
}
passwordField = inputElement;
usernameField = null;
}
// Need a valid password field to do anything.
@ -1031,6 +1041,53 @@ var LoginManagerContent = {
}
},
/**
* Verify if a field is a valid login form field and
* returns some information about it's FormLike.
*
* @param {Element} aField
* A form field we want to verify.
*
* @returns {Object} an object with information about the
* FormLike username and password field
* or null if the passed field is invalid.
*/
getFieldContext(aField) {
// If the element is not a proper form field, return null.
if (!(aField instanceof Ci.nsIDOMHTMLInputElement) ||
(aField.type != "password" && !LoginHelper.isUsernameFieldType(aField)) ||
!aField.ownerDocument) {
return null;
}
let form = FormLikeFactory.createFromField(aField);
let doc = aField.ownerDocument;
let messageManager = messageManagerFromWindow(doc.defaultView);
let recipes = messageManager.sendSyncMessage("RemoteLogins:findRecipes", {
formOrigin: LoginUtils._getPasswordOrigin(doc.documentURI),
})[0];
let [usernameField, newPasswordField, oldPasswordField] =
this._getFormFields(form, false, recipes);
// If we are not verifying a password field, we want
// to use aField as the username field.
if (aField.type != "password") {
usernameField = aField;
}
return {
usernameField: {
found: !!usernameField,
disabled: usernameField && (usernameField.disabled || usernameField.readOnly),
},
passwordField: {
found: !!newPasswordField,
disabled: newPasswordField && (newPasswordField.disabled || newPasswordField.readOnly),
},
};
},
};
var LoginUtils = {

View File

@ -60,7 +60,7 @@ let LoginManagerContextMenu = {
// login is bound so we can keep the reference to each object.
item.addEventListener("command", function(login, event) {
this._fillPassword(login, inputElement, browser, documentURI);
this._fillTargetField(login, inputElement, browser, documentURI);
}.bind(this, login));
fragment.appendChild(item);
@ -151,7 +151,7 @@ let LoginManagerContextMenu = {
* This isn't the same as the browser's top-level
* document URI when subframes are involved.
*/
_fillPassword(login, inputElement, browser, documentURI) {
_fillTargetField(login, inputElement, browser, documentURI) {
LoginManagerParent.fillForm({
browser: browser,
loginFormOrigin: documentURI.prePath,