From ba628d549a7a5cabae85b3fc21c775de9b46eb12 Mon Sep 17 00:00:00 2001 From: Jeffrey Holland Date: Wed, 3 Dec 2025 11:40:56 +0100 Subject: [PATCH] Handle when events are intercepted by the host site --- .../list/autofill-inline-menu-list.spec.ts | 30 +++++++++++++++++++ .../pages/list/autofill-inline-menu-list.ts | 5 ++++ .../autofill-inline-menu-page-element.ts | 18 +++++++++++ libs/common/src/autofill/constants/index.ts | 3 ++ 4 files changed, 56 insertions(+) diff --git a/apps/browser/src/autofill/overlay/inline-menu/pages/list/autofill-inline-menu-list.spec.ts b/apps/browser/src/autofill/overlay/inline-menu/pages/list/autofill-inline-menu-list.spec.ts index 81bf7240230..da5a2268afb 100644 --- a/apps/browser/src/autofill/overlay/inline-menu/pages/list/autofill-inline-menu-list.spec.ts +++ b/apps/browser/src/autofill/overlay/inline-menu/pages/list/autofill-inline-menu-list.spec.ts @@ -1226,6 +1226,36 @@ describe("AutofillInlineMenuList", () => { }); }); + describe("pointerdown event", () => { + it("prevents list updates from destroying buttons mid-click", async () => { + postWindowMessage( + createInitAutofillInlineMenuListMessageMock({ + authStatus: AuthenticationStatus.Unlocked, + ciphers: [], + portKey, + }), + ); + await flushPromises(); + + const newItemButton = autofillInlineMenuList["newItemButtonElement"]; + expect(newItemButton).toBeDefined(); + + const pointerDownEvent = new Event("pointerdown", { bubbles: true }); + globalThis.document.dispatchEvent(pointerDownEvent); + + postWindowMessage({ + command: "updateAutofillInlineMenuListCiphers", + ciphers: [], + showInlineMenuAccountCreation: true, + portKey, + token: "test-token", + }); + await flushPromises(); + + expect(autofillInlineMenuList["newItemButtonElement"]).toBe(newItemButton); + }); + }); + describe("keydown event", () => { beforeEach(() => { postWindowMessage(createInitAutofillInlineMenuListMessageMock({ portKey })); diff --git a/apps/browser/src/autofill/overlay/inline-menu/pages/list/autofill-inline-menu-list.ts b/apps/browser/src/autofill/overlay/inline-menu/pages/list/autofill-inline-menu-list.ts index c680fe4745c..7083139b161 100644 --- a/apps/browser/src/autofill/overlay/inline-menu/pages/list/autofill-inline-menu-list.ts +++ b/apps/browser/src/autofill/overlay/inline-menu/pages/list/autofill-inline-menu-list.ts @@ -476,6 +476,11 @@ export class AutofillInlineMenuList extends AutofillInlineMenuPageElement { ciphers, showInlineMenuAccountCreation, }: UpdateAutofillInlineMenuListCiphersParams) { + // Skip updates while user is actively clicking to prevent destroying buttons mid-click + if (this.pointerIsDown) { + return; + } + if (this.isPasskeyAuthInProgress) { return; } diff --git a/apps/browser/src/autofill/overlay/inline-menu/pages/shared/autofill-inline-menu-page-element.ts b/apps/browser/src/autofill/overlay/inline-menu/pages/shared/autofill-inline-menu-page-element.ts index 5df6e7cd190..c70ea8e241f 100644 --- a/apps/browser/src/autofill/overlay/inline-menu/pages/shared/autofill-inline-menu-page-element.ts +++ b/apps/browser/src/autofill/overlay/inline-menu/pages/shared/autofill-inline-menu-page-element.ts @@ -17,6 +17,7 @@ export class AutofillInlineMenuPageElement extends HTMLElement { /** Non-null asserted. */ protected windowMessageHandlers!: AutofillInlineMenuPageElementWindowMessageHandlers; private token?: string; + protected pointerIsDown = false; constructor() { super(); @@ -94,8 +95,25 @@ export class AutofillInlineMenuPageElement extends HTMLElement { globalThis.addEventListener(EVENTS.MESSAGE, this.handleWindowMessage); globalThis.addEventListener(EVENTS.BLUR, this.handleWindowBlurEvent); globalThis.document.addEventListener(EVENTS.KEYDOWN, this.handleDocumentKeyDownEvent); + globalThis.document.addEventListener(EVENTS.POINTERDOWN, this.handlePointerDownEvent, true); + globalThis.document.addEventListener(EVENTS.POINTERUP, this.handlePointerUpEvent, true); + globalThis.document.addEventListener(EVENTS.POINTERCANCEL, this.handlePointerUpEvent, true); } + /** + * Handles pointerdown events. Sets a flag when the user is actively interacting with the menu. + */ + private handlePointerDownEvent = () => { + this.pointerIsDown = true; + }; + + /** + * Handles pointerup/pointercancel events. Clears the `pointerIsDown` flag. + */ + private handlePointerUpEvent = () => { + this.pointerIsDown = false; + }; + /** * Handles window messages from the parent window. * diff --git a/libs/common/src/autofill/constants/index.ts b/libs/common/src/autofill/constants/index.ts index dc79e27b6aa..b9550aecdfb 100644 --- a/libs/common/src/autofill/constants/index.ts +++ b/libs/common/src/autofill/constants/index.ts @@ -25,6 +25,9 @@ export const EVENTS = { MOUSELEAVE: "mouseleave", MOUSEUP: "mouseup", MOUSEOUT: "mouseout", + POINTERDOWN: "pointerdown", + POINTERUP: "pointerup", + POINTERCANCEL: "pointercancel", SUBMIT: "submit", } as const;