Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. Weโ€™ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[PM-14954] implement multi input totp styling #12449

Open
wants to merge 7 commits into
base: main
Choose a base branch
from
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,17 @@ export type InlineMenuElementPosition = {
height: number;
};

export type FieldRect = {
bottom: number;
height: number;
left: number;
right: number;
top: number;
width: number;
x: number;
y: number;
};

export type InlineMenuPosition = {
button?: InlineMenuElementPosition;
list?: InlineMenuElementPosition;
Expand Down Expand Up @@ -134,6 +145,7 @@ export type OverlayBackgroundExtensionMessage = {
isFieldCurrentlyFilling?: boolean;
subFrameData?: SubFrameOffsetData;
focusedFieldData?: FocusedFieldData;
allFieldsRect?: any;
isOpeningFullInlineMenu?: boolean;
styles?: Partial<CSSStyleDeclaration>;
data?: LockedVaultPendingNotificationsData;
Expand Down
118 changes: 118 additions & 0 deletions apps/browser/src/autofill/background/overlay.background.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2899,6 +2899,124 @@ describe("OverlayBackground", () => {
);
});
});
describe("handles menu position when input is focused", () => {
it("sets button and menu width and position when non-multi-input totp field is focused", async () => {
const subframe = {
top: 0,
left: 0,
url: "",
frameId: 0,
};

overlayBackground["focusedFieldData"] = createFocusedFieldDataMock({
focusedFieldRects: {
width: 49.328125,
height: 64,
top: 302.171875,
left: 1270.8125,
},
});

const buttonPostion = overlayBackground.getInlineMenuButtonPosition(subframe);
const menuPostion = overlayBackground.getInlineMenuListPosition(subframe);
Comment on lines +2920 to +2921
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should allow keeping the methods as private (here and elsewhere)

Suggested change
const buttonPostion = overlayBackground.getInlineMenuButtonPosition(subframe);
const menuPostion = overlayBackground.getInlineMenuListPosition(subframe);
const buttonPostion = overlayBackground['getInlineMenuButtonPosition'](subframe);
const menuPostion = overlayBackground['getInlineMenuListPosition'](subframe);


expect(menuPostion).toEqual({
width: "49px",
top: "366px",
left: "1271px",
});
expect(buttonPostion).toEqual({
width: "34px",
height: "34px",
top: "317px",
left: "1271px",
});
});
it("sets button and menu width and position when multi-input totp field is focused", async () => {
const subframe = {
top: 0,
left: 0,
url: "",
frameId: 0,
};

const totpFields = [
createAutofillFieldMock({ autoCompleteType: "one-time-code", opid: "__0" }),
createAutofillFieldMock({ autoCompleteType: "one-time-code", opid: "__1" }),
createAutofillFieldMock({ autoCompleteType: "one-time-code", opid: "__2" }),
];
const allFieldData = [
createAutofillFieldMock({
autoCompleteType: "one-time-code",
opid: "__0",
rect: {
x: 1041.5,
y: 302.171875,
width: 49.328125,
height: 64,
top: 302.171875,
right: 1090.828125,
bottom: 366.171875,
left: 1041.5,
},
}),
createAutofillFieldMock({
autoCompleteType: "one-time-code",
opid: "__1",
rect: {
x: 1098.828125,
y: 302.171875,
width: 49.328125,
height: 64,
top: 302.171875,
right: 1148.15625,
bottom: 366.171875,
left: 1098.828125,
},
}),
createAutofillFieldMock({
autoCompleteType: "one-time-code",
opid: "__2",
rect: {
x: 1156.15625,
y: 302.171875,
width: 249.328125,
height: 64,
top: 302.171875,
right: 2205.484375,
bottom: 366.171875,
left: 2156.15625,
},
}),
];
overlayBackground["focusedFieldData"] = createFocusedFieldDataMock({
focusedFieldRects: {
width: 49.328125,
height: 64,
top: 302.171875,
left: 1270.8125,
},
});

overlayBackground["allFieldData"] = allFieldData;
jest.spyOn(overlayBackground as any, "isTotpFieldForCurrentField").mockReturnValue(true);
jest.spyOn(overlayBackground as any, "getTotpFields").mockReturnValue(totpFields);

const buttonPostion = overlayBackground.getInlineMenuButtonPosition(subframe);
const menuPostion = overlayBackground.getInlineMenuListPosition(subframe);
expect(menuPostion).toEqual({
width: "1164px",
top: "366px",
left: "1042px",
});
expect(buttonPostion).toEqual({
width: "34px",
height: "34px",
top: "292px",
left: "2187px",
});
});
});

