From 42080a737784dabd27da707e0d8e3310f8cea8f2 Mon Sep 17 00:00:00 2001 From: Cesar Gonzalez Date: Wed, 12 Jun 2024 06:00:59 -0500 Subject: [PATCH] [PM-5189] Fixing an issue with how we handle closing the inline menu after a programmtic redirection --- .../background/overlay.background.spec.ts | 28 +++++++++++++++++ .../autofill/background/overlay.background.ts | 31 +++++++++++++------ ...utofill-inline-menu-iframe.service.spec.ts | 2 +- .../autofill-inline-menu-iframe.service.ts | 2 +- 4 files changed, 52 insertions(+), 11 deletions(-) diff --git a/apps/browser/src/autofill/background/overlay.background.spec.ts b/apps/browser/src/autofill/background/overlay.background.spec.ts index 3e7cb693862..13cfca5bbcb 100644 --- a/apps/browser/src/autofill/background/overlay.background.spec.ts +++ b/apps/browser/src/autofill/background/overlay.background.spec.ts @@ -1488,6 +1488,7 @@ describe("OverlayBackground", () => { describe("triggerDelayedAutofillInlineMenuClosure message handler", () => { it("skips triggering the delayed closure of the inline menu if a field is currently focused", async () => { + jest.useFakeTimers(); sendMockExtensionMessage({ command: "updateIsFieldCurrentlyFocused", isFieldCurrentlyFocused: true, @@ -1499,6 +1500,7 @@ describe("OverlayBackground", () => { portKey, }); await flushPromises(); + jest.advanceTimersByTime(100); const message = { command: "triggerDelayedAutofillInlineMenuClosure" }; expect(buttonPortSpy.postMessage).not.toHaveBeenCalledWith(message); @@ -1506,16 +1508,42 @@ describe("OverlayBackground", () => { }); it("sends a message to the button and list ports that triggers a delayed closure of the inline menu", async () => { + jest.useFakeTimers(); sendPortMessage(buttonMessageConnectorSpy, { command: "triggerDelayedAutofillInlineMenuClosure", portKey, }); await flushPromises(); + jest.advanceTimersByTime(100); const message = { command: "triggerDelayedAutofillInlineMenuClosure" }; expect(buttonPortSpy.postMessage).toHaveBeenCalledWith(message); expect(listPortSpy.postMessage).toHaveBeenCalledWith(message); }); + + it("triggers a single delayed closure if called again within a 100ms threshold", async () => { + jest.useFakeTimers(); + sendPortMessage(buttonMessageConnectorSpy, { + command: "triggerDelayedAutofillInlineMenuClosure", + portKey, + }); + await flushPromises(); + jest.advanceTimersByTime(50); + sendPortMessage(buttonMessageConnectorSpy, { + command: "triggerDelayedAutofillInlineMenuClosure", + portKey, + }); + await flushPromises(); + jest.advanceTimersByTime(100); + + const message = { command: "triggerDelayedAutofillInlineMenuClosure" }; + expect(buttonPortSpy.postMessage).toHaveBeenCalledTimes(2); + expect(buttonPortSpy.postMessage).not.toHaveBeenNthCalledWith(1, message); + expect(buttonPortSpy.postMessage).toHaveBeenNthCalledWith(2, message); + expect(listPortSpy.postMessage).toHaveBeenCalledTimes(2); + expect(listPortSpy.postMessage).not.toHaveBeenNthCalledWith(1, message); + expect(listPortSpy.postMessage).toHaveBeenNthCalledWith(2, message); + }); }); describe("autofillInlineMenuBlurred message handler", () => { diff --git a/apps/browser/src/autofill/background/overlay.background.ts b/apps/browser/src/autofill/background/overlay.background.ts index 993dcddbc72..5197fabfb37 100644 --- a/apps/browser/src/autofill/background/overlay.background.ts +++ b/apps/browser/src/autofill/background/overlay.background.ts @@ -60,6 +60,7 @@ export class OverlayBackground implements OverlayBackgroundInterface { private inlineMenuPageTranslations: Record; private inlineMenuFadeInTimeout: number | NodeJS.Timeout; private updateInlineMenuPositionTimeout: number | NodeJS.Timeout; + private delayedCloseTimeout: number | NodeJS.Timeout; private focusedFieldData: FocusedFieldData; private isFieldCurrentlyFocused: boolean = false; private isFieldCurrentlyFilling: boolean = false; @@ -99,8 +100,7 @@ export class OverlayBackground implements OverlayBackgroundInterface { deletedCipher: () => this.updateInlineMenuCiphers(), }; private readonly inlineMenuButtonPortMessageHandlers: InlineMenuButtonPortMessageHandlers = { - triggerDelayedAutofillInlineMenuClosure: ({ port }) => - this.triggerDelayedInlineMenuClosure(port.sender), + triggerDelayedAutofillInlineMenuClosure: ({ port }) => this.triggerDelayedInlineMenuClosure(), autofillInlineMenuButtonClicked: ({ port }) => this.handleInlineMenuButtonClicked(port), autofillInlineMenuBlurred: () => this.checkInlineMenuListFocused(), redirectAutofillInlineMenuFocusOut: ({ message, port }) => @@ -372,7 +372,7 @@ export class OverlayBackground implements OverlayBackgroundInterface { } } - this.updateInlineMenuPositionTimeout = setTimeout( + this.updateInlineMenuPositionTimeout = globalThis.setTimeout( () => this.updateInlineMenuPositionAfterSubFrameRebuild(sender), 650, ); @@ -510,17 +510,29 @@ export class OverlayBackground implements OverlayBackgroundInterface { * Sends a message to the sender tab to trigger a delayed closure of the inline menu. * This is used to ensure that we capture click events on the inline menu in the case * that some on page programmatic method attempts to force focus redirection. - * - * @param sender - The sender of the port message */ - private triggerDelayedInlineMenuClosure(sender: chrome.runtime.MessageSender) { + private triggerDelayedInlineMenuClosure() { if (this.isFieldCurrentlyFocused) { return; } - const message = { command: "triggerDelayedAutofillInlineMenuClosure" }; - this.inlineMenuButtonPort?.postMessage(message); - this.inlineMenuListPort?.postMessage(message); + this.clearDelayedInlineMenuClosure(); + + this.delayedCloseTimeout = globalThis.setTimeout(() => { + const message = { command: "triggerDelayedAutofillInlineMenuClosure" }; + this.inlineMenuButtonPort?.postMessage(message); + this.inlineMenuListPort?.postMessage(message); + }, 100); + } + + /** + * Clears the delayed closure timeout for the inline menu, effectively + * cancelling the event from occurring. + */ + private clearDelayedInlineMenuClosure() { + if (this.delayedCloseTimeout) { + clearTimeout(this.delayedCloseTimeout); + } } /** @@ -772,6 +784,7 @@ export class OverlayBackground implements OverlayBackgroundInterface { return; } + this.clearDelayedInlineMenuClosure(); await this.openInlineMenu(false, true); } diff --git a/apps/browser/src/autofill/overlay/inline-menu/iframe-content/autofill-inline-menu-iframe.service.spec.ts b/apps/browser/src/autofill/overlay/inline-menu/iframe-content/autofill-inline-menu-iframe.service.spec.ts index cedbb430e63..231cf10b63f 100644 --- a/apps/browser/src/autofill/overlay/inline-menu/iframe-content/autofill-inline-menu-iframe.service.spec.ts +++ b/apps/browser/src/autofill/overlay/inline-menu/iframe-content/autofill-inline-menu-iframe.service.spec.ts @@ -416,7 +416,7 @@ describe("AutofillInlineMenuIframeService", () => { "opacity 65ms ease-out 0s", ); - jest.advanceTimersByTime(200); + jest.advanceTimersByTime(100); expect(autofillInlineMenuIframeService["iframe"].style.transition).toBe( "opacity 125ms ease-out 0s", ); diff --git a/apps/browser/src/autofill/overlay/inline-menu/iframe-content/autofill-inline-menu-iframe.service.ts b/apps/browser/src/autofill/overlay/inline-menu/iframe-content/autofill-inline-menu-iframe.service.ts index aaa629bc67b..ac69b5b620c 100644 --- a/apps/browser/src/autofill/overlay/inline-menu/iframe-content/autofill-inline-menu-iframe.service.ts +++ b/apps/browser/src/autofill/overlay/inline-menu/iframe-content/autofill-inline-menu-iframe.service.ts @@ -322,7 +322,7 @@ export class AutofillInlineMenuIframeService implements AutofillInlineMenuIframe this.delayedCloseTimeout = globalThis.setTimeout(() => { this.updateElementStyles(this.iframe, { transition: this.fadeInOpacityTransition }); this.forceCloseInlineMenu(); - }, 200); + }, 100); } /**