diff --git a/apps/browser/src/autofill/background/abstractions/overlay.background.ts b/apps/browser/src/autofill/background/abstractions/overlay.background.ts index 94e13bab2ca..627e1730830 100644 --- a/apps/browser/src/autofill/background/abstractions/overlay.background.ts +++ b/apps/browser/src/autofill/background/abstractions/overlay.background.ts @@ -65,7 +65,6 @@ export type OverlayBackgroundExtensionMessage = { subFrameData?: SubFrameOffsetData; focusedFieldData?: FocusedFieldData; styles?: Partial; - triggerInlineMenuPositionUpdate?: boolean; data?: LockedVaultPendingNotificationsData; } & OverlayAddNewItemMessage & CloseInlineMenuMessage; @@ -121,7 +120,8 @@ export type OverlayBackgroundExtensionMessageHandlers = { checkShouldRepositionInlineMenu: ({ sender }: BackgroundSenderParam) => boolean; getCurrentTabFrameId: ({ sender }: BackgroundSenderParam) => number; updateSubFrameData: ({ message, sender }: BackgroundOnMessageHandlerParams) => void; - rebuildSubFrameOffsets: ({ message, sender }: BackgroundOnMessageHandlerParams) => void; + updateSubFrameOffsetsForReflowEvent: ({ sender }: BackgroundSenderParam) => void; + repositionAutofillInlineMenuForSubFrame: ({ sender }: BackgroundSenderParam) => void; destroyAutofillInlineMenuListeners: ({ message, sender, diff --git a/apps/browser/src/autofill/background/overlay.background.spec.ts b/apps/browser/src/autofill/background/overlay.background.spec.ts index a8fe4f9a97e..a257372f827 100644 --- a/apps/browser/src/autofill/background/overlay.background.spec.ts +++ b/apps/browser/src/autofill/background/overlay.background.spec.ts @@ -361,11 +361,11 @@ describe("OverlayBackground", () => { ); }); - describe("rebuildSubFrameOffsets", () => { + describe("repositionInlineMenuForSubFrame", () => { it("skips rebuilding sub frame offsets if the sender contains the currently focused field", () => { const sender = mock({ tab, frameId: bottomFrameId }); - sendMockExtensionMessage({ command: "rebuildSubFrameOffsets" }, sender); + sendMockExtensionMessage({ command: "repositionInlineMenuForSubFrame" }, sender); expect(getFrameDetailsSpy).not.toHaveBeenCalled(); }); @@ -376,7 +376,7 @@ describe("OverlayBackground", () => { frameId: 0, }); - sendMockExtensionMessage({ command: "rebuildSubFrameOffsets" }, sender); + sendMockExtensionMessage({ command: "repositionInlineMenuForSubFrame" }, sender); expect(getFrameDetailsSpy).not.toHaveBeenCalled(); }); @@ -384,7 +384,7 @@ describe("OverlayBackground", () => { it("rebuilds the sub frame offsets for a given tab", async () => { const sender = mock({ tab, frameId: middleFrameId }); - sendMockExtensionMessage({ command: "rebuildSubFrameOffsets" }, sender); + sendMockExtensionMessage({ command: "repositionInlineMenuForSubFrame" }, sender); await flushPromises(); expect(getFrameDetailsSpy).toHaveBeenCalledWith({ tabId, frameId: topFrameId }); @@ -399,7 +399,7 @@ describe("OverlayBackground", () => { jest.spyOn(overlayBackground as any, "updateInlineMenuPositionAfterSubFrameRebuild"); sendMockExtensionMessage( - { command: "rebuildSubFrameOffsets", triggerInlineMenuPositionUpdate: true }, + { command: "repositionInlineMenuForSubFrame", triggerInlineMenuPositionUpdate: true }, sender, ); await flushPromises(); @@ -435,7 +435,7 @@ describe("OverlayBackground", () => { isFieldCurrentlyFocused: false, }); - sendMockExtensionMessage({ command: "rebuildSubFrameOffsets" }, sender); + sendMockExtensionMessage({ command: "repositionInlineMenuForSubFrame" }, sender); await flushInlineMenuUpdatePromises(); expect(tabsSendMessageSpy).not.toHaveBeenCalledWith( @@ -458,7 +458,7 @@ describe("OverlayBackground", () => { it("updates the position of the inline menu elements", async () => { sendMockExtensionMessage( - { command: "rebuildSubFrameOffsets", triggerInlineMenuPositionUpdate: true }, + { command: "repositionInlineMenuForSubFrame", triggerInlineMenuPositionUpdate: true }, sender, ); await flushInlineMenuUpdatePromises(); @@ -491,7 +491,7 @@ describe("OverlayBackground", () => { }); sendMockExtensionMessage( - { command: "rebuildSubFrameOffsets", triggerInlineMenuPositionUpdate: true }, + { command: "repositionInlineMenuForSubFrame", triggerInlineMenuPositionUpdate: true }, sender, ); await flushInlineMenuUpdatePromises(); diff --git a/apps/browser/src/autofill/background/overlay.background.ts b/apps/browser/src/autofill/background/overlay.background.ts index b25b4c800cc..931824515e5 100644 --- a/apps/browser/src/autofill/background/overlay.background.ts +++ b/apps/browser/src/autofill/background/overlay.background.ts @@ -64,6 +64,7 @@ export class OverlayBackground implements OverlayBackgroundInterface { private inlineMenuPageTranslations: Record; private inlineMenuFadeInTimeout: number | NodeJS.Timeout; private updateInlineMenuPositionTimeout: number | NodeJS.Timeout; + private isReflowUpdatingSubFrames: boolean = false; private delayedCloseTimeout: number | NodeJS.Timeout; private focusedFieldData: FocusedFieldData; private isFieldCurrentlyFocused: boolean = false; @@ -95,7 +96,9 @@ export class OverlayBackground implements OverlayBackgroundInterface { checkShouldRepositionInlineMenu: ({ sender }) => this.checkShouldRepositionInlineMenu(sender), getCurrentTabFrameId: ({ sender }) => this.getSenderFrameId(sender), updateSubFrameData: ({ message, sender }) => this.updateSubFrameData(message, sender), - rebuildSubFrameOffsets: ({ message, sender }) => this.rebuildSubFrameOffsets(message, sender), + updateSubFrameOffsetsForReflowEvent: ({ sender }) => this.rebuildSubFrameOffsets(sender), + repositionAutofillInlineMenuForSubFrame: ({ sender }) => + this.repositionInlineMenuForSubFrame(sender), destroyAutofillInlineMenuListeners: ({ message, sender }) => this.triggerDestroyInlineMenuListeners(sender.tab, message.subFrameData.frameId), collectPageDetailsResponse: ({ message, sender }) => this.storePageDetails(message, sender), @@ -376,26 +379,12 @@ export class OverlayBackground implements OverlayBackgroundInterface { } /** - * Handles rebuilding the sub frame offsets when the tab is repositioned or scrolled. - * Will trigger a re-positioning of the inline menu list and button. Note that we - * do not trigger an update to sub frame data if the sender is the frame that has - * the field currently focused. We trigger a re-calculation of the field's position - * and as a result, the sub frame offsets of that frame will be updated. + * Rebuilds the sub frame offsets for the tab associated with the sender. * - * @param message - The message received from the `rebuildSubFrameOffsets` command * @param sender - The sender of the message */ - private async rebuildSubFrameOffsets( - message: OverlayBackgroundExtensionMessage, - sender: chrome.runtime.MessageSender, - ) { - if (sender.frameId === this.focusedFieldData?.frameId) { - return; - } - - if (this.updateInlineMenuPositionTimeout) { - clearTimeout(this.updateInlineMenuPositionTimeout); - } + private async rebuildSubFrameOffsets(sender: chrome.runtime.MessageSender) { + this.clearDelayedInlineMenuClosure(); const subFrameOffsetsForTab = this.subFrameOffsetsForTab[sender.tab.id]; if (subFrameOffsetsForTab) { @@ -409,13 +398,32 @@ export class OverlayBackground implements OverlayBackgroundInterface { await this.buildSubFrameOffsets(sender.tab, frameId, sender.url); } } + } - if (message.triggerInlineMenuPositionUpdate) { - this.updateInlineMenuPositionTimeout = globalThis.setTimeout( - () => this.updateInlineMenuPositionAfterSubFrameRebuild(sender), - 650, - ); + /** + * Handles rebuilding the sub frame offsets when the tab is repositioned or scrolled. + * Will trigger a re-positioning of the inline menu list and button. Note that we + * do not trigger an update to sub frame data if the sender is the frame that has + * the field currently focused. We trigger a re-calculation of the field's position + * and as a result, the sub frame offsets of that frame will be updated. + * + * @param sender - The sender of the message + */ + private async repositionInlineMenuForSubFrame(sender: chrome.runtime.MessageSender) { + if (sender.frameId === this.focusedFieldData?.frameId) { + return; } + + if (this.updateInlineMenuPositionTimeout) { + clearTimeout(this.updateInlineMenuPositionTimeout); + } + + await this.rebuildSubFrameOffsets(sender); + + this.updateInlineMenuPositionTimeout = globalThis.setTimeout( + () => this.updateInlineMenuPositionAfterSubFrameRebuild(sender), + 650, + ); } /** 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 771509e951b..3eeec2422e1 100644 --- a/apps/browser/src/autofill/services/autofill-overlay-content.service.ts +++ b/apps/browser/src/autofill/services/autofill-overlay-content.service.ts @@ -787,7 +787,16 @@ export class AutofillOverlayContentService implements AutofillOverlayContentServ capture: true, }); globalThis.addEventListener(EVENTS.RESIZE, this.handleOverlayRepositionEvent); - this.performanceObserver = new PerformanceObserver(() => this.rebuildSubFrameOffsets(0, false)); + this.performanceObserver = new PerformanceObserver((list) => { + const entries: any = list.getEntries(); + for (let index = 0; index < entries.length; index++) { + const entry = entries[index]; + if (entry.sources?.length > 0) { + void this.sendExtensionMessage("updateSubFrameOffsetsForReflowEvent"); + return; + } + } + }); this.performanceObserver.observe({ type: "layout-shift", buffered: true }); } @@ -811,7 +820,7 @@ export class AutofillOverlayContentService implements AutofillOverlayContentServ return; } - this.rebuildSubFrameOffsets(); + this.repositionInlineMenuForSubFrame(); this.toggleInlineMenuHidden(true); this.clearUserInteractionEventTimeout(); this.userInteractionEventTimeout = globalThis.setTimeout( @@ -823,14 +832,11 @@ export class AutofillOverlayContentService implements AutofillOverlayContentServ /** * Triggers a rebuild of a sub frame's offsets within the tab. */ - private rebuildSubFrameOffsets(delay: number = 150, triggerInlineMenuPositionUpdate = true) { + private repositionInlineMenuForSubFrame() { this.clearRecalculateSubFrameOffsetsTimeout(); this.recalculateSubFrameOffsetsTimeout = globalThis.setTimeout( - () => - void this.sendExtensionMessage("rebuildSubFrameOffsets", { - triggerInlineMenuPositionUpdate, - }), - delay, + () => void this.sendExtensionMessage("repositionAutofillInlineMenuForSubFrame"), + 150, ); }