describe("triggerDelayedAutofillInlineMenuClosure message handler", () => {
it("skips triggering the delayed closure of the inline menu if a field is currently focused", async () => {
Expand Down
86 changes: 81 additions & 5 deletions apps/browser/src/autofill/background/overlay.background.ts
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,7 @@
private currentInlineMenuCiphersCount: number = 0;
private currentAddNewItemData: CurrentAddNewItemData;
private focusedFieldData: FocusedFieldData;
private allFieldData: AutofillField[];
private isFieldCurrentlyFocused: boolean = false;
private isFieldCurrentlyFilling: boolean = false;
private isInlineMenuButtonVisible: boolean = false;
Expand Down Expand Up @@ -1344,6 +1345,67 @@
this.isInlineMenuListVisible = false;
}

/**
* Get all the totp fields for the tab and frame of the currently focused field
*/
private getTotpFields(): AutofillField[] {
const currentTabId = this.focusedFieldData?.tabId;
const currentFrameId = this.focusedFieldData?.frameId;
const pageDetailsMap = this.pageDetailsForTab[currentTabId];
const pageDetails = pageDetailsMap?.get(currentFrameId);

const fields = pageDetails.details.fields;
const totpFields = fields.filter((f) =>
this.inlineMenuFieldQualificationService.isTotpField(f),

Check warning on line 1359 in apps/browser/src/autofill/background/overlay.background.ts

View check run for this annotation

Codecov / codecov/patch

apps/browser/src/autofill/background/overlay.background.ts#L1359

Added line #L1359 was not covered by tests
);

return totpFields;
}

/**
* calculates the postion and width for multi-input totp field inline menu
* @param totpFieldArray - the totp fields used to evaluate the position of the menu
*/
private calculateTotpMultiInputMenuBounds(totpFieldArray: AutofillField[]) {
// Filter the fields based on the provided totpfields
const filteredObjects = this.allFieldData.filter((obj) =>
totpFieldArray.some((o) => o.opid === obj.opid),
);

// Return null if no matching objects are found
if (filteredObjects.length === 0) {
return null;

Check warning on line 1377 in apps/browser/src/autofill/background/overlay.background.ts

View check run for this annotation

Codecov / codecov/patch

apps/browser/src/autofill/background/overlay.background.ts#L1377

Added line #L1377 was not covered by tests
}
// Calculate the smallest left and largest right values to determine width
const left = Math.min(...filteredObjects.map((obj) => obj.rect.left));
const largestRight = Math.max(...filteredObjects.map((obj) => obj.rect.right));

const width = largestRight - left;

return { left, width };
}

/**
* calculates the postion for multi-input totp field inline button
* @param totpFieldArray - the totp fields used to evaluate the position of the menu
*/
private calculateTotpMultiInputButtonBounds(totpFieldArray: AutofillField[]) {
const filteredObjects = this.allFieldData.filter((obj) =>
totpFieldArray.some((o) => o.opid === obj.opid),
);

if (filteredObjects.length === 0) {
return null;

Check warning on line 1398 in apps/browser/src/autofill/background/overlay.background.ts

View check run for this annotation

Codecov / codecov/patch

apps/browser/src/autofill/background/overlay.background.ts#L1398

Added line #L1398 was not covered by tests
}

const maxRight = Math.max(...filteredObjects.map((obj) => obj.rect.right));
const maxObject = filteredObjects.find((obj) => obj.rect.right === maxRight);
const top = maxObject.rect.top - maxObject.rect.height * 0.39;
const left = maxRight - maxObject.rect.height * 0.3;

return { left, top };
}

/**
* Updates the position of either the inline menu list or button. The position
* is based on the focused field's position and dimensions.
Expand Down Expand Up @@ -1445,12 +1507,19 @@
* Gets the position of the focused field and calculates the position
* of the inline menu button based on the focused field's position and dimensions.
*/
private getInlineMenuButtonPosition(subFrameOffsets: SubFrameOffsetData) {
getInlineMenuButtonPosition(subFrameOffsets: SubFrameOffsetData) {
const subFrameTopOffset = subFrameOffsets?.top || 0;
const subFrameLeftOffset = subFrameOffsets?.left || 0;

const { top, left, width, height } = this.focusedFieldData.focusedFieldRects;
const { width, height } = this.focusedFieldData.focusedFieldRects;
let { top, left } = this.focusedFieldData.focusedFieldRects;
const { paddingRight, paddingLeft } = this.focusedFieldData.focusedFieldStyles;

if (this.isTotpFieldForCurrentField()) {
const totpFields = this.getTotpFields();
({ left, top } = this.calculateTotpMultiInputButtonBounds(totpFields));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit; this reassignment pattern feels weird to me ๐Ÿค”

}

let elementOffset = height * 0.37;
if (height >= 35) {
elementOffset = height >= 50 ? height * 0.47 : height * 0.42;
Expand Down Expand Up @@ -1485,11 +1554,17 @@
* Gets the position of the focused field and calculates the position
* of the inline menu list based on the focused field's position and dimensions.
*/
private getInlineMenuListPosition(subFrameOffsets: SubFrameOffsetData) {
getInlineMenuListPosition(subFrameOffsets: SubFrameOffsetData) {
const subFrameTopOffset = subFrameOffsets?.top || 0;
const subFrameLeftOffset = subFrameOffsets?.left || 0;

const { top, left, width, height } = this.focusedFieldData.focusedFieldRects;
const { top, height } = this.focusedFieldData.focusedFieldRects;
let { left, width } = this.focusedFieldData.focusedFieldRects;

if (this.isTotpFieldForCurrentField()) {
const totpFields = this.getTotpFields();
({ left, width } = this.calculateTotpMultiInputMenuBounds(totpFields));
}

this.inlineMenuPosition.list = {
top: Math.round(top + height + subFrameTopOffset),
Expand All @@ -1512,7 +1587,7 @@
* @param sender - The sender of the extension message
*/
private setFocusedFieldData(
{ focusedFieldData }: OverlayBackgroundExtensionMessage,
{ focusedFieldData, allFieldsRect }: OverlayBackgroundExtensionMessage,
sender: chrome.runtime.MessageSender,
) {
if (
Expand All @@ -1529,6 +1604,7 @@

const previousFocusedFieldData = this.focusedFieldData;
this.focusedFieldData = { ...focusedFieldData, tabId: sender.tab.id, frameId: sender.frameId };
this.allFieldData = allFieldsRect;
this.isFieldCurrentlyFocused = true;

if (this.shouldUpdatePasswordGeneratorMenuOnFieldFocus()) {
Expand Down
6 changes: 6 additions & 0 deletions apps/browser/src/autofill/models/autofill-field.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { FieldRect } from "../background/abstractions/overlay.background";
// FIXME: Update this file to be type safe and remove this and next line
// @ts-strict-ignore
import { AutofillFieldQualifierType } from "../enums/autofill-field.enums";
Expand Down Expand Up @@ -124,4 +125,9 @@ export default class AutofillField {
fieldQualifier?: AutofillFieldQualifierType;

accountCreationFieldType?: InlineMenuAccountCreationFieldTypes;

/**
* used for totp multiline calculations
*/
fieldRect?: FieldRect;
}
Original file line number Diff line number Diff line change
Expand Up @@ -957,12 +957,21 @@ export class AutofillOverlayContentService implements AutofillOverlayContentServ
accountCreationFieldType: autofillFieldData?.accountCreationFieldType,
};

const allFields = this.formFieldElements;
const allFieldsRect = [];

for (const key of allFields.keys()) {
const rect = await this.getMostRecentlyFocusedFieldRects(key);
allFieldsRect.push({ ...allFields.get(key), rect }); // Add the combined result to the array
}

await this.sendExtensionMessage("updateFocusedFieldData", {
focusedFieldData: this.focusedFieldData,
allFieldsRect,
});
}

/**
/**his.formFieldElements
* Gets the bounding client rects for the most recently focused field. This method will
* attempt to use an intersection observer to get the most recently focused field's
* bounding client rects. If the intersection observer is not supported, or the
Expand Down
Loading