зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1847687 - Enable checking a credit card or address field's focusability by calling Services.focus.elementIsFocusable before autofilling - r=credential-management-reviewers,dimi
Differential Revision: https://phabricator.services.mozilla.com/D189072
This commit is contained in:
Родитель
f1a05364e3
Коммит
ae6038dcc3
|
@ -22,6 +22,8 @@ skip-if = ["apple_silicon && !debug"]
|
|||
|
||||
["browser_ignore_invisible_fields.js"]
|
||||
|
||||
["browser_ignore_unfocusable_fields.js"]
|
||||
|
||||
["browser_label_rules.js"]
|
||||
|
||||
["browser_multiple_section.js"]
|
||||
|
|
|
@ -7,7 +7,14 @@ http://creativecommons.org/publicdomain/zero/1.0/ */
|
|||
|
||||
add_heuristic_tests([
|
||||
{
|
||||
description: "all fields are visible",
|
||||
description:
|
||||
"All fields are visible (interactivityCheckMode is set to visibility).",
|
||||
prefs: [
|
||||
[
|
||||
"extensions.formautofill.heuristics.interactivityCheckMode",
|
||||
"visibility",
|
||||
],
|
||||
],
|
||||
fixtureData: `
|
||||
<html>
|
||||
<body>
|
||||
|
@ -44,7 +51,14 @@ add_heuristic_tests([
|
|||
],
|
||||
},
|
||||
{
|
||||
description: "some fields are invisible because of css style",
|
||||
description:
|
||||
"Some fields are invisible due to css styling (interactivityCheckMode is set to visibility).",
|
||||
prefs: [
|
||||
[
|
||||
"extensions.formautofill.heuristics.interactivityCheckMode",
|
||||
"visibility",
|
||||
],
|
||||
],
|
||||
fixtureData: `
|
||||
<html>
|
||||
<body>
|
||||
|
@ -52,9 +66,9 @@ add_heuristic_tests([
|
|||
<input type="text" id="name" autocomplete="name" />
|
||||
<input type="text" id="tel" autocomplete="tel" />
|
||||
<input type="text" id="email" autocomplete="email" />
|
||||
<input type="text" id="country" autocomplete="country" hidden />
|
||||
<input type="text" id="postal-code" autocomplete="postal-code" style="display:none" />
|
||||
<input type="text" id="address-line1" autocomplete="address-line1" style="opacity:0" />
|
||||
<input type="text" id="country" autocomplete="country" />
|
||||
<input type="text" id="postal-code" autocomplete="postal-code" hidden />
|
||||
<input type="text" id="address-line1" autocomplete="address-line1" style="display:none" />
|
||||
<div style="visibility: hidden">
|
||||
<input type="text" id="address-line2" autocomplete="address-line2" />
|
||||
</div>
|
||||
|
@ -71,15 +85,22 @@ add_heuristic_tests([
|
|||
{ fieldName: "name" },
|
||||
{ fieldName: "tel" },
|
||||
{ fieldName: "email" },
|
||||
{ fieldName: "country" },
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
// hidden and style="display:none" are always considered regardless what visibility check we use
|
||||
// hidden, style="display:none" are always considered (when mode visibility)
|
||||
description:
|
||||
"invisible fields are identified because number of elemenent in the form exceed the threshold",
|
||||
prefs: [["extensions.formautofill.heuristics.visibilityCheckThreshold", 1]],
|
||||
"Number of form elements exceeds the threshold (interactivityCheckMode is set to visibility).",
|
||||
prefs: [
|
||||
["extensions.formautofill.heuristics.visibilityCheckThreshold", 1],
|
||||
[
|
||||
"extensions.formautofill.heuristics.interactivityCheckMode",
|
||||
"visibility",
|
||||
],
|
||||
],
|
||||
fixtureData: `
|
||||
<html>
|
||||
<body>
|
||||
|
@ -87,9 +108,9 @@ add_heuristic_tests([
|
|||
<input type="text" id="name" autocomplete="name" />
|
||||
<input type="text" id="tel" autocomplete="tel" />
|
||||
<input type="text" id="email" autocomplete="email" />
|
||||
<input type="text" id="country" autocomplete="country" hidden />
|
||||
<input type="text" id="postal-code" autocomplete="postal-code" style="display:none" />
|
||||
<input type="text" id="address-line1" autocomplete="address-line1" style="opacity:0" />
|
||||
<input type="text" id="country" autocomplete="country" disabled />
|
||||
<input type="text" id="postal-code" autocomplete="postal-code" hidden />
|
||||
<input type="text" id="address-line1" autocomplete="address-line1" style="display:none" />
|
||||
<div style="visibility: hidden">
|
||||
<input type="text" id="address-line2" autocomplete="address-line2" />
|
||||
</div>
|
||||
|
@ -106,7 +127,7 @@ add_heuristic_tests([
|
|||
{ fieldName: "name" },
|
||||
{ fieldName: "tel" },
|
||||
{ fieldName: "email" },
|
||||
{ fieldName: "address-line1" },
|
||||
{ fieldName: "country" },
|
||||
{ fieldName: "address-line2" },
|
||||
],
|
||||
},
|
||||
|
|
|
@ -0,0 +1,179 @@
|
|||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
/* global add_heuristic_tests */
|
||||
|
||||
"use strict";
|
||||
|
||||
add_heuristic_tests([
|
||||
{
|
||||
description: "All visual fields are considered focusable.",
|
||||
prefs: [
|
||||
[
|
||||
"extensions.formautofill.heuristics.interactivityCheckMode",
|
||||
"focusability",
|
||||
],
|
||||
],
|
||||
fixtureData: `
|
||||
<html>
|
||||
<body>
|
||||
<form>
|
||||
<input type="text" id="name" autocomplete="name" />
|
||||
<input type="text" id="tel" autocomplete="tel" />
|
||||
<input type="text" id="email" autocomplete="email"/>
|
||||
<select id="country" autocomplete="country">
|
||||
<option value="United States">United States</option>
|
||||
</select>
|
||||
<input type="text" id="postal-code" autocomplete="postal-code"/>
|
||||
<input type="text" id="address-line1" autocomplete="address-line1" />
|
||||
<div>
|
||||
<input type="text" id="address-line2" autocomplete="address-line2" />
|
||||
</div>
|
||||
</form>
|
||||
</form>
|
||||
</body>
|
||||
</html>
|
||||
`,
|
||||
expectedResult: [
|
||||
{
|
||||
default: {
|
||||
reason: "autocomplete",
|
||||
},
|
||||
fields: [
|
||||
{ fieldName: "name" },
|
||||
{ fieldName: "tel" },
|
||||
{ fieldName: "email" },
|
||||
{ fieldName: "country" },
|
||||
{ fieldName: "postal-code" },
|
||||
{ fieldName: "address-line1" },
|
||||
{ fieldName: "address-line2" },
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
// ignore opacity (see Bug 1835852),
|
||||
description:
|
||||
"Invisible fields with style.opacity=0 set are considered focusable.",
|
||||
prefs: [
|
||||
[
|
||||
"extensions.formautofill.heuristics.interactivityCheckMode",
|
||||
"focusability",
|
||||
],
|
||||
],
|
||||
fixtureData: `
|
||||
<html>
|
||||
<body>
|
||||
<form>
|
||||
<input type="text" id="name" autocomplete="name" style="opacity:0" />
|
||||
<input type="text" id="tel" autocomplete="tel" />
|
||||
<input type="text" id="email" autocomplete="email" style="opacity:0"/>
|
||||
<select id="country" autocomplete="country">
|
||||
<option value="United States">United States</option>
|
||||
</select>
|
||||
<input type="text" id="postal-code" autocomplete="postal-code" />
|
||||
<input type="text" id="address-line1" autocomplete="address-line1" />
|
||||
<div>
|
||||
<input type="text" id="address-line2" autocomplete="address-line2" />
|
||||
</div>
|
||||
</form>
|
||||
</form>
|
||||
</body>
|
||||
</html>
|
||||
`,
|
||||
expectedResult: [
|
||||
{
|
||||
default: {
|
||||
reason: "autocomplete",
|
||||
},
|
||||
fields: [
|
||||
{ fieldName: "name" },
|
||||
{ fieldName: "tel" },
|
||||
{ fieldName: "email" },
|
||||
{ fieldName: "country" },
|
||||
{ fieldName: "postal-code" },
|
||||
{ fieldName: "address-line1" },
|
||||
{ fieldName: "address-line2" },
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
description:
|
||||
"Some fields are considered unfocusable due to their invisibility.",
|
||||
prefs: [
|
||||
[
|
||||
"extensions.formautofill.heuristics.interactivityCheckMode",
|
||||
"focusability",
|
||||
],
|
||||
],
|
||||
fixtureData: `
|
||||
<html>
|
||||
<body>
|
||||
<form>
|
||||
<input type="text" id="name" autocomplete="name" />
|
||||
<input type="text" id="tel" autocomplete="tel" />
|
||||
<input type="text" id="email" autocomplete="email" />
|
||||
<input type="text" id="country" autocomplete="country" />
|
||||
<input type="text" id="postal-code" autocomplete="postal-code" hidden />
|
||||
<input type="text" id="address-line1" autocomplete="address-line1" style="display:none" />
|
||||
<div style="visibility: hidden">
|
||||
<input type="text" id="address-line2" autocomplete="address-line2" />
|
||||
</div>
|
||||
</form>
|
||||
</body>
|
||||
</html>
|
||||
`,
|
||||
expectedResult: [
|
||||
{
|
||||
default: {
|
||||
reason: "autocomplete",
|
||||
},
|
||||
fields: [
|
||||
{ fieldName: "name" },
|
||||
{ fieldName: "tel" },
|
||||
{ fieldName: "email" },
|
||||
{ fieldName: "country" },
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
description: `Disabled field and field with tabindex="-1" is considered unfocusable`,
|
||||
prefs: [
|
||||
[
|
||||
"extensions.formautofill.heuristics.interactivityCheckMode",
|
||||
"focusability",
|
||||
],
|
||||
],
|
||||
fixtureData: `
|
||||
<html>
|
||||
<body>
|
||||
<form>
|
||||
<input type="text" id="name" autocomplete="name" />
|
||||
<input type="text" id="tel" autocomplete="tel" />
|
||||
<input type="text" id="email" autocomplete="email" />
|
||||
<input type="text" id="country" autocomplete="country" disabled/>
|
||||
<input type="text" id="postal-code" autocomplete="postal-code" tabindex="-1"/>
|
||||
<input type="text" id="address-line1" autocomplete="address-line1" />
|
||||
<input type="text" id="address-line2" autocomplete="address-line2" />
|
||||
</form>
|
||||
</body>
|
||||
</html>
|
||||
`,
|
||||
expectedResult: [
|
||||
{
|
||||
default: {
|
||||
reason: "autocomplete",
|
||||
},
|
||||
fields: [
|
||||
{ fieldName: "name" },
|
||||
{ fieldName: "tel" },
|
||||
{ fieldName: "email" },
|
||||
{ fieldName: "address-line1" },
|
||||
{ fieldName: "address-line2" },
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
]);
|
|
@ -71,6 +71,24 @@ region-name-tw = Taiwan
|
|||
L10nRegistry.getInstance().registerSources([mockSource]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Mock the return value of Services.focus.elementIsFocusable
|
||||
* since a field's focusability can't be tested in a unit test.
|
||||
*/
|
||||
(function ignoreAFieldsFocusability() {
|
||||
let stub = sinon.stub(Services, "focus").get(() => {
|
||||
return {
|
||||
elementIsFocusable() {
|
||||
return true;
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
registerCleanupFunction(() => {
|
||||
stub.restore();
|
||||
});
|
||||
})();
|
||||
|
||||
do_get_profile();
|
||||
|
||||
const EXTENSION_ID = "formautofill@mozilla.org";
|
||||
|
|
|
@ -421,39 +421,6 @@ const TESTCASES = [
|
|||
"cc-exp-year": "25",
|
||||
},
|
||||
},
|
||||
{
|
||||
description:
|
||||
"Form with hidden input and visible input that share the same autocomplete attribute",
|
||||
document: `<form>
|
||||
<input id="hidden-cc" autocomplete="cc-number" hidden>
|
||||
<input id="hidden-cc-2" autocomplete="cc-number" style="display:none">
|
||||
<input id="visible-cc" autocomplete="cc-number">
|
||||
<input id="hidden-name" autocomplete="cc-name" hidden>
|
||||
<input id="hidden-name-2" autocomplete="cc-name" style="display:none">
|
||||
<input id="visible-name" autocomplete="cc-name">
|
||||
<input id="cc-exp-month" autocomplete="cc-exp-month">
|
||||
<input id="cc-exp-year" autocomplete="cc-exp-year">
|
||||
</form>`,
|
||||
focusedInputId: "visible-cc",
|
||||
profileData: {
|
||||
guid: "123",
|
||||
"cc-number": "4111111111111111",
|
||||
"cc-name": "test name",
|
||||
"cc-exp-month": 6,
|
||||
"cc-exp-year": 25,
|
||||
},
|
||||
expectedResult: {
|
||||
guid: "123",
|
||||
"visible-cc": "4111111111111111",
|
||||
"visible-name": "test name",
|
||||
"cc-exp-month": "06",
|
||||
"cc-exp-year": "25",
|
||||
"hidden-cc": undefined,
|
||||
"hidden-cc-2": undefined,
|
||||
"hidden-name": undefined,
|
||||
"hidden-name-2": undefined,
|
||||
},
|
||||
},
|
||||
{
|
||||
description:
|
||||
"Fill credit card fields in a form where the value property is being used as a placeholder for cardholder name",
|
||||
|
|
|
@ -3986,6 +3986,9 @@ pref("extensions.formautofill.creditCards.heuristics.fathom.testConfidence", "0"
|
|||
|
||||
pref("extensions.formautofill.firstTimeUse", true);
|
||||
pref("extensions.formautofill.loglevel", "Warn");
|
||||
// The interactivityCheckMode pref is only temporary.
|
||||
// It will be removed when we decide to only support the `focusability` mode
|
||||
pref("extensions.formautofill.heuristics.interactivityCheckMode", "focusability");
|
||||
|
||||
pref("toolkit.osKeyStore.loglevel", "Warn");
|
||||
|
||||
|
|
|
@ -27,9 +27,10 @@ const IOS_DEFAULT_PREFERENCES = {
|
|||
"extensions.formautofill.addresses.ignoreAutocompleteOff": true,
|
||||
"extensions.formautofill.heuristics.enabled": true,
|
||||
"extensions.formautofill.section.enabled": true,
|
||||
// WebKit doesn't support the checkVisibility API, setting the threshold value to 0 to esnure
|
||||
// WebKit doesn't support the checkVisibility API, setting the threshold value to 0 to ensure
|
||||
// `IsFieldVisible` function doesn't use it
|
||||
"extensions.formautofill.heuristics.visibilityCheckThreshold": 0,
|
||||
"extensions.formautofill.heuristics.interactivityCheckMode": "focusability",
|
||||
"extensions.formautofill.focusOnAutofill": false,
|
||||
};
|
||||
|
||||
|
|
|
@ -123,10 +123,23 @@ export const OSKeyStore = withNotImplementedError({
|
|||
ensureLoggedIn: () => true,
|
||||
});
|
||||
|
||||
// Checks an element's focusability and accessibility via keyboard navigation
|
||||
const checkFocusability = element => {
|
||||
return (
|
||||
!element.disabled &&
|
||||
!element.hidden &&
|
||||
element.style.display != "none" &&
|
||||
element.tabIndex != "-1"
|
||||
);
|
||||
};
|
||||
|
||||
// Define mock for Services
|
||||
// NOTE: Services is a global so we need to attach it to the window
|
||||
// eslint-disable-next-line no-shadow
|
||||
export const Services = withNotImplementedError({
|
||||
focus: withNotImplementedError({
|
||||
elementIsFocusable: checkFocusability,
|
||||
}),
|
||||
intl: withNotImplementedError({
|
||||
getAvailableLocaleDisplayNames: () => [],
|
||||
getRegionDisplayNames: () => [],
|
||||
|
|
|
@ -550,25 +550,7 @@ export const FormAutofillHeuristics = {
|
|||
* all sections within its field details in the form.
|
||||
*/
|
||||
getFormInfo(form) {
|
||||
let elements = Array.from(form.elements).filter(element =>
|
||||
lazy.FormAutofillUtils.isCreditCardOrAddressFieldType(element)
|
||||
);
|
||||
|
||||
// Due to potential performance impact while running visibility check on
|
||||
// a large amount of elements, a comprehensive visibility check
|
||||
// (considering opacity and CSS visibility) is only applied when the number
|
||||
// of eligible elements is below a certain threshold.
|
||||
const runVisiblityCheck =
|
||||
elements.length < lazy.FormAutofillUtils.visibilityCheckThreshold;
|
||||
if (!runVisiblityCheck) {
|
||||
lazy.log.debug(
|
||||
`Skip running visibility check, because of too many elements (${elements.length})`
|
||||
);
|
||||
}
|
||||
|
||||
elements = elements.filter(element =>
|
||||
lazy.FormAutofillUtils.isFieldVisible(element, runVisiblityCheck)
|
||||
);
|
||||
let elements = this.getFormElements(form);
|
||||
|
||||
const scanner = new lazy.FieldScanner(elements, element =>
|
||||
this.inferFieldInfo(element, elements)
|
||||
|
@ -617,6 +599,44 @@ export const FormAutofillHeuristics = {
|
|||
);
|
||||
},
|
||||
|
||||
/**
|
||||
* Get form elements that are of credit card or address type and filtered by either
|
||||
* visibility or focusability - depending on the interactivity mode (default = focusability)
|
||||
* This distinction is only temporary as we want to test switching from visibility mode
|
||||
* to focusability mode. The visibility mode is then removed.
|
||||
*
|
||||
* @param {HTMLElement} form
|
||||
* @returns {Array<HTMLElement>} elements filtered by interactivity mode (visibility or focusability)
|
||||
*/
|
||||
getFormElements(form) {
|
||||
let elements = Array.from(form.elements).filter(element =>
|
||||
lazy.FormAutofillUtils.isCreditCardOrAddressFieldType(element)
|
||||
);
|
||||
const interactivityMode = lazy.FormAutofillUtils.interactivityCheckMode;
|
||||
|
||||
if (interactivityMode == "focusability") {
|
||||
elements = elements.filter(element =>
|
||||
lazy.FormAutofillUtils.isFieldFocusable(element)
|
||||
);
|
||||
} else if (interactivityMode == "visibility") {
|
||||
// Due to potential performance impact while running visibility check on
|
||||
// a large amount of elements, a comprehensive visibility check
|
||||
// (considering opacity and CSS visibility) is only applied when the number
|
||||
// of eligible elements is below a certain threshold.
|
||||
const runVisiblityCheck =
|
||||
elements.length < lazy.FormAutofillUtils.visibilityCheckThreshold;
|
||||
if (!runVisiblityCheck) {
|
||||
lazy.log.debug(
|
||||
`Skip running visibility check, because of too many elements (${elements.length})`
|
||||
);
|
||||
}
|
||||
elements = elements.filter(element =>
|
||||
lazy.FormAutofillUtils.isFieldVisible(element, runVisiblityCheck)
|
||||
);
|
||||
}
|
||||
return elements;
|
||||
},
|
||||
|
||||
/**
|
||||
* The result is an array contains the sections with its belonging field details.
|
||||
*
|
||||
|
|
|
@ -438,7 +438,7 @@ FormAutofillUtils = {
|
|||
* @returns {boolean} true if the element is visible
|
||||
*/
|
||||
isFieldVisible(element, visibilityCheck = true) {
|
||||
if (visibilityCheck) {
|
||||
if (visibilityCheck && element.checkVisibility) {
|
||||
return element.checkVisibility({
|
||||
checkOpacity: true,
|
||||
checkVisibilityCSS: true,
|
||||
|
@ -448,6 +448,23 @@ FormAutofillUtils = {
|
|||
return !element.hidden && element.style.display != "none";
|
||||
},
|
||||
|
||||
/**
|
||||
* Determines if an element is focusable
|
||||
* and accessible via keyboard navigation or not.
|
||||
*
|
||||
* @param {HTMLElement} element
|
||||
*
|
||||
* @returns {bool} true if the element is focusable and accessible
|
||||
*/
|
||||
isFieldFocusable(element) {
|
||||
return (
|
||||
// The Services.focus.elementIsFocusable API considers elements with
|
||||
// tabIndex="-1" set as focusable. But since they are not accessible
|
||||
// via keyboard navigation we treat them as non-interactive
|
||||
Services.focus.elementIsFocusable(element, 0) && element.tabIndex != "-1"
|
||||
);
|
||||
},
|
||||
|
||||
/**
|
||||
* Determines if an element is eligible to be used by credit card or address autofill.
|
||||
*
|
||||
|
@ -1226,6 +1243,13 @@ XPCOMUtils.defineLazyPreferenceGetter(
|
|||
200
|
||||
);
|
||||
|
||||
XPCOMUtils.defineLazyPreferenceGetter(
|
||||
FormAutofillUtils,
|
||||
"interactivityCheckMode",
|
||||
"extensions.formautofill.heuristics.interactivityCheckMode",
|
||||
"focusability"
|
||||
);
|
||||
|
||||
// This is only used in iOS
|
||||
XPCOMUtils.defineLazyPreferenceGetter(
|
||||
FormAutofillUtils,
|
||||
|
|
Загрузка…
Ссылка в новой задаче