From 7623023c722fff98e1193a5adf32af364ffa8e43 Mon Sep 17 00:00:00 2001 From: Deepankar Bajpeyi Date: Tue, 17 Dec 2024 16:17:54 +0100 Subject: [PATCH 1/2] fix: call credentials flow call first, and then close parent (#723) --- dist/autofill-debug.js | 2 +- dist/autofill.js | 2 +- src/CredentialsImport.js | 2 +- swift-package/Resources/assets/autofill-debug.js | 2 +- swift-package/Resources/assets/autofill.js | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/dist/autofill-debug.js b/dist/autofill-debug.js index 6a9fbe5cd..6c651d6f2 100644 --- a/dist/autofill-debug.js +++ b/dist/autofill-debug.js @@ -7769,8 +7769,8 @@ class CredentialsImport { activeInput?.focus(); } async started() { - this.device.deviceApi.notify(new _deviceApiCalls.CloseAutofillParentCall(null)); this.device.deviceApi.notify(new _deviceApiCalls.StartCredentialsImportFlowCall({})); + this.device.deviceApi.notify(new _deviceApiCalls.CloseAutofillParentCall(null)); } async dismissed() { this.device.deviceApi.notify(new _deviceApiCalls.CredentialsImportFlowPermanentlyDismissedCall(null)); diff --git a/dist/autofill.js b/dist/autofill.js index 04931d74e..b7dbbe96e 100644 --- a/dist/autofill.js +++ b/dist/autofill.js @@ -3406,8 +3406,8 @@ class CredentialsImport { activeInput?.focus(); } async started() { - this.device.deviceApi.notify(new _deviceApiCalls.CloseAutofillParentCall(null)); this.device.deviceApi.notify(new _deviceApiCalls.StartCredentialsImportFlowCall({})); + this.device.deviceApi.notify(new _deviceApiCalls.CloseAutofillParentCall(null)); } async dismissed() { this.device.deviceApi.notify(new _deviceApiCalls.CredentialsImportFlowPermanentlyDismissedCall(null)); diff --git a/src/CredentialsImport.js b/src/CredentialsImport.js index fe1904b7f..cbf2358d5 100644 --- a/src/CredentialsImport.js +++ b/src/CredentialsImport.js @@ -59,8 +59,8 @@ class CredentialsImport { } async started() { - this.device.deviceApi.notify(new CloseAutofillParentCall(null)); this.device.deviceApi.notify(new StartCredentialsImportFlowCall({})); + this.device.deviceApi.notify(new CloseAutofillParentCall(null)); } async dismissed() { diff --git a/swift-package/Resources/assets/autofill-debug.js b/swift-package/Resources/assets/autofill-debug.js index 6a9fbe5cd..6c651d6f2 100644 --- a/swift-package/Resources/assets/autofill-debug.js +++ b/swift-package/Resources/assets/autofill-debug.js @@ -7769,8 +7769,8 @@ class CredentialsImport { activeInput?.focus(); } async started() { - this.device.deviceApi.notify(new _deviceApiCalls.CloseAutofillParentCall(null)); this.device.deviceApi.notify(new _deviceApiCalls.StartCredentialsImportFlowCall({})); + this.device.deviceApi.notify(new _deviceApiCalls.CloseAutofillParentCall(null)); } async dismissed() { this.device.deviceApi.notify(new _deviceApiCalls.CredentialsImportFlowPermanentlyDismissedCall(null)); diff --git a/swift-package/Resources/assets/autofill.js b/swift-package/Resources/assets/autofill.js index 04931d74e..b7dbbe96e 100644 --- a/swift-package/Resources/assets/autofill.js +++ b/swift-package/Resources/assets/autofill.js @@ -3406,8 +3406,8 @@ class CredentialsImport { activeInput?.focus(); } async started() { - this.device.deviceApi.notify(new _deviceApiCalls.CloseAutofillParentCall(null)); this.device.deviceApi.notify(new _deviceApiCalls.StartCredentialsImportFlowCall({})); + this.device.deviceApi.notify(new _deviceApiCalls.CloseAutofillParentCall(null)); } async dismissed() { this.device.deviceApi.notify(new _deviceApiCalls.CredentialsImportFlowPermanentlyDismissedCall(null)); From 47c26dc32b94cdbcef3e6157497147917678c25c Mon Sep 17 00:00:00 2001 From: Deepankar Bajpeyi Date: Wed, 18 Dec 2024 15:47:43 +0100 Subject: [PATCH 2/2] [FeatureToggle] Add new autofill config for partial save trigger (#724) * fix: call credentials flow call first, and then close parent * chore: move partial save behind feature toggle * refactor: add feature toggle to support apple-only * chore:PR comments --- dist/autofill-debug.js | 32 ++++++++++--------- dist/autofill.js | 29 +++++++++-------- integration-test/helpers/mocks.android.js | 1 + integration-test/helpers/mocks.webkit.js | 1 + .../tests/save-prompts.windows.spec.js | 27 ++++++++++++++++ src/DeviceInterface/InterfacePrototype.js | 15 ++++++--- src/Form/Form.js | 2 +- src/Form/Form.test.js | 8 ++--- src/Form/formatters.js | 15 +++++---- src/Form/formatters.test.js | 18 +++++++++-- src/Settings.js | 1 + 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 | 32 ++++++++++--------- swift-package/Resources/assets/autofill.js | 29 +++++++++-------- 17 files changed, 144 insertions(+), 78 deletions(-) diff --git a/dist/autofill-debug.js b/dist/autofill-debug.js index 6c651d6f2..a3ad7f798 100644 --- a/dist/autofill-debug.js +++ b/dist/autofill-debug.js @@ -9476,16 +9476,14 @@ class InterfacePrototype { postSubmit(values, form) { if (!form.form) return; if (!form.hasValues(values)) return; - const isUsernameOnly = Object.keys(values?.credentials || {}).length === 1 && values?.credentials?.username; - const checks = [form.shouldPromptToStoreData && !form.submitHandlerExecuted, this.passwordGenerator.generated, isUsernameOnly]; + const shouldTriggerPartialSave = Object.keys(values?.credentials || {}).length === 1 && Boolean(values?.credentials?.username) && this.settings.featureToggles.partial_form_saves; + const checks = [form.shouldPromptToStoreData && !form.submitHandlerExecuted, this.passwordGenerator.generated, shouldTriggerPartialSave]; if (checks.some(Boolean)) { const formData = (0, _Credentials.appendGeneratedKey)(values, { password: this.passwordGenerator.password, username: this.emailProtection.lastGenerated }); - - // If credentials has only username field, and no password field, then trigger is a partialSave - const trigger = isUsernameOnly ? 'partialSave' : 'formSubmission'; + const trigger = shouldTriggerPartialSave ? 'partialSave' : 'formSubmission'; this.storeFormData(formData, trigger); } } @@ -10266,7 +10264,7 @@ class Form { */ getValuesReadyForStorage() { const formValues = this.getRawValues(); - return (0, _formatters.prepareFormValuesForStorage)(formValues); + return (0, _formatters.prepareFormValuesForStorage)(formValues, this.device.settings.featureToggles.partial_form_saves); } /** @@ -12094,7 +12092,7 @@ const getMMAndYYYYFromString = expiration => { }; /** - * @param {InternalDataStorageObject} credentials + * @param {InternalDataStorageObject} data * @return {boolean} */ exports.getMMAndYYYYFromString = getMMAndYYYYFromString; @@ -12106,7 +12104,7 @@ const shouldStoreIdentities = _ref3 => { }; /** - * @param {InternalDataStorageObject} credentials + * @param {InternalDataStorageObject} data * @return {boolean} */ const shouldStoreCreditCards = _ref4 => { @@ -12135,7 +12133,8 @@ const formatPhoneNumber = phone => phone.replaceAll(/[^0-9|+]/g, ''); * @return {DataStorageObject} */ exports.formatPhoneNumber = formatPhoneNumber; -const prepareFormValuesForStorage = formValues => { +const prepareFormValuesForStorage = function (formValues) { + let canTriggerPartialSave = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false; /** @type {Partial} */ let { credentials, @@ -12149,13 +12148,14 @@ const prepareFormValuesForStorage = formValues => { } /** Fixes for credentials */ - if (!credentials.username && (0, _autofillUtils.hasUsernameLikeIdentity)(identities)) { - // @ts-ignore - We know that username is not a useful value here + // If we don't have a username to match a password, let's see if email or phone are available + if (credentials.password && !credentials.username && (0, _autofillUtils.hasUsernameLikeIdentity)(identities)) { + // @ts-ignore - username will be likely undefined, but needs to be specifically assigned to a string value credentials.username = identities.emailAddress || identities.phone; } - // If we still don't have any credentials, we discard the object - if (Object.keys(credentials ?? {}).length === 0) { + // If there's no password, and we shouldn't trigger a partial save, let's discard the object + if (!credentials.password && !canTriggerPartialSave) { credentials = undefined; } @@ -15381,7 +15381,8 @@ class Settings { inputType_credentials: false, inputType_creditCards: false, inlineIcon_credentials: false, - unknown_username_categorization: false + unknown_username_categorization: false, + partial_form_saves: false }, /** @type {AvailableInputTypes} */ availableInputTypes: { @@ -18447,7 +18448,8 @@ const autofillFeatureTogglesSchema = exports.autofillFeatureTogglesSchema = _zod credentials_saving: _zod.z.boolean().optional(), inlineIcon_credentials: _zod.z.boolean().optional(), third_party_credentials_provider: _zod.z.boolean().optional(), - unknown_username_categorization: _zod.z.boolean().optional() + unknown_username_categorization: _zod.z.boolean().optional(), + partial_form_saves: _zod.z.boolean().optional() }); const emailProtectionGetIsLoggedInResultSchema = exports.emailProtectionGetIsLoggedInResultSchema = _zod.z.object({ success: _zod.z.boolean().optional(), diff --git a/dist/autofill.js b/dist/autofill.js index b7dbbe96e..c4584483d 100644 --- a/dist/autofill.js +++ b/dist/autofill.js @@ -5113,16 +5113,14 @@ class InterfacePrototype { postSubmit(values, form) { if (!form.form) return; if (!form.hasValues(values)) return; - const isUsernameOnly = Object.keys(values?.credentials || {}).length === 1 && values?.credentials?.username; - const checks = [form.shouldPromptToStoreData && !form.submitHandlerExecuted, this.passwordGenerator.generated, isUsernameOnly]; + const shouldTriggerPartialSave = Object.keys(values?.credentials || {}).length === 1 && Boolean(values?.credentials?.username) && this.settings.featureToggles.partial_form_saves; + const checks = [form.shouldPromptToStoreData && !form.submitHandlerExecuted, this.passwordGenerator.generated, shouldTriggerPartialSave]; if (checks.some(Boolean)) { const formData = (0, _Credentials.appendGeneratedKey)(values, { password: this.passwordGenerator.password, username: this.emailProtection.lastGenerated }); - - // If credentials has only username field, and no password field, then trigger is a partialSave - const trigger = isUsernameOnly ? 'partialSave' : 'formSubmission'; + const trigger = shouldTriggerPartialSave ? 'partialSave' : 'formSubmission'; this.storeFormData(formData, trigger); } } @@ -5903,7 +5901,7 @@ class Form { */ getValuesReadyForStorage() { const formValues = this.getRawValues(); - return (0, _formatters.prepareFormValuesForStorage)(formValues); + return (0, _formatters.prepareFormValuesForStorage)(formValues, this.device.settings.featureToggles.partial_form_saves); } /** @@ -7731,7 +7729,7 @@ const getMMAndYYYYFromString = expiration => { }; /** - * @param {InternalDataStorageObject} credentials + * @param {InternalDataStorageObject} data * @return {boolean} */ exports.getMMAndYYYYFromString = getMMAndYYYYFromString; @@ -7743,7 +7741,7 @@ const shouldStoreIdentities = _ref3 => { }; /** - * @param {InternalDataStorageObject} credentials + * @param {InternalDataStorageObject} data * @return {boolean} */ const shouldStoreCreditCards = _ref4 => { @@ -7772,7 +7770,8 @@ const formatPhoneNumber = phone => phone.replaceAll(/[^0-9|+]/g, ''); * @return {DataStorageObject} */ exports.formatPhoneNumber = formatPhoneNumber; -const prepareFormValuesForStorage = formValues => { +const prepareFormValuesForStorage = function (formValues) { + let canTriggerPartialSave = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false; /** @type {Partial} */ let { credentials, @@ -7786,13 +7785,14 @@ const prepareFormValuesForStorage = formValues => { } /** Fixes for credentials */ - if (!credentials.username && (0, _autofillUtils.hasUsernameLikeIdentity)(identities)) { - // @ts-ignore - We know that username is not a useful value here + // If we don't have a username to match a password, let's see if email or phone are available + if (credentials.password && !credentials.username && (0, _autofillUtils.hasUsernameLikeIdentity)(identities)) { + // @ts-ignore - username will be likely undefined, but needs to be specifically assigned to a string value credentials.username = identities.emailAddress || identities.phone; } - // If we still don't have any credentials, we discard the object - if (Object.keys(credentials ?? {}).length === 0) { + // If there's no password, and we shouldn't trigger a partial save, let's discard the object + if (!credentials.password && !canTriggerPartialSave) { credentials = undefined; } @@ -11018,7 +11018,8 @@ class Settings { inputType_credentials: false, inputType_creditCards: false, inlineIcon_credentials: false, - unknown_username_categorization: false + unknown_username_categorization: false, + partial_form_saves: false }, /** @type {AvailableInputTypes} */ availableInputTypes: { diff --git a/integration-test/helpers/mocks.android.js b/integration-test/helpers/mocks.android.js index eb34272e2..a520933c2 100644 --- a/integration-test/helpers/mocks.android.js +++ b/integration-test/helpers/mocks.android.js @@ -43,6 +43,7 @@ export function androidStringReplacements(overrides = {}) { inputType_credentials: true, inputType_identities: false, inputType_creditCards: false, + partial_form_saves: true, emailProtection: true, emailProtection_incontext_signup: true, password_generation: false, diff --git a/integration-test/helpers/mocks.webkit.js b/integration-test/helpers/mocks.webkit.js index e0b692ede..3ac1f7217 100644 --- a/integration-test/helpers/mocks.webkit.js +++ b/integration-test/helpers/mocks.webkit.js @@ -244,6 +244,7 @@ export function createWebkitMocks(platform = 'macos') { password_generation: true, credentials_saving: true, inlineIcon_credentials: true, + partial_form_saves: true, email: true, }, }, diff --git a/integration-test/tests/save-prompts.windows.spec.js b/integration-test/tests/save-prompts.windows.spec.js index 3caccd19a..fdc3dd6c8 100644 --- a/integration-test/tests/save-prompts.windows.spec.js +++ b/integration-test/tests/save-prompts.windows.spec.js @@ -58,4 +58,31 @@ test.describe('Save prompts on windows', () => { await signup.assertWasNotPromptedToSaveWindows(); }); }); + + test.describe('When partial form saves are disabled', () => { + test('I should not be prompted to save for username only', async ({ page }) => { + // enable in-terminal exceptions + await forwardConsoleMessages(page); + + const { personalAddress } = constants.fields.email; + + const credentials = { + username: personalAddress, + }; + + const signup = signupPage(page); + await signup.navigate(); + + await createWindowsMocks() + .withFeatureToggles({ + partial_form_saves: false, + }) + .applyTo(page); + + await createAutofillScript().platform('windows').applyTo(page); + + await signup.enterCredentials(credentials); + await signup.assertWasNotPromptedToSaveWindows(); + }); + }); }); diff --git a/src/DeviceInterface/InterfacePrototype.js b/src/DeviceInterface/InterfacePrototype.js index 51fb4398f..68b9b1abb 100644 --- a/src/DeviceInterface/InterfacePrototype.js +++ b/src/DeviceInterface/InterfacePrototype.js @@ -799,17 +799,22 @@ class InterfacePrototype { postSubmit(values, form) { if (!form.form) return; if (!form.hasValues(values)) return; - - const isUsernameOnly = Object.keys(values?.credentials || {}).length === 1 && values?.credentials?.username; - const checks = [form.shouldPromptToStoreData && !form.submitHandlerExecuted, this.passwordGenerator.generated, isUsernameOnly]; + const shouldTriggerPartialSave = + Object.keys(values?.credentials || {}).length === 1 && + Boolean(values?.credentials?.username) && + this.settings.featureToggles.partial_form_saves; + const checks = [ + form.shouldPromptToStoreData && !form.submitHandlerExecuted, + this.passwordGenerator.generated, + shouldTriggerPartialSave, + ]; if (checks.some(Boolean)) { const formData = appendGeneratedKey(values, { password: this.passwordGenerator.password, username: this.emailProtection.lastGenerated, }); - // If credentials has only username field, and no password field, then trigger is a partialSave - const trigger = isUsernameOnly ? 'partialSave' : 'formSubmission'; + const trigger = shouldTriggerPartialSave ? 'partialSave' : 'formSubmission'; this.storeFormData(formData, trigger); } } diff --git a/src/Form/Form.js b/src/Form/Form.js index e5fcb46ad..d0b993806 100644 --- a/src/Form/Form.js +++ b/src/Form/Form.js @@ -240,7 +240,7 @@ class Form { */ getValuesReadyForStorage() { const formValues = this.getRawValues(); - return prepareFormValuesForStorage(formValues); + return prepareFormValuesForStorage(formValues, this.device.settings.featureToggles.partial_form_saves); } /** diff --git a/src/Form/Form.test.js b/src/Form/Form.test.js index bf69ff3e9..86e916019 100644 --- a/src/Form/Form.test.js +++ b/src/Form/Form.test.js @@ -84,8 +84,8 @@ describe('Test the form class reading values correctly', () => { `, - expHasValues: true, - expValues: { credentials: { username: 'testUsername' } }, + expHasValues: false, + expValues: { credentials: undefined }, }, { testCase: 'form where the password is <=3 characters long', @@ -95,8 +95,8 @@ describe('Test the form class reading values correctly', () => { `, - expHasValues: true, - expValues: { credentials: { username: 'testUsername' } }, + expHasValues: false, + expValues: { credentials: undefined }, }, { testCase: 'form with hidden email field', diff --git a/src/Form/formatters.js b/src/Form/formatters.js index 976145926..4dbc9b77f 100644 --- a/src/Form/formatters.js +++ b/src/Form/formatters.js @@ -161,14 +161,14 @@ const getMMAndYYYYFromString = (expiration) => { }; /** - * @param {InternalDataStorageObject} credentials + * @param {InternalDataStorageObject} data * @return {boolean} */ const shouldStoreIdentities = ({ identities }) => Boolean((identities.firstName || identities.fullName) && identities.addressStreet && identities.addressCity); /** - * @param {InternalDataStorageObject} credentials + * @param {InternalDataStorageObject} data * @return {boolean} */ const shouldStoreCreditCards = ({ creditCards }) => { @@ -193,7 +193,7 @@ const formatPhoneNumber = (phone) => phone.replaceAll(/[^0-9|+]/g, ''); * @param {InternalDataStorageObject} formValues * @return {DataStorageObject} */ -const prepareFormValuesForStorage = (formValues) => { +const prepareFormValuesForStorage = (formValues, canTriggerPartialSave = false) => { /** @type {Partial} */ let { credentials, identities, creditCards } = formValues; @@ -203,13 +203,14 @@ const prepareFormValuesForStorage = (formValues) => { } /** Fixes for credentials */ - if (!credentials.username && hasUsernameLikeIdentity(identities)) { - // @ts-ignore - We know that username is not a useful value here + // If we don't have a username to match a password, let's see if email or phone are available + if (credentials.password && !credentials.username && hasUsernameLikeIdentity(identities)) { + // @ts-ignore - username will be likely undefined, but needs to be specifically assigned to a string value credentials.username = identities.emailAddress || identities.phone; } - // If we still don't have any credentials, we discard the object - if (Object.keys(credentials ?? {}).length === 0) { + // If there's no password, and we shouldn't trigger a partial save, let's discard the object + if (!credentials.password && !canTriggerPartialSave) { credentials = undefined; } diff --git a/src/Form/formatters.test.js b/src/Form/formatters.test.js index 0e12b5333..559b516a5 100644 --- a/src/Form/formatters.test.js +++ b/src/Form/formatters.test.js @@ -61,7 +61,7 @@ describe('Can strip phone formatting characters', () => { describe('prepareFormValuesForStorage()', () => { describe('handling credentials', () => { - it('accepts for username only', () => { + it('rejects for username only', () => { const values = prepareFormValuesForStorage({ credentials: { username: 'dax@example.com' }, // @ts-ignore @@ -69,7 +69,7 @@ describe('prepareFormValuesForStorage()', () => { // @ts-ignore identities: {}, }); - expect(values.credentials?.username).toBe('dax@example.com'); + expect(values.credentials).toBeUndefined(); }); it('accepts password only', () => { const values = prepareFormValuesForStorage({ @@ -93,5 +93,19 @@ describe('prepareFormValuesForStorage()', () => { }); expect(values.credentials).toEqual(inputCredentials); }); + it('accepts username only with partial_form_saves feature', () => { + const inputCredentials = { username: 'dax@example.com' }; + const values = prepareFormValuesForStorage( + { + credentials: inputCredentials, + // @ts-ignore + creditCards: {}, + // @ts-ignore + identities: {}, + }, + true, + ); + expect(values.credentials).toEqual(inputCredentials); + }); }); }); diff --git a/src/Settings.js b/src/Settings.js index b0e38b6b2..39ec6aa6b 100644 --- a/src/Settings.js +++ b/src/Settings.js @@ -330,6 +330,7 @@ export class Settings { inputType_creditCards: false, inlineIcon_credentials: false, unknown_username_categorization: false, + partial_form_saves: false, }, /** @type {AvailableInputTypes} */ availableInputTypes: { diff --git a/src/Settings.test.js b/src/Settings.test.js index 700adbd5a..9c36b1ace 100644 --- a/src/Settings.test.js +++ b/src/Settings.test.js @@ -146,6 +146,7 @@ describe('Settings', () => { "inputType_credentials": false, "inputType_creditCards": false, "inputType_identities": false, + "partial_form_saves": 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 23787cf7c..6c7223729 100644 --- a/src/deviceApiCalls/__generated__/validators-ts.ts +++ b/src/deviceApiCalls/__generated__/validators-ts.ts @@ -629,6 +629,10 @@ export interface AutofillFeatureToggles { * If true, we will attempt categorizaing username, based on the rest of the input fields in the form */ unknown_username_categorization?: boolean; + /** + * If true, then username only form saves will be allowed + */ + partial_form_saves?: boolean; } export interface GetAliasParams { requiresUserPermission: boolean; diff --git a/src/deviceApiCalls/__generated__/validators.zod.js b/src/deviceApiCalls/__generated__/validators.zod.js index 87ae05418..1c39db2a4 100644 --- a/src/deviceApiCalls/__generated__/validators.zod.js +++ b/src/deviceApiCalls/__generated__/validators.zod.js @@ -237,7 +237,8 @@ export const autofillFeatureTogglesSchema = z.object({ credentials_saving: z.boolean().optional(), inlineIcon_credentials: z.boolean().optional(), third_party_credentials_provider: z.boolean().optional(), - unknown_username_categorization: z.boolean().optional() + unknown_username_categorization: z.boolean().optional(), + partial_form_saves: z.boolean().optional() }); export const emailProtectionGetIsLoggedInResultSchema = z.object({ diff --git a/src/deviceApiCalls/schemas/autofill-settings.json b/src/deviceApiCalls/schemas/autofill-settings.json index aea548a60..4a46f5881 100644 --- a/src/deviceApiCalls/schemas/autofill-settings.json +++ b/src/deviceApiCalls/schemas/autofill-settings.json @@ -41,6 +41,10 @@ "unknown_username_categorization": { "type": "boolean", "description": "If true, we will attempt categorizaing username, based on the rest of the input fields in the form" + }, + "partial_form_saves": { + "type": "boolean", + "description": "If true, then username only form saves will be allowed" } }, "required": [] diff --git a/swift-package/Resources/assets/autofill-debug.js b/swift-package/Resources/assets/autofill-debug.js index 6c651d6f2..a3ad7f798 100644 --- a/swift-package/Resources/assets/autofill-debug.js +++ b/swift-package/Resources/assets/autofill-debug.js @@ -9476,16 +9476,14 @@ class InterfacePrototype { postSubmit(values, form) { if (!form.form) return; if (!form.hasValues(values)) return; - const isUsernameOnly = Object.keys(values?.credentials || {}).length === 1 && values?.credentials?.username; - const checks = [form.shouldPromptToStoreData && !form.submitHandlerExecuted, this.passwordGenerator.generated, isUsernameOnly]; + const shouldTriggerPartialSave = Object.keys(values?.credentials || {}).length === 1 && Boolean(values?.credentials?.username) && this.settings.featureToggles.partial_form_saves; + const checks = [form.shouldPromptToStoreData && !form.submitHandlerExecuted, this.passwordGenerator.generated, shouldTriggerPartialSave]; if (checks.some(Boolean)) { const formData = (0, _Credentials.appendGeneratedKey)(values, { password: this.passwordGenerator.password, username: this.emailProtection.lastGenerated }); - - // If credentials has only username field, and no password field, then trigger is a partialSave - const trigger = isUsernameOnly ? 'partialSave' : 'formSubmission'; + const trigger = shouldTriggerPartialSave ? 'partialSave' : 'formSubmission'; this.storeFormData(formData, trigger); } } @@ -10266,7 +10264,7 @@ class Form { */ getValuesReadyForStorage() { const formValues = this.getRawValues(); - return (0, _formatters.prepareFormValuesForStorage)(formValues); + return (0, _formatters.prepareFormValuesForStorage)(formValues, this.device.settings.featureToggles.partial_form_saves); } /** @@ -12094,7 +12092,7 @@ const getMMAndYYYYFromString = expiration => { }; /** - * @param {InternalDataStorageObject} credentials + * @param {InternalDataStorageObject} data * @return {boolean} */ exports.getMMAndYYYYFromString = getMMAndYYYYFromString; @@ -12106,7 +12104,7 @@ const shouldStoreIdentities = _ref3 => { }; /** - * @param {InternalDataStorageObject} credentials + * @param {InternalDataStorageObject} data * @return {boolean} */ const shouldStoreCreditCards = _ref4 => { @@ -12135,7 +12133,8 @@ const formatPhoneNumber = phone => phone.replaceAll(/[^0-9|+]/g, ''); * @return {DataStorageObject} */ exports.formatPhoneNumber = formatPhoneNumber; -const prepareFormValuesForStorage = formValues => { +const prepareFormValuesForStorage = function (formValues) { + let canTriggerPartialSave = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false; /** @type {Partial} */ let { credentials, @@ -12149,13 +12148,14 @@ const prepareFormValuesForStorage = formValues => { } /** Fixes for credentials */ - if (!credentials.username && (0, _autofillUtils.hasUsernameLikeIdentity)(identities)) { - // @ts-ignore - We know that username is not a useful value here + // If we don't have a username to match a password, let's see if email or phone are available + if (credentials.password && !credentials.username && (0, _autofillUtils.hasUsernameLikeIdentity)(identities)) { + // @ts-ignore - username will be likely undefined, but needs to be specifically assigned to a string value credentials.username = identities.emailAddress || identities.phone; } - // If we still don't have any credentials, we discard the object - if (Object.keys(credentials ?? {}).length === 0) { + // If there's no password, and we shouldn't trigger a partial save, let's discard the object + if (!credentials.password && !canTriggerPartialSave) { credentials = undefined; } @@ -15381,7 +15381,8 @@ class Settings { inputType_credentials: false, inputType_creditCards: false, inlineIcon_credentials: false, - unknown_username_categorization: false + unknown_username_categorization: false, + partial_form_saves: false }, /** @type {AvailableInputTypes} */ availableInputTypes: { @@ -18447,7 +18448,8 @@ const autofillFeatureTogglesSchema = exports.autofillFeatureTogglesSchema = _zod credentials_saving: _zod.z.boolean().optional(), inlineIcon_credentials: _zod.z.boolean().optional(), third_party_credentials_provider: _zod.z.boolean().optional(), - unknown_username_categorization: _zod.z.boolean().optional() + unknown_username_categorization: _zod.z.boolean().optional(), + partial_form_saves: _zod.z.boolean().optional() }); const emailProtectionGetIsLoggedInResultSchema = exports.emailProtectionGetIsLoggedInResultSchema = _zod.z.object({ success: _zod.z.boolean().optional(), diff --git a/swift-package/Resources/assets/autofill.js b/swift-package/Resources/assets/autofill.js index b7dbbe96e..c4584483d 100644 --- a/swift-package/Resources/assets/autofill.js +++ b/swift-package/Resources/assets/autofill.js @@ -5113,16 +5113,14 @@ class InterfacePrototype { postSubmit(values, form) { if (!form.form) return; if (!form.hasValues(values)) return; - const isUsernameOnly = Object.keys(values?.credentials || {}).length === 1 && values?.credentials?.username; - const checks = [form.shouldPromptToStoreData && !form.submitHandlerExecuted, this.passwordGenerator.generated, isUsernameOnly]; + const shouldTriggerPartialSave = Object.keys(values?.credentials || {}).length === 1 && Boolean(values?.credentials?.username) && this.settings.featureToggles.partial_form_saves; + const checks = [form.shouldPromptToStoreData && !form.submitHandlerExecuted, this.passwordGenerator.generated, shouldTriggerPartialSave]; if (checks.some(Boolean)) { const formData = (0, _Credentials.appendGeneratedKey)(values, { password: this.passwordGenerator.password, username: this.emailProtection.lastGenerated }); - - // If credentials has only username field, and no password field, then trigger is a partialSave - const trigger = isUsernameOnly ? 'partialSave' : 'formSubmission'; + const trigger = shouldTriggerPartialSave ? 'partialSave' : 'formSubmission'; this.storeFormData(formData, trigger); } } @@ -5903,7 +5901,7 @@ class Form { */ getValuesReadyForStorage() { const formValues = this.getRawValues(); - return (0, _formatters.prepareFormValuesForStorage)(formValues); + return (0, _formatters.prepareFormValuesForStorage)(formValues, this.device.settings.featureToggles.partial_form_saves); } /** @@ -7731,7 +7729,7 @@ const getMMAndYYYYFromString = expiration => { }; /** - * @param {InternalDataStorageObject} credentials + * @param {InternalDataStorageObject} data * @return {boolean} */ exports.getMMAndYYYYFromString = getMMAndYYYYFromString; @@ -7743,7 +7741,7 @@ const shouldStoreIdentities = _ref3 => { }; /** - * @param {InternalDataStorageObject} credentials + * @param {InternalDataStorageObject} data * @return {boolean} */ const shouldStoreCreditCards = _ref4 => { @@ -7772,7 +7770,8 @@ const formatPhoneNumber = phone => phone.replaceAll(/[^0-9|+]/g, ''); * @return {DataStorageObject} */ exports.formatPhoneNumber = formatPhoneNumber; -const prepareFormValuesForStorage = formValues => { +const prepareFormValuesForStorage = function (formValues) { + let canTriggerPartialSave = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false; /** @type {Partial} */ let { credentials, @@ -7786,13 +7785,14 @@ const prepareFormValuesForStorage = formValues => { } /** Fixes for credentials */ - if (!credentials.username && (0, _autofillUtils.hasUsernameLikeIdentity)(identities)) { - // @ts-ignore - We know that username is not a useful value here + // If we don't have a username to match a password, let's see if email or phone are available + if (credentials.password && !credentials.username && (0, _autofillUtils.hasUsernameLikeIdentity)(identities)) { + // @ts-ignore - username will be likely undefined, but needs to be specifically assigned to a string value credentials.username = identities.emailAddress || identities.phone; } - // If we still don't have any credentials, we discard the object - if (Object.keys(credentials ?? {}).length === 0) { + // If there's no password, and we shouldn't trigger a partial save, let's discard the object + if (!credentials.password && !canTriggerPartialSave) { credentials = undefined; } @@ -11018,7 +11018,8 @@ class Settings { inputType_credentials: false, inputType_creditCards: false, inlineIcon_credentials: false, - unknown_username_categorization: false + unknown_username_categorization: false, + partial_form_saves: false }, /** @type {AvailableInputTypes} */ availableInputTypes: {