From 1f12b78d9bac4a1d9b6bad18dc2ef0593bed34a3 Mon Sep 17 00:00:00 2001 From: Deepankar Bajpeyi Date: Thu, 8 Aug 2024 18:57:37 +0900 Subject: [PATCH] [Form] re-decorate input if there is 1 unknown username (#619) This PR adds logic in categorizeInputs to attempt categorizing unknown input fields to username, based on already categorized password (and other) input fields. PS: Creates mis-categorisation in chase_2fa-verification-step.html, where there is a one time passcode field, which is very similar semantically to a username field. I haven't figured out a way to filter that case out. Given that we don't really break that site, and still fix two new forms (dplinfo.ssb-ag.de and secure.bankofamerica.com), I'd consider this a win. --- dist/autofill-debug.js | 34 ++++++++- dist/autofill.js | 31 +++++++- integration-test/helpers/mocks.js | 2 +- .../helpers/pages/unknownUsernameLoginPage.js | 76 +++++++++++++++++++ integration-test/pages/index.html | 1 + .../pages/unknown-username-login.html | 21 +++++ .../tests/login-form.macos.spec.js | 51 ++++++++++++- src/Form/Form.js | 32 ++++++++ src/Form/input-classifiers.test.js | 3 + src/Settings.js | 3 +- src/Settings.test.js | 1 + .../__generated__/validators-ts.ts | 4 + .../__generated__/validators.zod.js | 3 +- .../schemas/autofill-settings.json | 4 + .../Resources/assets/autofill-debug.js | 34 ++++++++- swift-package/Resources/assets/autofill.js | 31 +++++++- test-forms/index.json | 2 +- 17 files changed, 322 insertions(+), 11 deletions(-) create mode 100644 integration-test/helpers/pages/unknownUsernameLoginPage.js create mode 100644 integration-test/pages/unknown-username-login.html diff --git a/dist/autofill-debug.js b/dist/autofill-debug.js index c7025f0fe..318ad3001 100644 --- a/dist/autofill-debug.js +++ b/dist/autofill-debug.js @@ -10099,6 +10099,31 @@ class Form { return; } } + + // Try to analyse the form inputs and categorize lone unknown input to username type, in login forms. + if (this.canCategorizeUnknownUsername()) { + const credentialInputs = [...this.inputs.credentials]; + const hasUsername = credentialInputs.some(input => (0, _matching.getInputSubtype)(input) === 'username'); + const hasIdentitiesOrCreditCards = this.inputs.identities.size > 0 || this.inputs.creditCards.size > 0; + const hasLoneUnknownInput = this.inputs.unknown.size === 1; + + // Categorise if the form: + // 1. doesn't have a username field, + // 2. doesn't have identities or credit cards, otherwise it's likely to be a more complex form. Categorising then will cause bad UX. + // 3. has exactly one unknown input, and + // 4. the form is a login form. + if (!hasUsername && !hasIdentitiesOrCreditCards && hasLoneUnknownInput && this.isLogin) { + const [unknownInput] = [...this.inputs.unknown]; + const passwordInputs = credentialInputs.filter(( /** @type {HTMLInputElement} */input) => (0, _matching.getInputSubtype)(input) === 'password'); + const inputSelector = this.matching.cssSelector('formInputsSelectorWithoutSelect'); + if (passwordInputs.length > 0 && unknownInput.matches?.(inputSelector)) { + unknownInput.setAttribute(ATTR_INPUT_TYPE, 'credentials.username'); + this.decorateInput(unknownInput); + this.inputs.credentials.add(unknownInput); + this.inputs.unknown.delete(unknownInput); + } + } + } this.initialScanComplete = true; // Observe only if the container isn't the body, to avoid performance overloads @@ -10106,6 +10131,9 @@ class Form { this.mutObs.observe(this.form, this.mutObsConfig); } } + canCategorizeUnknownUsername() { + return this.device.settings.featureToggles.unknown_username_categorization; + } get submitButtons() { const selector = this.matching.cssSelector('submitButtonSelector'); const allButtons = /** @type {HTMLElement[]} */(0, _autofillUtils.findEnclosedElements)(this.form, selector); @@ -14983,7 +15011,8 @@ class Settings { inputType_identities: false, inputType_credentials: false, inputType_creditCards: false, - inlineIcon_credentials: false + inlineIcon_credentials: false, + unknown_username_categorization: false }, /** @type {AvailableInputTypes} */ availableInputTypes: { @@ -17889,7 +17918,8 @@ const autofillFeatureTogglesSchema = exports.autofillFeatureTogglesSchema = _zod password_generation: _zod.z.boolean().optional(), credentials_saving: _zod.z.boolean().optional(), inlineIcon_credentials: _zod.z.boolean().optional(), - third_party_credentials_provider: _zod.z.boolean().optional() + third_party_credentials_provider: _zod.z.boolean().optional(), + unknown_username_categorization: _zod.z.boolean().optional() }); const getAutofillDataRequestSchema = exports.getAutofillDataRequestSchema = _zod.z.object({ generatedPassword: generatedPasswordSchema.optional(), diff --git a/dist/autofill.js b/dist/autofill.js index 0f2651de8..420fcd339 100644 --- a/dist/autofill.js +++ b/dist/autofill.js @@ -5933,6 +5933,31 @@ class Form { return; } } + + // Try to analyse the form inputs and categorize lone unknown input to username type, in login forms. + if (this.canCategorizeUnknownUsername()) { + const credentialInputs = [...this.inputs.credentials]; + const hasUsername = credentialInputs.some(input => (0, _matching.getInputSubtype)(input) === 'username'); + const hasIdentitiesOrCreditCards = this.inputs.identities.size > 0 || this.inputs.creditCards.size > 0; + const hasLoneUnknownInput = this.inputs.unknown.size === 1; + + // Categorise if the form: + // 1. doesn't have a username field, + // 2. doesn't have identities or credit cards, otherwise it's likely to be a more complex form. Categorising then will cause bad UX. + // 3. has exactly one unknown input, and + // 4. the form is a login form. + if (!hasUsername && !hasIdentitiesOrCreditCards && hasLoneUnknownInput && this.isLogin) { + const [unknownInput] = [...this.inputs.unknown]; + const passwordInputs = credentialInputs.filter(( /** @type {HTMLInputElement} */input) => (0, _matching.getInputSubtype)(input) === 'password'); + const inputSelector = this.matching.cssSelector('formInputsSelectorWithoutSelect'); + if (passwordInputs.length > 0 && unknownInput.matches?.(inputSelector)) { + unknownInput.setAttribute(ATTR_INPUT_TYPE, 'credentials.username'); + this.decorateInput(unknownInput); + this.inputs.credentials.add(unknownInput); + this.inputs.unknown.delete(unknownInput); + } + } + } this.initialScanComplete = true; // Observe only if the container isn't the body, to avoid performance overloads @@ -5940,6 +5965,9 @@ class Form { this.mutObs.observe(this.form, this.mutObsConfig); } } + canCategorizeUnknownUsername() { + return this.device.settings.featureToggles.unknown_username_categorization; + } get submitButtons() { const selector = this.matching.cssSelector('submitButtonSelector'); const allButtons = /** @type {HTMLElement[]} */(0, _autofillUtils.findEnclosedElements)(this.form, selector); @@ -10817,7 +10845,8 @@ class Settings { inputType_identities: false, inputType_credentials: false, inputType_creditCards: false, - inlineIcon_credentials: false + inlineIcon_credentials: false, + unknown_username_categorization: false }, /** @type {AvailableInputTypes} */ availableInputTypes: { diff --git a/integration-test/helpers/mocks.js b/integration-test/helpers/mocks.js index 067e48cd6..a39239cfc 100644 --- a/integration-test/helpers/mocks.js +++ b/integration-test/helpers/mocks.js @@ -26,7 +26,7 @@ export const constants = { 'shadowDom': `${privacyTestPagesPrefix}/shadow-dom.html`, 'selectInput': `${localPagesPrefix}/select-input.html`, 'shadowInputsLogin': `${localPagesPrefix}/shadow-inputs-login.html`, - 'loginWithPasswordReset': `${localPagesPrefix}/login-with-password-reset.html` + 'unknownUsernameLogin': `${localPagesPrefix}/unknown-username-login.html` }, forms: { 'www_ulisboa_pt_login.html': `${testFormsPrefix}/www_ulisboa_pt_login.html` diff --git a/integration-test/helpers/pages/unknownUsernameLoginPage.js b/integration-test/helpers/pages/unknownUsernameLoginPage.js new file mode 100644 index 000000000..0b37e49e0 --- /dev/null +++ b/integration-test/helpers/pages/unknownUsernameLoginPage.js @@ -0,0 +1,76 @@ +import {constants} from '../mocks.js' +import {expect} from '@playwright/test' + +/** + * A wrapper around interactions for `integration-test/pages/select-input.html` + * + * @param {import("@playwright/test").Page} page + */ +export function unknownUsernameLoginPage (page) { + class UnknownUsernameLoginPage { + /** + * @param {keyof typeof constants.pages} [to] + * @return {Promise} + */ + async navigate (to = 'unknownUsernameLogin') { + await page.goto(constants.pages[to]) + } + + /** + * + * @param {string} name + */ + async autofillWithUnknownField (name) { + await page.locator('#unknown').click() + const button = await page.waitForSelector(`button:has-text("${name}")`) + await button.click({force: true}) + await page.locator('#unknown').click() + } + + /** + * + * @param {string} name + */ + async autofillWithPasswordField (name) { + await page.locator('#password').click() + const button = await page.waitForSelector(`button:has-text("${name}")`) + await button.click({force: true}) + await page.locator('#password').click() + } + + async assertUnknownFieldIsUsername () { + const input = await page.locator('#unknown') + const inputType = await input.getAttribute('data-ddg-inputtype') + await expect(inputType).toBe('credentials.username') + } + + /** + * @param {string} value + * @return {Promise} + */ + async assertUnknownFieldFilled (value) { + const unknown = page.locator('#unknown') + await expect(unknown).toHaveValue(value) + } + /** + * @param {string} value + * @return {Promise} + */ + async assertPasswordFilled (value) { + const passwordField = page.locator('#password') + await expect(passwordField).toHaveValue(value) + } + + /** + * @param {string} username + * @param {string} password + * @return {Promise} + */ + async assertCredentialsFilled (username, password) { + await this.assertUnknownFieldFilled(username) + await this.assertPasswordFilled((password)) + } + } + + return new UnknownUsernameLoginPage() +} diff --git a/integration-test/pages/index.html b/integration-test/pages/index.html index 033a02bcb..d101c376f 100644 --- a/integration-test/pages/index.html +++ b/integration-test/pages/index.html @@ -21,6 +21,7 @@
  • signup.html
  • select-input.html
  • shadow-inputs-login
  • +
  • unknown-username
  • diff --git a/integration-test/pages/unknown-username-login.html b/integration-test/pages/unknown-username-login.html new file mode 100644 index 000000000..66ca4b60d --- /dev/null +++ b/integration-test/pages/unknown-username-login.html @@ -0,0 +1,21 @@ + + + + Ambiguous username field +

    + This login form has one vague username field, and a non-ambiguous password field. + Based on the presence of a password field, the unknown field should be classified + as a username field. +

    + + +
    + + + + + + +
    + + diff --git a/integration-test/tests/login-form.macos.spec.js b/integration-test/tests/login-form.macos.spec.js index 62ad0582b..fdb720d61 100644 --- a/integration-test/tests/login-form.macos.spec.js +++ b/integration-test/tests/login-form.macos.spec.js @@ -7,6 +7,7 @@ import {loginPage} from '../helpers/pages/loginPage.js' import {overlayPage} from '../helpers/pages/overlayPage.js' import {genericPage} from '../helpers/pages/genericPage.js' import { shadowInputsLoginPage } from '../helpers/pages/shadowInputsLoginPage.js' +import { unknownUsernameLoginPage } from '../helpers/pages/unknownUsernameLoginPage.js' /** * Tests for various auto-fill scenarios on macos @@ -18,9 +19,11 @@ const password = '123456' /** * @param {import("@playwright/test").Page} page + * @param {Partial} featureToggles */ -async function mocks (page) { +async function mocks (page, featureToggles = {}) { await createWebkitMocks() + .withFeatureToggles(featureToggles) .withAvailableInputTypes(createAvailableInputTypes()) .withCredentials({ id: '01', @@ -105,6 +108,52 @@ test.describe('Auto-fill a login form on macOS', () => { }) }) + test.describe('when there is a single unknown username field, and one password field', () => { + test('the unknown field is a username', async ({page}) => { + await forwardConsoleMessages(page) + await mocks(page, { + unknown_username_categorization: true + }) + await createAutofillScript() + .replaceAll(macosContentScopeReplacements({})) + .platform('macos') + .applyTo(page) + const login = unknownUsernameLoginPage(page) + await login.navigate() + await page.waitForTimeout(10) + await login.assertUnknownFieldIsUsername() + }) + + test('unknown field is autofilled when clicking into password', async ({page}) => { + await forwardConsoleMessages(page) + await mocks(page, { + unknown_username_categorization: true + }) + await createAutofillScript() + .replaceAll(macosContentScopeReplacements({})) + .platform('macos') + .applyTo(page) + const login = unknownUsernameLoginPage(page) + await login.navigate() + await login.autofillWithPasswordField(personalAddress) + await login.assertCredentialsFilled(personalAddress, password) + }) + test('password field is autofilled when clicking into unknown field', async ({page}) => { + await forwardConsoleMessages(page) + await mocks(page, { + unknown_username_categorization: true + }) + await createAutofillScript() + .replaceAll(macosContentScopeReplacements({})) + .platform('macos') + .applyTo(page) + const login = unknownUsernameLoginPage(page) + await login.navigate() + await login.autofillWithPasswordField(personalAddress) + await login.assertCredentialsFilled(personalAddress, password) + }) + }) + test.describe('without getAvailableInputTypes API', () => { test('with in-page HTMLTooltip', async ({page}) => { await testLoginPage(page) diff --git a/src/Form/Form.js b/src/Form/Form.js index e8bd13a5d..89f0ab92b 100644 --- a/src/Form/Form.js +++ b/src/Form/Form.js @@ -427,6 +427,34 @@ class Form { return } } + + // Try to analyse the form inputs and categorize lone unknown input to username type, in login forms. + if (this.canCategorizeUnknownUsername()) { + const credentialInputs = [...this.inputs.credentials] + const hasUsername = credentialInputs.some(input => getInputSubtype(input) === 'username') + const hasIdentitiesOrCreditCards = this.inputs.identities.size > 0 || this.inputs.creditCards.size > 0 + const hasLoneUnknownInput = this.inputs.unknown.size === 1 + + // Categorise if the form: + // 1. doesn't have a username field, + // 2. doesn't have identities or credit cards, otherwise it's likely to be a more complex form. Categorising then will cause bad UX. + // 3. has exactly one unknown input, and + // 4. the form is a login form. + if (!hasUsername && !hasIdentitiesOrCreditCards && hasLoneUnknownInput && this.isLogin) { + const [unknownInput] = [...this.inputs.unknown] + const passwordInputs = credentialInputs.filter( + (/** @type {HTMLInputElement} */ input) => getInputSubtype(input) === 'password' + ) + const inputSelector = this.matching.cssSelector('formInputsSelectorWithoutSelect') + if (passwordInputs.length > 0 && unknownInput.matches?.(inputSelector)) { + unknownInput.setAttribute(ATTR_INPUT_TYPE, 'credentials.username') + this.decorateInput(unknownInput) + this.inputs.credentials.add(unknownInput) + this.inputs.unknown.delete(unknownInput) + } + } + } + this.initialScanComplete = true // Observe only if the container isn't the body, to avoid performance overloads @@ -435,6 +463,10 @@ class Form { } } + canCategorizeUnknownUsername () { + return this.device.settings.featureToggles.unknown_username_categorization + } + get submitButtons () { const selector = this.matching.cssSelector('submitButtonSelector') const allButtons = /** @type {HTMLElement[]} */(findEnclosedElements(this.form, selector)) diff --git a/src/Form/input-classifiers.test.js b/src/Form/input-classifiers.test.js index 64ab83dc1..d5ef1d9e5 100644 --- a/src/Form/input-classifiers.test.js +++ b/src/Form/input-classifiers.test.js @@ -203,6 +203,9 @@ describe.each(testCases)('Test $html fields', (testCase) => { const deviceInterface = InterfacePrototype.default() const availableInputTypes = createAvailableInputTypes({credentials: {username: true, password: true}}) deviceInterface.settings.setAvailableInputTypes(availableInputTypes) + deviceInterface.settings.setFeatureToggles({ + unknown_username_categorization: true + }) const scanner = createScanner(deviceInterface) scanner.findEligibleInputs(document) diff --git a/src/Settings.js b/src/Settings.js index fae9436d9..b271c1f89 100644 --- a/src/Settings.js +++ b/src/Settings.js @@ -328,7 +328,8 @@ export class Settings { inputType_identities: false, inputType_credentials: false, inputType_creditCards: false, - inlineIcon_credentials: false + inlineIcon_credentials: false, + unknown_username_categorization: false }, /** @type {AvailableInputTypes} */ availableInputTypes: { diff --git a/src/Settings.test.js b/src/Settings.test.js index 9c821470b..2e7061149 100644 --- a/src/Settings.test.js +++ b/src/Settings.test.js @@ -141,6 +141,7 @@ describe('Settings', () => { "inputType_creditCards": false, "inputType_identities": false, "password_generation": false, + "unknown_username_categorization": false, } `) }) diff --git a/src/deviceApiCalls/__generated__/validators-ts.ts b/src/deviceApiCalls/__generated__/validators-ts.ts index dad3dbd23..72dd3ade8 100644 --- a/src/deviceApiCalls/__generated__/validators-ts.ts +++ b/src/deviceApiCalls/__generated__/validators-ts.ts @@ -602,6 +602,10 @@ export interface AutofillFeatureToggles { credentials_saving?: boolean; inlineIcon_credentials?: boolean; third_party_credentials_provider?: boolean; + /** + * If true, we will attempt categorizaing username, based on the rest of the input fields in the form + */ + unknown_username_categorization?: boolean; } export interface GetAliasParams { requiresUserPermission: boolean; diff --git a/src/deviceApiCalls/__generated__/validators.zod.js b/src/deviceApiCalls/__generated__/validators.zod.js index c8952ca9f..48736d4a5 100644 --- a/src/deviceApiCalls/__generated__/validators.zod.js +++ b/src/deviceApiCalls/__generated__/validators.zod.js @@ -204,7 +204,8 @@ export const autofillFeatureTogglesSchema = z.object({ password_generation: z.boolean().optional(), credentials_saving: z.boolean().optional(), inlineIcon_credentials: z.boolean().optional(), - third_party_credentials_provider: z.boolean().optional() + third_party_credentials_provider: z.boolean().optional(), + unknown_username_categorization: z.boolean().optional() }); export const getAutofillDataRequestSchema = z.object({ diff --git a/src/deviceApiCalls/schemas/autofill-settings.json b/src/deviceApiCalls/schemas/autofill-settings.json index b353801b0..aea548a60 100644 --- a/src/deviceApiCalls/schemas/autofill-settings.json +++ b/src/deviceApiCalls/schemas/autofill-settings.json @@ -37,6 +37,10 @@ }, "third_party_credentials_provider": { "type": "boolean" + }, + "unknown_username_categorization": { + "type": "boolean", + "description": "If true, we will attempt categorizaing username, based on the rest of the input fields in the form" } }, "required": [] diff --git a/swift-package/Resources/assets/autofill-debug.js b/swift-package/Resources/assets/autofill-debug.js index c7025f0fe..318ad3001 100644 --- a/swift-package/Resources/assets/autofill-debug.js +++ b/swift-package/Resources/assets/autofill-debug.js @@ -10099,6 +10099,31 @@ class Form { return; } } + + // Try to analyse the form inputs and categorize lone unknown input to username type, in login forms. + if (this.canCategorizeUnknownUsername()) { + const credentialInputs = [...this.inputs.credentials]; + const hasUsername = credentialInputs.some(input => (0, _matching.getInputSubtype)(input) === 'username'); + const hasIdentitiesOrCreditCards = this.inputs.identities.size > 0 || this.inputs.creditCards.size > 0; + const hasLoneUnknownInput = this.inputs.unknown.size === 1; + + // Categorise if the form: + // 1. doesn't have a username field, + // 2. doesn't have identities or credit cards, otherwise it's likely to be a more complex form. Categorising then will cause bad UX. + // 3. has exactly one unknown input, and + // 4. the form is a login form. + if (!hasUsername && !hasIdentitiesOrCreditCards && hasLoneUnknownInput && this.isLogin) { + const [unknownInput] = [...this.inputs.unknown]; + const passwordInputs = credentialInputs.filter(( /** @type {HTMLInputElement} */input) => (0, _matching.getInputSubtype)(input) === 'password'); + const inputSelector = this.matching.cssSelector('formInputsSelectorWithoutSelect'); + if (passwordInputs.length > 0 && unknownInput.matches?.(inputSelector)) { + unknownInput.setAttribute(ATTR_INPUT_TYPE, 'credentials.username'); + this.decorateInput(unknownInput); + this.inputs.credentials.add(unknownInput); + this.inputs.unknown.delete(unknownInput); + } + } + } this.initialScanComplete = true; // Observe only if the container isn't the body, to avoid performance overloads @@ -10106,6 +10131,9 @@ class Form { this.mutObs.observe(this.form, this.mutObsConfig); } } + canCategorizeUnknownUsername() { + return this.device.settings.featureToggles.unknown_username_categorization; + } get submitButtons() { const selector = this.matching.cssSelector('submitButtonSelector'); const allButtons = /** @type {HTMLElement[]} */(0, _autofillUtils.findEnclosedElements)(this.form, selector); @@ -14983,7 +15011,8 @@ class Settings { inputType_identities: false, inputType_credentials: false, inputType_creditCards: false, - inlineIcon_credentials: false + inlineIcon_credentials: false, + unknown_username_categorization: false }, /** @type {AvailableInputTypes} */ availableInputTypes: { @@ -17889,7 +17918,8 @@ const autofillFeatureTogglesSchema = exports.autofillFeatureTogglesSchema = _zod password_generation: _zod.z.boolean().optional(), credentials_saving: _zod.z.boolean().optional(), inlineIcon_credentials: _zod.z.boolean().optional(), - third_party_credentials_provider: _zod.z.boolean().optional() + third_party_credentials_provider: _zod.z.boolean().optional(), + unknown_username_categorization: _zod.z.boolean().optional() }); const getAutofillDataRequestSchema = exports.getAutofillDataRequestSchema = _zod.z.object({ generatedPassword: generatedPasswordSchema.optional(), diff --git a/swift-package/Resources/assets/autofill.js b/swift-package/Resources/assets/autofill.js index 0f2651de8..420fcd339 100644 --- a/swift-package/Resources/assets/autofill.js +++ b/swift-package/Resources/assets/autofill.js @@ -5933,6 +5933,31 @@ class Form { return; } } + + // Try to analyse the form inputs and categorize lone unknown input to username type, in login forms. + if (this.canCategorizeUnknownUsername()) { + const credentialInputs = [...this.inputs.credentials]; + const hasUsername = credentialInputs.some(input => (0, _matching.getInputSubtype)(input) === 'username'); + const hasIdentitiesOrCreditCards = this.inputs.identities.size > 0 || this.inputs.creditCards.size > 0; + const hasLoneUnknownInput = this.inputs.unknown.size === 1; + + // Categorise if the form: + // 1. doesn't have a username field, + // 2. doesn't have identities or credit cards, otherwise it's likely to be a more complex form. Categorising then will cause bad UX. + // 3. has exactly one unknown input, and + // 4. the form is a login form. + if (!hasUsername && !hasIdentitiesOrCreditCards && hasLoneUnknownInput && this.isLogin) { + const [unknownInput] = [...this.inputs.unknown]; + const passwordInputs = credentialInputs.filter(( /** @type {HTMLInputElement} */input) => (0, _matching.getInputSubtype)(input) === 'password'); + const inputSelector = this.matching.cssSelector('formInputsSelectorWithoutSelect'); + if (passwordInputs.length > 0 && unknownInput.matches?.(inputSelector)) { + unknownInput.setAttribute(ATTR_INPUT_TYPE, 'credentials.username'); + this.decorateInput(unknownInput); + this.inputs.credentials.add(unknownInput); + this.inputs.unknown.delete(unknownInput); + } + } + } this.initialScanComplete = true; // Observe only if the container isn't the body, to avoid performance overloads @@ -5940,6 +5965,9 @@ class Form { this.mutObs.observe(this.form, this.mutObsConfig); } } + canCategorizeUnknownUsername() { + return this.device.settings.featureToggles.unknown_username_categorization; + } get submitButtons() { const selector = this.matching.cssSelector('submitButtonSelector'); const allButtons = /** @type {HTMLElement[]} */(0, _autofillUtils.findEnclosedElements)(this.form, selector); @@ -10817,7 +10845,8 @@ class Settings { inputType_identities: false, inputType_credentials: false, inputType_creditCards: false, - inlineIcon_credentials: false + inlineIcon_credentials: false, + unknown_username_categorization: false }, /** @type {AvailableInputTypes} */ availableInputTypes: { diff --git a/test-forms/index.json b/test-forms/index.json index e9230084b..472dbc53f 100644 --- a/test-forms/index.json +++ b/test-forms/index.json @@ -496,7 +496,7 @@ { "html": "ua_ctcorpmpc_com-signup.html" }, { "html": "joybird_com.html" }, { "html": "ikea_signup-verification.html", "expectedSubmitFalsePositives": 2 }, - { "html": "chase_2fa-verification-step.html" }, + { "html": "chase_2fa-verification-step.html", "expectedFailures": ["unknown"] }, { "html": "alaskaair_checkout.html", "expectedFailures": ["phone"], "expectedSubmitFalsePositives": 4 }, { "html": "autostrade_login.html" }, { "html": "morningstar_login.html" },