diff --git a/apps/browser/src/autofill/services/autofill.service.spec.ts b/apps/browser/src/autofill/services/autofill.service.spec.ts index 2427c754aeb..dc9f3fcdbd4 100644 --- a/apps/browser/src/autofill/services/autofill.service.spec.ts +++ b/apps/browser/src/autofill/services/autofill.service.spec.ts @@ -209,6 +209,9 @@ describe("AutofillService", () => { tab2 = createChromeTabMock({ id: 2, url: "http://some-url.com" }); tab3 = createChromeTabMock({ id: 3, url: "chrome-extension://some-extension-route" }); jest.spyOn(BrowserApi, "tabsQuery").mockResolvedValueOnce([tab1, tab2]); + jest + .spyOn(BrowserApi, "getAllFrameDetails") + .mockResolvedValue([mock({ frameId: 0 })]); jest .spyOn(autofillService, "getOverlayVisibility") .mockResolvedValue(AutofillOverlayVisibility.OnFieldFocus); @@ -219,6 +222,7 @@ describe("AutofillService", () => { jest.spyOn(autofillService, "injectAutofillScripts"); await autofillService.loadAutofillScriptsOnInstall(); + await flushPromises(); expect(BrowserApi.tabsQuery).toHaveBeenCalledWith({}); expect(autofillService.injectAutofillScripts).toHaveBeenCalledWith(tab1, 0, false); diff --git a/apps/browser/src/autofill/services/autofill.service.ts b/apps/browser/src/autofill/services/autofill.service.ts index ef051e5912b..4c37cd1f07f 100644 --- a/apps/browser/src/autofill/services/autofill.service.ts +++ b/apps/browser/src/autofill/services/autofill.service.ts @@ -2136,7 +2136,8 @@ export default class AutofillService implements AutofillServiceInterface { for (let index = 0; index < tabs.length; index++) { const tab = tabs[index]; if (tab.url?.startsWith("http")) { - void this.injectAutofillScripts(tab, 0, false); + const frames = await BrowserApi.getAllFrameDetails(tab.id); + frames.forEach((frame) => this.injectAutofillScripts(tab, frame.frameId, false)); } } } diff --git a/apps/browser/src/manifest.v3.json b/apps/browser/src/manifest.v3.json index 26b63b1229f..ed35ab3021f 100644 --- a/apps/browser/src/manifest.v3.json +++ b/apps/browser/src/manifest.v3.json @@ -62,7 +62,8 @@ "scripting", "offscreen", "webRequest", - "webRequestAuthProvider" + "webRequestAuthProvider", + "webNavigation" ], "optional_permissions": ["nativeMessaging", "privacy"], "host_permissions": ["https://*/*", "http://*/*"], diff --git a/apps/browser/src/platform/browser/browser-api.spec.ts b/apps/browser/src/platform/browser/browser-api.spec.ts index 7e0c61c9d17..adf248707c2 100644 --- a/apps/browser/src/platform/browser/browser-api.spec.ts +++ b/apps/browser/src/platform/browser/browser-api.spec.ts @@ -235,6 +235,46 @@ describe("BrowserApi", () => { }); }); + describe("getFrameDetails", () => { + it("returns the frame details of the specified frame", async () => { + const tabId = 1; + const frameId = 2; + const mockFrameDetails = mock(); + chrome.webNavigation.getFrame = jest + .fn() + .mockImplementation((_details, callback) => callback(mockFrameDetails)); + + const returnFrame = await BrowserApi.getFrameDetails({ tabId, frameId }); + + expect(chrome.webNavigation.getFrame).toHaveBeenCalledWith( + { tabId, frameId }, + expect.any(Function), + ); + expect(returnFrame).toEqual(mockFrameDetails); + }); + }); + + describe("getAllFrameDetails", () => { + it("returns all sub frame details of the specified tab", async () => { + const tabId = 1; + const mockFrameDetails1 = mock(); + const mockFrameDetails2 = mock(); + chrome.webNavigation.getAllFrames = jest + .fn() + .mockImplementation((_details, callback) => + callback([mockFrameDetails1, mockFrameDetails2]), + ); + + const frames = await BrowserApi.getAllFrameDetails(tabId); + + expect(chrome.webNavigation.getAllFrames).toHaveBeenCalledWith( + { tabId }, + expect.any(Function), + ); + expect(frames).toEqual([mockFrameDetails1, mockFrameDetails2]); + }); + }); + describe("reloadExtension", () => { it("reloads the window location if the passed globalContext is for the window", () => { const windowMock = mock({ diff --git a/apps/browser/src/platform/browser/browser-api.ts b/apps/browser/src/platform/browser/browser-api.ts index d0695d53fd1..0d461a69830 100644 --- a/apps/browser/src/platform/browser/browser-api.ts +++ b/apps/browser/src/platform/browser/browser-api.ts @@ -176,21 +176,21 @@ export class BrowserApi { return BrowserApi.tabSendMessage(tab, obj); } - static async tabSendMessage( + static async tabSendMessage( tab: chrome.tabs.Tab, obj: T, options: chrome.tabs.MessageSendOptions = null, - ): Promise { + ): Promise { if (!tab || !tab.id) { return; } - return new Promise((resolve) => { - chrome.tabs.sendMessage(tab.id, obj, options, () => { + return new Promise((resolve) => { + chrome.tabs.sendMessage(tab.id, obj, options, (response) => { if (chrome.runtime.lastError) { // Some error happened } - resolve(); + resolve(response); }); }); } @@ -263,6 +263,28 @@ export class BrowserApi { ); } + /** + * Gathers the details for a specified sub-frame of a tab. + * + * @param details - The details of the frame to get. + */ + static async getFrameDetails( + details: chrome.webNavigation.GetFrameDetails, + ): Promise { + return new Promise((resolve) => chrome.webNavigation.getFrame(details, resolve)); + } + + /** + * Gets all frames associated with a tab. + * + * @param tabId - The id of the tab to get the frames for. + */ + static async getAllFrameDetails( + tabId: chrome.tabs.Tab["id"], + ): Promise { + return new Promise((resolve) => chrome.webNavigation.getAllFrames({ tabId }, resolve)); + } + // Keep track of all the events registered in a Safari popup so we can remove // them when the popup gets unloaded, otherwise we cause a memory leak private static trackedChromeEventListeners: [ diff --git a/apps/browser/test.setup.ts b/apps/browser/test.setup.ts index 5435c6fd7fe..16ebdcbc605 100644 --- a/apps/browser/test.setup.ts +++ b/apps/browser/test.setup.ts @@ -135,6 +135,8 @@ const permissions = { }; const webNavigation = { + getFrame: jest.fn(), + getAllFrames: jest.fn(), onCommitted: { addListener: jest.fn(), removeListener: jest.fn(),