diff --git a/apps/browser/src/autofill/services/autofill-overlay-content.service.spec.ts b/apps/browser/src/autofill/services/autofill-overlay-content.service.spec.ts index fbe8867d2d..f8f4134328 100644 --- a/apps/browser/src/autofill/services/autofill-overlay-content.service.spec.ts +++ b/apps/browser/src/autofill/services/autofill-overlay-content.service.spec.ts @@ -1110,6 +1110,33 @@ describe("AutofillOverlayContentService", () => { }); }); + describe("checkIsMostRecentlyFocusedFieldWithinViewport message handler", () => { + it("updates the bounding rects of the most recently focused field and returns whether the field is within the viewport", async () => { + autofillOverlayContentService["mostRecentlyFocusedField"] = + mock>(); + const updateMostRecentlyFocusedFieldSpy = jest + .spyOn(autofillOverlayContentService as any, "updateMostRecentlyFocusedField") + .mockImplementation(() => { + autofillOverlayContentService["focusedFieldData"] = { + focusedFieldStyles: { paddingRight: "10", paddingLeft: "10" }, + focusedFieldRects: { width: 10, height: 10, top: 10, left: 10 }, + }; + }); + + sendMockExtensionMessage( + { + command: "checkIsMostRecentlyFocusedFieldWithinViewport", + }, + mock(), + sendResponseSpy, + ); + await flushPromises(); + + expect(updateMostRecentlyFocusedFieldSpy).toHaveBeenCalled(); + expect(sendResponseSpy).toHaveBeenCalledWith(true); + }); + }); + describe("messages that trigger a blur of the most recently focused field", () => { const messages = [ "blurMostRecentlyFocusedField", @@ -1482,6 +1509,95 @@ describe("AutofillOverlayContentService", () => { }); }); + describe("setupRebuildSubFrameOffsetsListeners message handler", () => { + let autofillFieldElement: ElementWithOpId; + + beforeEach(() => { + Object.defineProperty(window, "top", { + value: null, + writable: true, + }); + jest.spyOn(globalThis, "addEventListener"); + jest.spyOn(globalThis.document.body, "addEventListener"); + document.body.innerHTML = ` +
+ + +
+ `; + autofillFieldElement = document.getElementById( + "username-field", + ) as ElementWithOpId; + }); + + describe("skipping the setup of the sub frame listeners", () => { + it('skips setup when the window is the "top" frame', async () => { + Object.defineProperty(window, "top", { + value: window, + writable: true, + }); + + sendMockExtensionMessage({ command: "setupRebuildSubFrameOffsetsListeners" }); + await flushPromises(); + + expect(globalThis.addEventListener).not.toHaveBeenCalledWith( + EVENTS.FOCUS, + expect.any(Function), + ); + expect(globalThis.document.body.addEventListener).not.toHaveBeenCalledWith( + EVENTS.MOUSEENTER, + expect.any(Function), + ); + }); + + it("skips setup when no form fields exist on the current frame", async () => { + autofillOverlayContentService["formFieldElements"] = new Set(); + + sendMockExtensionMessage({ command: "setupRebuildSubFrameOffsetsListeners" }); + await flushPromises(); + + expect(globalThis.addEventListener).not.toHaveBeenCalledWith( + EVENTS.FOCUS, + expect.any(Function), + ); + expect(globalThis.document.body.addEventListener).not.toHaveBeenCalledWith( + EVENTS.MOUSEENTER, + expect.any(Function), + ); + }); + }); + + it("sets up the sub frame rebuild listeners when the sub frame contains fields", async () => { + autofillOverlayContentService["formFieldElements"].add(autofillFieldElement); + + sendMockExtensionMessage({ command: "setupRebuildSubFrameOffsetsListeners" }); + await flushPromises(); + + expect(globalThis.addEventListener).toHaveBeenCalledWith( + EVENTS.FOCUS, + expect.any(Function), + ); + expect(globalThis.document.body.addEventListener).toHaveBeenCalledWith( + EVENTS.MOUSEENTER, + expect.any(Function), + ); + }); + + describe("triggering the sub frame listener", () => { + beforeEach(async () => { + autofillOverlayContentService["formFieldElements"].add(autofillFieldElement); + await sendMockExtensionMessage({ command: "setupRebuildSubFrameOffsetsListeners" }); + }); + + it("triggers a rebuild of the sub frame listener when a focus event occurs", async () => { + globalThis.dispatchEvent(new Event(EVENTS.FOCUS)); + await flushPromises(); + + expect(sendExtensionMessageSpy).toHaveBeenCalledWith("triggerSubFrameFocusInRebuild"); + }); + }); + }); + describe("destroyAutofillInlineMenuListeners message handler", () => { it("destroys the inline menu listeners", () => { jest.spyOn(autofillOverlayContentService, "destroy"); @@ -1533,11 +1649,12 @@ describe("AutofillOverlayContentService", () => { pageDetailsMock, ); autofillOverlayContentService["mostRecentlyFocusedField"] = autofillFieldElement; + jest.spyOn(globalThis, "clearTimeout"); + jest.spyOn(globalThis.document, "removeEventListener"); + jest.spyOn(globalThis, "removeEventListener"); }); it("de-registers all global event listeners", () => { - jest.spyOn(globalThis.document, "removeEventListener"); - jest.spyOn(globalThis, "removeEventListener"); jest.spyOn(autofillOverlayContentService as any, "removeOverlayRepositionEventListeners"); autofillOverlayContentService.destroy(); @@ -1577,5 +1694,22 @@ describe("AutofillOverlayContentService", () => { autofillFieldElement, ); }); + + it("clears all existing timeouts", () => { + autofillOverlayContentService["focusInlineMenuListTimeout"] = setTimeout(jest.fn(), 100); + autofillOverlayContentService["closeInlineMenuOnRedirectTimeout"] = setTimeout( + jest.fn(), + 100, + ); + + autofillOverlayContentService.destroy(); + + expect(clearTimeout).toHaveBeenCalledWith( + autofillOverlayContentService["focusInlineMenuListTimeout"], + ); + expect(clearTimeout).toHaveBeenCalledWith( + autofillOverlayContentService["closeInlineMenuOnRedirectTimeout"], + ); + }); }); }); 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 43780cc264..397cd2f8db 100644 --- a/apps/browser/src/autofill/services/autofill-overlay-content.service.ts +++ b/apps/browser/src/autofill/services/autofill-overlay-content.service.ts @@ -1075,6 +1075,9 @@ export class AutofillOverlayContentService implements AutofillOverlayContentServ ); }; + /** + * Removes the listeners that facilitate a rebuild of the sub frame offsets. + */ private removeRebuildSubFrameOffsetsListeners = () => { globalThis.removeEventListener( EVENTS.FOCUS, @@ -1089,6 +1092,10 @@ export class AutofillOverlayContentService implements AutofillOverlayContentServ delete this.eventHandlersMemo[AUTOFILL_OVERLAY_SUB_FRAME_ON_MOUSE_ENTER]; }; + /** + * Re-establishes listeners that handle the sub frame offsets rebuild of the frame + * based on user interaction with the sub frame. + */ private setupSubFrameFocusOutListeners = () => { globalThis.addEventListener( EVENTS.BLUR, @@ -1106,6 +1113,9 @@ export class AutofillOverlayContentService implements AutofillOverlayContentServ ); }; + /** + * Removes the listeners that trigger when a user focuses away from the sub frame. + */ private removeSubFrameFocusOutListeners = () => { globalThis.removeEventListener( EVENTS.BLUR,