diff --git a/apps/browser/src/autofill/background/overlay.background.ts b/apps/browser/src/autofill/background/overlay.background.ts index b2d3e0a29c..e5747789e9 100644 --- a/apps/browser/src/autofill/background/overlay.background.ts +++ b/apps/browser/src/autofill/background/overlay.background.ts @@ -647,7 +647,7 @@ export class OverlayBackground implements OverlayBackgroundInterface { * Identifies whether the inline menu is being shown on an account creation field. */ private showInlineMenuAccountCreation(): boolean { - if (this.focusedFieldData?.inlineMenuFillType === InlineMenuFillType.AccountCreation) { + if (this.focusedFieldData?.inlineMenuFillType === InlineMenuFillType.AccountCreationUsername) { return true; } @@ -1359,8 +1359,8 @@ export class OverlayBackground implements OverlayBackgroundInterface { this.isFieldCurrentlyFocused = true; const accountCreationFieldBlurred = - previousFocusedFieldData?.inlineMenuFillType === InlineMenuFillType.AccountCreation && - this.focusedFieldData.inlineMenuFillType !== InlineMenuFillType.AccountCreation; + previousFocusedFieldData?.inlineMenuFillType === InlineMenuFillType.AccountCreationUsername && + this.focusedFieldData.inlineMenuFillType !== InlineMenuFillType.AccountCreationUsername; if (this.focusedFieldData.inlineMenuFillType === InlineMenuFillType.PasswordGeneration) { this.refreshGeneratedPassword().catch((error) => this.logService.error(error)); @@ -1442,7 +1442,7 @@ export class OverlayBackground implements OverlayBackgroundInterface { this.inlineMenuFieldQualificationService.isNewPasswordField(field); if ( this.focusedFieldData.inlineMenuFillType === InlineMenuFillType.PasswordGeneration || - this.focusedFieldData.inlineMenuFillType === InlineMenuFillType.AccountCreation + this.focusedFieldData.inlineMenuFillType === InlineMenuFillType.AccountCreationUsername ) { return isNewPasswordField; } diff --git a/apps/browser/src/autofill/enums/autofill-overlay.enum.ts b/apps/browser/src/autofill/enums/autofill-overlay.enum.ts index 2f540b59f3..873c8c8a6f 100644 --- a/apps/browser/src/autofill/enums/autofill-overlay.enum.ts +++ b/apps/browser/src/autofill/enums/autofill-overlay.enum.ts @@ -22,9 +22,18 @@ export const RedirectFocusDirection = { } as const; export enum InlineMenuFillType { - AccountCreation = 5, + AccountCreationUsername = 5, PasswordGeneration = 6, } export type InlineMenuFillTypes = InlineMenuFillType | CipherType; +export const InlineMenuAccountCreationFieldType = { + Email: "email", + Password: "password", +} as const; + +export type InlineMenuAccountCreationFieldTypes = + | string + | (typeof InlineMenuAccountCreationFieldType)[keyof typeof InlineMenuAccountCreationFieldType]; + export const MAX_SUB_FRAME_DEPTH = 8; diff --git a/apps/browser/src/autofill/models/autofill-field.ts b/apps/browser/src/autofill/models/autofill-field.ts index dbc97d59d0..9f55dc3870 100644 --- a/apps/browser/src/autofill/models/autofill-field.ts +++ b/apps/browser/src/autofill/models/autofill-field.ts @@ -1,5 +1,8 @@ import { AutofillFieldQualifierType } from "../enums/autofill-field.enums"; -import { InlineMenuFillTypes } from "../enums/autofill-overlay.enum"; +import { + InlineMenuAccountCreationFieldTypes, + InlineMenuFillTypes, +} from "../enums/autofill-overlay.enum"; /** * Represents a single field that is collected from the page source and is potentially autofilled. @@ -112,9 +115,9 @@ export default class AutofillField { inlineMenuFillType?: InlineMenuFillTypes; - showInlineMenuAccountCreation?: boolean; - showPasskeys?: boolean; fieldQualifier?: AutofillFieldQualifierType; + + accountCreationFieldType?: InlineMenuAccountCreationFieldTypes; } diff --git a/apps/browser/src/autofill/overlay/inline-menu/pages/list/list.scss b/apps/browser/src/autofill/overlay/inline-menu/pages/list/list.scss index 79f300116b..1246f2646f 100644 --- a/apps/browser/src/autofill/overlay/inline-menu/pages/list/list.scss +++ b/apps/browser/src/autofill/overlay/inline-menu/pages/list/list.scss @@ -404,7 +404,7 @@ body { align-content: flex-start; align-items: center; justify-content: flex-start; - padding: 0.8rem 0.4rem 1rem 0.65rem; + padding: 0.8rem 0.4rem 1.1rem 0.65rem; border-radius: 0.4rem; &:has(:focus-visible):not(.remove-outline) { diff --git a/apps/browser/src/autofill/services/autofill-overlay-content.service.spec.ts b/apps/browser/src/autofill/services/autofill-overlay-content.service.spec.ts index 00dfde79c2..c94314a8b2 100644 --- a/apps/browser/src/autofill/services/autofill-overlay-content.service.spec.ts +++ b/apps/browser/src/autofill/services/autofill-overlay-content.service.spec.ts @@ -441,13 +441,11 @@ describe("AutofillOverlayContentService", () => { const randomElement = document.createElement( "input", ) as ElementWithOpId; - jest.spyOn(autofillOverlayContentService as any, "qualifyUserFilledLoginField"); + jest.spyOn(autofillOverlayContentService as any, "qualifyUserFilledField"); autofillOverlayContentService["storeModifiedFormElement"](randomElement); - expect( - autofillOverlayContentService["qualifyUserFilledLoginField"], - ).not.toHaveBeenCalled(); + expect(autofillOverlayContentService["qualifyUserFilledField"]).not.toHaveBeenCalled(); }); it("sets the field as the most recently focused form field element", async () => { diff --git a/apps/browser/src/autofill/services/autofill-overlay-content.service.ts b/apps/browser/src/autofill/services/autofill-overlay-content.service.ts index b3798d2a4c..69d5146f04 100644 --- a/apps/browser/src/autofill/services/autofill-overlay-content.service.ts +++ b/apps/browser/src/autofill/services/autofill-overlay-content.service.ts @@ -22,6 +22,7 @@ import { AutofillExtensionMessage } from "../content/abstractions/autofill-init" import { AutofillFieldQualifier, AutofillFieldQualifierType } from "../enums/autofill-field.enums"; import { AutofillOverlayElement, + InlineMenuAccountCreationFieldType, InlineMenuFillType, MAX_SUB_FRAME_DEPTH, RedirectFocusDirection, @@ -95,6 +96,11 @@ export class AutofillOverlayContentService implements AutofillOverlayContentServ [AutofillFieldQualifier.password]: this.inlineMenuFieldQualificationService.isCurrentPasswordField, }; + private readonly accountCreationFieldQualifiers: Record = { + [AutofillFieldQualifier.username]: this.inlineMenuFieldQualificationService.isUsernameField, + [AutofillFieldQualifier.newPassword]: + this.inlineMenuFieldQualificationService.isNewPasswordField, + }; private readonly cardFieldQualifiers: Record = { [AutofillFieldQualifier.cardholderName]: this.inlineMenuFieldQualificationService.isFieldForCardholderName, @@ -141,8 +147,6 @@ export class AutofillOverlayContentService implements AutofillOverlayContentServ this.inlineMenuFieldQualificationService.isFieldForIdentityEmail, [AutofillFieldQualifier.identityUsername]: this.inlineMenuFieldQualificationService.isFieldForIdentityUsername, - [AutofillFieldQualifier.newPassword]: - this.inlineMenuFieldQualificationService.isNewPasswordField, }; constructor( @@ -783,13 +787,17 @@ export class AutofillOverlayContentService implements AutofillOverlayContentServ if (!autofillFieldData.fieldQualifier) { switch (autofillFieldData.inlineMenuFillType) { case CipherType.Login: - this.qualifyUserFilledLoginField(autofillFieldData); + this.qualifyUserFilledField(autofillFieldData, this.loginFieldQualifiers); + break; + case InlineMenuFillType.AccountCreationUsername: + case InlineMenuFillType.PasswordGeneration: + this.qualifyUserFilledField(autofillFieldData, this.accountCreationFieldQualifiers); break; case CipherType.Card: - this.qualifyUserFilledCardField(autofillFieldData); + this.qualifyUserFilledField(autofillFieldData, this.cardFieldQualifiers); break; case CipherType.Identity: - this.qualifyUserFilledIdentityField(autofillFieldData); + this.qualifyUserFilledField(autofillFieldData, this.identityFieldQualifiers); break; } } @@ -798,52 +806,22 @@ export class AutofillOverlayContentService implements AutofillOverlayContentServ } /** - * Handles qualifying the user field login field to be used when adding a new vault item. + * Handles qualification of the user filled field based on the field qualifiers provided. * * @param autofillFieldData - Autofill field data captured from the form field element. + * @param qualifiers - The field qualifiers to use when qualifying the user filled field. */ - private qualifyUserFilledLoginField(autofillFieldData: AutofillField) { - for (const [fieldQualifier, fieldQualifierFunction] of Object.entries( - this.loginFieldQualifiers, - )) { + private qualifyUserFilledField = ( + autofillFieldData: AutofillField, + qualifiers: Record, + ) => { + for (const [fieldQualifier, fieldQualifierFunction] of Object.entries(qualifiers)) { if (fieldQualifierFunction(autofillFieldData)) { autofillFieldData.fieldQualifier = fieldQualifier as AutofillFieldQualifierType; return; } } - } - - /** - * Handles qualifying the user field card field to be used when adding a new vault item. - * - * @param autofillFieldData - Autofill field data captured from the form field element. - */ - private qualifyUserFilledCardField(autofillFieldData: AutofillField) { - for (const [fieldQualifier, fieldQualifierFunction] of Object.entries( - this.cardFieldQualifiers, - )) { - if (fieldQualifierFunction(autofillFieldData)) { - autofillFieldData.fieldQualifier = fieldQualifier as AutofillFieldQualifierType; - return; - } - } - } - - /** - * Handles qualifying the user field identity field to be used when adding a new vault item. - * - * @param autofillFieldData - Autofill field data captured from the form field element. - */ - private qualifyUserFilledIdentityField(autofillFieldData: AutofillField) { - for (const [fieldQualifier, fieldQualifierFunction] of Object.entries( - this.identityFieldQualifiers, - )) { - if (fieldQualifierFunction(autofillFieldData)) { - autofillFieldData.fieldQualifier = fieldQualifier as AutofillFieldQualifierType; - return; - } - } - } + }; /** * Stores the qualified user filled filed to allow for referencing its value when adding a new vault item. @@ -1020,33 +998,13 @@ export class AutofillOverlayContentService implements AutofillOverlayContentServ const { width, height, top, left } = await this.getMostRecentlyFocusedFieldRects(formFieldElement); const autofillFieldData = this.formFieldElements.get(formFieldElement); - let accountCreationFieldType = null; - - // TODO: This should be refactored to ensure we cache the result of the qualification. - if ( - [InlineMenuFillType.AccountCreation, CipherType.Login].includes( - autofillFieldData?.inlineMenuFillType, - ) - ) { - if (this.inlineMenuFieldQualificationService.isUsernameField(autofillFieldData)) { - accountCreationFieldType = this.inlineMenuFieldQualificationService.isEmailField( - autofillFieldData, - ) - ? "email" - : autofillFieldData.type; - } else if (this.inlineMenuFieldQualificationService.isNewPasswordField(autofillFieldData)) { - autofillFieldData.inlineMenuFillType = InlineMenuFillType.PasswordGeneration; - } else { - accountCreationFieldType = "password"; - } - } this.focusedFieldData = { focusedFieldStyles: { paddingRight, paddingLeft }, focusedFieldRects: { width, height, top, left }, inlineMenuFillType: autofillFieldData?.inlineMenuFillType, showPasskeys: !!autofillFieldData?.showPasskeys, - accountCreationFieldType, + accountCreationFieldType: autofillFieldData?.accountCreationFieldType, }; await this.sendExtensionMessage("updateFocusedFieldData", { @@ -1127,8 +1085,7 @@ export class AutofillOverlayContentService implements AutofillOverlayContentServ if ( this.inlineMenuFieldQualificationService.isFieldForLoginForm(autofillFieldData, pageDetails) ) { - autofillFieldData.inlineMenuFillType = CipherType.Login; - autofillFieldData.showPasskeys = autofillFieldData.autoCompleteType.includes("webauthn"); + void this.setQualifiedLoginFillType(autofillFieldData); return false; } @@ -1148,7 +1105,7 @@ export class AutofillOverlayContentService implements AutofillOverlayContentServ pageDetails, ) ) { - autofillFieldData.inlineMenuFillType = InlineMenuFillType.AccountCreation; + this.setQualifiedAccountCreationFillType(autofillFieldData); return false; } @@ -1165,6 +1122,36 @@ export class AutofillOverlayContentService implements AutofillOverlayContentServ return true; } + private async setQualifiedLoginFillType(autofillFieldData: AutofillField) { + autofillFieldData.inlineMenuFillType = CipherType.Login; + autofillFieldData.showPasskeys = autofillFieldData.autoCompleteType.includes("webauthn"); + if (!(await this.isInlineMenuCiphersPopulated())) { + this.qualifyAccountCreationFieldType(autofillFieldData); + } + } + + private setQualifiedAccountCreationFillType(autofillFieldData: AutofillField) { + autofillFieldData.inlineMenuFillType = + this.inlineMenuFieldQualificationService.isNewPasswordField(autofillFieldData) + ? InlineMenuFillType.PasswordGeneration + : InlineMenuFillType.AccountCreationUsername; + this.qualifyAccountCreationFieldType(autofillFieldData); + } + + private qualifyAccountCreationFieldType(autofillFieldData: AutofillField) { + if (!this.inlineMenuFieldQualificationService.isUsernameField(autofillFieldData)) { + autofillFieldData.accountCreationFieldType = InlineMenuAccountCreationFieldType.Password; + return; + } + + if (this.inlineMenuFieldQualificationService.isEmailField(autofillFieldData)) { + autofillFieldData.accountCreationFieldType = InlineMenuAccountCreationFieldType.Email; + return; + } + + autofillFieldData.accountCreationFieldType = autofillFieldData.type; + } + /** * Validates whether a field is considered to be "hidden" based on the field's attributes. * If the field is hidden, a fallback listener will be set up to ensure that the