Bug 1189524 - Provide visual feedback to the user when login fields are autofilled and autocompleted. r=MattN

Differential Revision: https://phabricator.services.mozilla.com/D18153

--HG--
extra : transplant_source : %B6%06%3A%9B%99%FBhi%06_%93%EC%1C%89%EE%D8%C8%C6%D0%96
This commit is contained in:
prathiksha 2019-02-06 23:50:04 -08:00
parent dba455e2b4
commit 763eb39bf4
7 changed files with 200 additions and 20 deletions

View File

@ -61,7 +61,7 @@ addEventListener("DOMAutoComplete", function(event) {
if (shouldIgnoreLoginManagerEvent(event)) {
return;
}
LoginManagerContent.onUsernameInput(event);
LoginManagerContent.onDOMAutoComplete(event);
});
ContentMetaHandler.init(this);

View File

@ -214,15 +214,7 @@ BrowserCLH.prototype = {
if (shouldIgnoreLoginManagerEvent(event)) {
return;
}
this.LoginManagerContent.onUsernameInput(event);
}, options);
aWindow.addEventListener("blur", event => {
if (ChromeUtils.getClassName(event.target) !== "HTMLInputElement" ||
shouldIgnoreLoginManagerEvent(event)) {
return;
}
this.LoginManagerContent.onUsernameInput(event);
this.LoginManagerContent.onDOMAutoComplete(event);
}, options);
aWindow.addEventListener("pageshow", event => {

View File

@ -10,6 +10,7 @@ var EXPORTED_SYMBOLS = [ "LoginManagerContent",
const PASSWORD_INPUT_ADDED_COALESCING_THRESHOLD_MS = 1;
const AUTOCOMPLETE_AFTER_RIGHT_CLICK_THRESHOLD_MS = 400;
const AUTOFILL_STATE = "-moz-autofill";
const {XPCOMUtils} = ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
const {Services} = ChromeUtils.import("resource://gre/modules/Services.jsm");
@ -605,9 +606,9 @@ var LoginManagerContent = {
},
/**
* Listens for DOMAutoComplete and blur events on an input field.
* Listens for DOMAutoComplete event on login form.
*/
onUsernameInput(event) {
onDOMAutoComplete(event) {
if (!event.isTrusted) {
return;
}
@ -616,21 +617,27 @@ var LoginManagerContent = {
return;
}
var acInputField = event.target;
let acInputField = event.target;
// This is probably a bit over-conservatative.
if (ChromeUtils.getClassName(acInputField.ownerDocument) != "HTMLDocument") {
return;
}
if (!LoginHelper.isUsernameFieldType(acInputField)) {
if (!LoginFormFactory.createFromField(acInputField)) {
return;
}
var acForm = LoginFormFactory.createFromField(acInputField);
if (!acForm) {
return;
if (LoginHelper.isUsernameFieldType(acInputField)) {
this.onUsernameInput(event);
}
},
/**
* Calls fill form on the username field.
*/
onUsernameInput(event) {
let acInputField = event.target;
// If the username is blank, bail out now -- we don't want
// fillForm() to try filling in a login without a username
@ -641,6 +648,7 @@ var LoginManagerContent = {
log("onUsernameInput from", event.type);
let acForm = LoginFormFactory.createFromField(acInputField);
let doc = acForm.ownerDocument;
let formOrigin = LoginUtils._getPasswordOrigin(doc.documentURI);
let recipes = LoginRecipesContent.getRecipes(formOrigin, doc.defaultView);
@ -998,6 +1006,28 @@ var LoginManagerContent = {
});
},
/** Remove login field highlight when its value is cleared or overwritten.
*/
_removeFillFieldHighlight(event) {
let winUtils = event.target.ownerGlobal.windowUtils;
winUtils.removeManuallyManagedState(event.target, AUTOFILL_STATE);
},
/**
* Highlight login fields on autocomplete or autofill on page load.
* @param {Node} element that needs highlighting.
*/
_highlightFilledField(element) {
let winUtils = element.ownerGlobal.windowUtils;
winUtils.addManuallyManagedState(element, AUTOFILL_STATE);
// Remove highlighting when the field is changed.
element.addEventListener("input", this._removeFillFieldHighlight, {
mozSystemGroup: true,
once: true,
});
},
/**
* Attempt to find the username and password fields in a form, and fill them
* in using the provided logins and recipes.
@ -1248,9 +1278,13 @@ var LoginManagerContent = {
let userEnteredDifferentCase = userTriggered && userNameDiffers &&
usernameField.value.toLowerCase() == selectedLogin.username.toLowerCase();
if (!disabledOrReadOnly && !userEnteredDifferentCase && userNameDiffers) {
if (!disabledOrReadOnly) {
if (!userEnteredDifferentCase && userNameDiffers) {
usernameField.setUserInput(selectedLogin.username);
}
this._highlightFilledField(usernameField);
}
}
let doc = form.ownerDocument;
@ -1267,6 +1301,8 @@ var LoginManagerContent = {
this.stateForDocument(doc).fillsByRootElement.set(form.rootElement, autoFilledLogin);
}
this._highlightFilledField(passwordField);
log("_fillForm succeeded");
autofillResult = AUTOFILL_RESULT.FILLED;

View File

@ -21,11 +21,16 @@ support-files =
../authenticate.sjs
skip-if = toolkit == 'android' && !isFennec # Don't run on GeckoView
[test_autocomplete_highlight.html]
scheme = https
skip-if = toolkit == 'android' # autocomplete
[test_autocomplete_https_upgrade.html]
skip-if = toolkit == 'android' # autocomplete
[test_autocomplete_sandboxed.html]
scheme = https
skip-if = toolkit == 'android' # autocomplete
[test_autofill_highlight.html]
scheme = https
[test_autofill_https_upgrade.html]
skip-if = toolkit == 'android' # Bug 1259768
[test_autofill_sandboxed.html]

View File

@ -0,0 +1,81 @@
<!DOCTYPE HTML>
<html>
<head>
<meta charset="utf-8">
<title>Test form field autofill highlight</title>
<script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
<script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
<script type="text/javascript" src="/tests/SimpleTest/AddTask.js"></script>
<script type="text/javascript" src="../../../satchel/test/satchel_common.js"></script>
<script type="text/javascript" src="pwmgr_common.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
</head>
<script>
runInParent(function initLogins() {
const {Services} = ChromeUtils.import("resource://gre/modules/Services.jsm");
let login1 = Cc["@mozilla.org/login-manager/loginInfo;1"]
.createInstance(Ci.nsILoginInfo);
login1.init("https://example.com", "https://autocomplete", null,
"user1", "pass1", "", "");
let login2 = Cc["@mozilla.org/login-manager/loginInfo;1"]
.createInstance(Ci.nsILoginInfo);
login2.init("https://example.com", "https://autocomplete", null,
"user2", "pass2", "", "");
Services.logins.addLogin(login1);
Services.logins.addLogin(login2);
});
</script>
<body>
<p id="display"></p>
<div id="content">
<form id="form1" action="https://autocomplete" onsubmit="return false;">
<input type="text" id="uname">
<input type="password" id="pword">
<button type="submit">Submit</button>
</form>
<pre id="test">
<script>
let {ContentTaskUtils} = SpecialPowers.Cu.import("resource://testing-common/ContentTaskUtils.jsm", {});
add_task(async function test_field_highlight_on_autocomplete() {
// Test username autocomplete.
let username = document.getElementById("uname");
let password = document.getElementById("pword");
username.focus();
let shownPromise = promiseACShown();
synthesizeKey("KEY_ArrowDown");
await shownPromise;
synthesizeKey("KEY_ArrowDown");
await synthesizeKey("KEY_Enter");
await ContentTaskUtils.waitForCondition(() => {
return document.defaultView.getComputedStyle(username).getPropertyValue("filter") !== "none";
}, "Highlight was successfully applied to the username field on username autocomplete");
isnot(document.defaultView.getComputedStyle(password).getPropertyValue("filter"), "none",
"Highlight was successfully applied to the password field on username autocomplete");
// Clear existing highlight on login fields. We check by pressing the tab key after backspace
// (by shifting focus to the next element) because the tab key is known to cause a bug where the
// highlight is applied once again.
username.focus();
synthesizeKey("KEY_Backspace");
synthesizeKey("KEY_Tab");
is(document.defaultView.getComputedStyle(username).getPropertyValue("filter"), "none",
"Highlight was successfully removed on the username field");
synthesizeKey("KEY_Backspace");
synthesizeKey("KEY_Tab");
is(document.defaultView.getComputedStyle(password).getPropertyValue("filter"), "none",
"Highlight was successfully removed on the password field");
});
</script>
</body>
</html>

View File

@ -0,0 +1,67 @@
<!DOCTYPE HTML>
<html>
<head>
<meta charset="utf-8">
<title>Test form field autofill highlight</title>
<script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
<script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
<script type="text/javascript" src="/tests/SimpleTest/AddTask.js"></script>
<script type="text/javascript" src="pwmgr_common.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
</head>
<script>
runInParent(function initLogins() {
const {Services} = ChromeUtils.import("resource://gre/modules/Services.jsm");
let login1 = Cc["@mozilla.org/login-manager/loginInfo;1"]
.createInstance(Ci.nsILoginInfo);
login1.init("https://example.com", "https://autofill", null,
"user1", "pass1", "", "");
Services.logins.addLogin(login1);
});
</script>
<body>
<p id="display"></p>
<div id="content">
<form id="form1" action="https://autofill" onsubmit="return false;">
<input type="text" id="uname">
<input type="password" id="pword">
<button type="submit">Submit</button>
</form>
<pre id="test">
<script>
let {ContentTaskUtils} = SpecialPowers.Cu.import("resource://testing-common/ContentTaskUtils.jsm", {});
add_task(async function test_field_highlight_on_autofill() {
let username = document.getElementById("uname");
let password = document.getElementById("pword");
await ContentTaskUtils.waitForCondition(() => {
return document.defaultView.getComputedStyle(username).getPropertyValue("filter") !== "none";
}, "Highlight was successfully applied to the username field on page load autofill");
isnot(document.defaultView.getComputedStyle(password).getPropertyValue("filter"), "none",
"Highlight was successfully applied to the password field on page load autofill");
// Test that initiating a change on the input value will remove the highlight. We check by pressing
// the tab key after backspace(by shifting focus to the next element) because the tab key is known to
// cause a bug where the highlight is applied once again.
username.focus();
synthesizeKey("KEY_Backspace");
synthesizeKey("KEY_Tab");
let computedStyle = document.defaultView.getComputedStyle(username);
is(computedStyle.getPropertyValue("filter"), "none", "Highlight was successfully removed on change in value of username input element");
synthesizeKey("KEY_Backspace");
synthesizeKey("KEY_Tab");
computedStyle = document.defaultView.getComputedStyle(password);
is(computedStyle.getPropertyValue("filter"), "none", "Highlight was successfully removed on change in value of password input element");
});
</script>
</body>
</html>

View File

@ -6,7 +6,6 @@
<script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
<script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
<script type="text/javascript" src="/tests/SimpleTest/AddTask.js"></script>
<script type="text/javascript" src="../../../satchel/test/satchel_common.js"></script>
<script type="text/javascript" src="pwmgr_common.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
</head>