[PM-8833] Refactoring elements of the OverlayContentService to facilitate expected interactions with the password generation inline menu behavior

This commit is contained in:
Cesar Gonzalez 2024-09-19 10:32:43 -05:00
parent b94165b945
commit bfe9927fbb
No known key found for this signature in database
GPG Key ID: 3381A5457F8CCECF
6 changed files with 77 additions and 80 deletions

View File

@ -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;
}

View File

@ -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;

View File

@ -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;
}

View File

@ -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) {

View File

@ -441,13 +441,11 @@ describe("AutofillOverlayContentService", () => {
const randomElement = document.createElement(
"input",
) as ElementWithOpId<FillableFormFieldElement>;
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 () => {

View File

@ -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<string, CallableFunction> = {
[AutofillFieldQualifier.username]: this.inlineMenuFieldQualificationService.isUsernameField,
[AutofillFieldQualifier.newPassword]:
this.inlineMenuFieldQualificationService.isNewPasswordField,
};
private readonly cardFieldQualifiers: Record<string, CallableFunction> = {
[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<string, CallableFunction>,
) => {
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