diff --git a/apps/browser/src/autofill/background/abstractions/overlay.background.ts b/apps/browser/src/autofill/background/abstractions/overlay.background.ts index 1acab3e0e1..6cbaebc6e4 100644 --- a/apps/browser/src/autofill/background/abstractions/overlay.background.ts +++ b/apps/browser/src/autofill/background/abstractions/overlay.background.ts @@ -73,13 +73,6 @@ export type OverlayBackgroundExtensionMessage = { CloseInlineMenuMessage & ToggleInlineMenuHiddenMessage; -export type OverlayPortMessage = { - [key: string]: any; - command: string; - direction?: string; - inlineMenuCipherId?: string; -}; - export type InlineMenuCipherData = { id: string; name: string; @@ -101,7 +94,7 @@ export type BackgroundOnMessageHandlerParams = BackgroundMessageParam & Backgrou export type OverlayBackgroundExtensionMessageHandlers = { [key: string]: CallableFunction; - autofillOverlayElementClosed: ({ message, sender }: BackgroundOnMessageHandlerParams) => void; + autofillOverlayAddNewVaultItem: ({ message, sender }: BackgroundOnMessageHandlerParams) => void; triggerAutofillOverlayReposition: ({ message, sender }: BackgroundOnMessageHandlerParams) => void; checkIsInlineMenuCiphersPopulated: ({ sender }: BackgroundSenderParam) => void; @@ -137,6 +130,11 @@ export type OverlayBackgroundExtensionMessageHandlers = { deletedCipher: () => void; }; +export type OverlayPortMessage = OverlayBackgroundExtensionMessage & { + direction?: string; + inlineMenuCipherId?: string; +}; + export type PortMessageParam = { message: OverlayPortMessage; }; @@ -145,6 +143,11 @@ export type PortConnectionParam = { }; export type PortOnMessageHandlerParams = PortMessageParam & PortConnectionParam; +export type OverlayContentScriptPortMessageHandlers = { + [key: string]: CallableFunction; + autofillOverlayElementClosed: ({ message, port }: PortOnMessageHandlerParams) => void; +}; + export type InlineMenuButtonPortMessageHandlers = { [key: string]: CallableFunction; triggerDelayedAutofillInlineMenuClosure: ({ port }: PortConnectionParam) => void; diff --git a/apps/browser/src/autofill/background/overlay.background.ts b/apps/browser/src/autofill/background/overlay.background.ts index 2f21b795c3..ea925a20ca 100644 --- a/apps/browser/src/autofill/background/overlay.background.ts +++ b/apps/browser/src/autofill/background/overlay.background.ts @@ -50,6 +50,7 @@ import { SubFrameOffsetsForTab, CloseInlineMenuMessage, ToggleInlineMenuHiddenMessage, + OverlayContentScriptPortMessageHandlers, } from "./abstractions/overlay.background"; export class OverlayBackground implements OverlayBackgroundInterface { @@ -75,8 +76,6 @@ export class OverlayBackground implements OverlayBackgroundInterface { private isFieldCurrentlyFilling: boolean = false; private iconsServerUrl: string; private readonly extensionMessageHandlers: OverlayBackgroundExtensionMessageHandlers = { - autofillOverlayElementClosed: ({ message, sender }) => - this.overlayElementClosed(message, sender), autofillOverlayAddNewVaultItem: ({ message, sender }) => this.addNewVaultItem(message, sender), triggerAutofillOverlayReposition: ({ sender }) => this.triggerOverlayReposition(sender), checkIsInlineMenuCiphersPopulated: ({ sender }) => @@ -110,6 +109,9 @@ export class OverlayBackground implements OverlayBackgroundInterface { editedCipher: () => this.updateInlineMenuCiphers(), deletedCipher: () => this.updateInlineMenuCiphers(), }; + private readonly contentScriptPortMessageHandlers: OverlayContentScriptPortMessageHandlers = { + autofillOverlayElementClosed: ({ message, port }) => this.overlayElementClosed(message, port), + }; private readonly inlineMenuButtonPortMessageHandlers: InlineMenuButtonPortMessageHandlers = { triggerDelayedAutofillInlineMenuClosure: ({ port }) => this.triggerDelayedInlineMenuClosure(), autofillInlineMenuButtonClicked: ({ port }) => this.handleInlineMenuButtonClicked(port), @@ -612,13 +614,13 @@ export class OverlayBackground implements OverlayBackgroundInterface { * the list and button ports and sets them to null. * * @param overlayElement - The overlay element that was closed, either the list or button - * @param sender - The sender of the port message + * @param port - The port that sent the message */ private overlayElementClosed( { overlayElement }: OverlayBackgroundExtensionMessage, - sender: chrome.runtime.MessageSender, + port: chrome.runtime.Port, ) { - if (sender.tab.id !== this.focusedFieldData?.tabId) { + if (port.sender.tab.id !== this.focusedFieldData?.tabId) { this.expiredPorts.forEach((port) => port.disconnect()); this.expiredPorts = []; return; @@ -1217,6 +1219,11 @@ export class OverlayBackground implements OverlayBackgroundInterface { * @param port - The port that connected to the extension background */ private handlePortOnConnect = async (port: chrome.runtime.Port) => { + if (port.name === AutofillOverlayPort.ContentScript) { + port.onMessage.addListener(this.handleContentScriptPortMessage); + return; + } + const isInlineMenuListMessageConnector = port.name === AutofillOverlayPort.ListMessageConnector; const isInlineMenuButtonMessageConnector = port.name === AutofillOverlayPort.ButtonMessageConnector; @@ -1293,6 +1300,21 @@ export class OverlayBackground implements OverlayBackgroundInterface { } } + private handleContentScriptPortMessage = ( + message: OverlayPortMessage, + port: chrome.runtime.Port, + ) => { + if (port.name !== AutofillOverlayPort.ContentScript) { + return; + } + + const handler: CallableFunction | undefined = + this.contentScriptPortMessageHandlers[message.command]; + if (handler) { + handler({ message, port }); + } + }; + /** * Handles messages sent to the overlay list or button ports. * @@ -1300,7 +1322,7 @@ export class OverlayBackground implements OverlayBackgroundInterface { * @param port - The port that sent the message */ private handleOverlayElementPortMessage = ( - message: OverlayBackgroundExtensionMessage, + message: OverlayPortMessage, port: chrome.runtime.Port, ) => { const tabPortKey = this.portKeyForTab[port.sender.tab.id]; diff --git a/apps/browser/src/autofill/content/autofill-init.ts b/apps/browser/src/autofill/content/autofill-init.ts index 70f815d223..e5a30eca37 100644 --- a/apps/browser/src/autofill/content/autofill-init.ts +++ b/apps/browser/src/autofill/content/autofill-init.ts @@ -33,14 +33,14 @@ class AutofillInit implements AutofillInitInterface { * CollectAutofillContentService and InsertAutofillContentService classes. * * @param autofillOverlayContentService - The autofill overlay content service, potentially undefined. - * @param inlineMenuElements - The inline menu elements, potentially undefined. + * @param autofillInlineMenuContentService - The inline menu elements, potentially undefined. */ constructor( autofillOverlayContentService?: AutofillOverlayContentService, - inlineMenuElements?: AutofillInlineMenuContentService, + autofillInlineMenuContentService?: AutofillInlineMenuContentService, ) { this.autofillOverlayContentService = autofillOverlayContentService; - this.autofillInlineMenuContentService = inlineMenuElements; + this.autofillInlineMenuContentService = autofillInlineMenuContentService; this.domElementVisibilityService = new DomElementVisibilityService( this.autofillInlineMenuContentService, ); diff --git a/apps/browser/src/autofill/content/bootstrap-autofill-overlay.ts b/apps/browser/src/autofill/content/bootstrap-autofill-overlay.ts index 2243022766..50376e741c 100644 --- a/apps/browser/src/autofill/content/bootstrap-autofill-overlay.ts +++ b/apps/browser/src/autofill/content/bootstrap-autofill-overlay.ts @@ -1,3 +1,4 @@ +import { AutofillOverlayPort } from "../enums/autofill-overlay.enum"; import { AutofillInlineMenuContentService } from "../overlay/inline-menu/content/autofill-inline-menu-content.service"; import { AutofillOverlayContentService } from "../services/autofill-overlay-content.service"; import { InlineMenuFieldQualificationService } from "../services/inline-menu-field-qualification.service"; @@ -7,17 +8,19 @@ import AutofillInit from "./autofill-init"; (function (windowContext) { if (!windowContext.bitwardenAutofillInit) { + const overlayPort = chrome.runtime.connect({ name: AutofillOverlayPort.ContentScript }); const inlineMenuFieldQualificationService = new InlineMenuFieldQualificationService(); const autofillOverlayContentService = new AutofillOverlayContentService( + overlayPort, inlineMenuFieldQualificationService, ); - let inlineMenuElements: AutofillInlineMenuContentService; + let autofillInlineMenuContentService: AutofillInlineMenuContentService; if (globalThis.self === globalThis.top) { - inlineMenuElements = new AutofillInlineMenuContentService(); + autofillInlineMenuContentService = new AutofillInlineMenuContentService(overlayPort); } windowContext.bitwardenAutofillInit = new AutofillInit( autofillOverlayContentService, - inlineMenuElements, + autofillInlineMenuContentService, ); setupAutofillInitDisconnectAction(windowContext); diff --git a/apps/browser/src/autofill/enums/autofill-overlay.enum.ts b/apps/browser/src/autofill/enums/autofill-overlay.enum.ts index 8dc99e9c40..ea9eb0eb40 100644 --- a/apps/browser/src/autofill/enums/autofill-overlay.enum.ts +++ b/apps/browser/src/autofill/enums/autofill-overlay.enum.ts @@ -4,6 +4,7 @@ export const AutofillOverlayElement = { } as const; export const AutofillOverlayPort = { + ContentScript: "autofill-overlay-content-script-port", Button: "autofill-inline-menu-button-port", ButtonMessageConnector: "autofill-inline-menu-button-message-connector", List: "autofill-inline-menu-list-port", diff --git a/apps/browser/src/autofill/overlay/inline-menu/content/autofill-inline-menu-content.service.spec.ts b/apps/browser/src/autofill/overlay/inline-menu/content/autofill-inline-menu-content.service.spec.ts index 147ffc612a..d816df6cbd 100644 --- a/apps/browser/src/autofill/overlay/inline-menu/content/autofill-inline-menu-content.service.spec.ts +++ b/apps/browser/src/autofill/overlay/inline-menu/content/autofill-inline-menu-content.service.spec.ts @@ -1,14 +1,15 @@ import { mock } from "jest-mock-extended"; import AutofillInit from "../../../content/autofill-init"; -import { AutofillOverlayElement } from "../../../enums/autofill-overlay.enum"; -import { createMutationRecordMock } from "../../../spec/autofill-mocks"; +import { AutofillOverlayElement, AutofillOverlayPort } from "../../../enums/autofill-overlay.enum"; +import { createMutationRecordMock, createPortSpyMock } from "../../../spec/autofill-mocks"; import { flushPromises, sendMockExtensionMessage } from "../../../spec/testing-utils"; import { ElementWithOpId } from "../../../types"; import { AutofillInlineMenuContentService } from "./autofill-inline-menu-content.service"; describe("AutofillInlineMenuContentService", () => { + let overlayPort: chrome.runtime.Port; let autofillInlineMenuContentService: AutofillInlineMenuContentService; let autofillInit: AutofillInit; let sendExtensionMessageSpy: jest.SpyInstance; @@ -17,7 +18,8 @@ describe("AutofillInlineMenuContentService", () => { beforeEach(() => { globalThis.document.body.innerHTML = ""; - autofillInlineMenuContentService = new AutofillInlineMenuContentService(); + overlayPort = createPortSpyMock(AutofillOverlayPort.ContentScript); + autofillInlineMenuContentService = new AutofillInlineMenuContentService(overlayPort); autofillInit = new AutofillInit(null, autofillInlineMenuContentService); autofillInit.init(); observeBodyMutationsSpy = jest.spyOn( diff --git a/apps/browser/src/autofill/overlay/inline-menu/content/autofill-inline-menu-content.service.ts b/apps/browser/src/autofill/overlay/inline-menu/content/autofill-inline-menu-content.service.ts index a9e3b541d3..dbdc7d6893 100644 --- a/apps/browser/src/autofill/overlay/inline-menu/content/autofill-inline-menu-content.service.ts +++ b/apps/browser/src/autofill/overlay/inline-menu/content/autofill-inline-menu-content.service.ts @@ -41,7 +41,7 @@ export class AutofillInlineMenuContentService implements AutofillInlineMenuConte checkIsAutofillInlineMenuListVisible: () => this.isInlineMenuListVisible(), }; - constructor() { + constructor(private port: chrome.runtime.Port) { this.setupMutationObserver(); } @@ -115,7 +115,7 @@ export class AutofillInlineMenuContentService implements AutofillInlineMenuConte if (this.buttonElement) { this.buttonElement.remove(); this.isButtonVisible = false; - void this.sendExtensionMessage("autofillOverlayElementClosed", { + this.sendPortMessage("autofillOverlayElementClosed", { overlayElement: AutofillOverlayElement.Button, }); } @@ -128,7 +128,7 @@ export class AutofillInlineMenuContentService implements AutofillInlineMenuConte if (this.listElement) { this.listElement.remove(); this.isListVisible = false; - void this.sendExtensionMessage("autofillOverlayElementClosed", { + this.sendPortMessage("autofillOverlayElementClosed", { overlayElement: AutofillOverlayElement.List, }); } @@ -421,6 +421,16 @@ export class AutofillInlineMenuContentService implements AutofillInlineMenuConte return false; } + /** + * Sends a message through the port to the background script. + * + * @param command - The command to send through the port. + * @param message - The message to send through the port. + */ + private sendPortMessage(command: string, message: Omit) { + this.port.postMessage({ command, ...message }); + } + /** * Disconnects the mutation observers and removes the inline menu elements from the DOM. */ 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 1efd1356cf..da9e7f6200 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 @@ -6,13 +6,14 @@ import { AutofillOverlayVisibility, EVENTS } from "@bitwarden/common/autofill/co import AutofillInit from "../content/autofill-init"; import { AutofillOverlayElement, + AutofillOverlayPort, MAX_SUB_FRAME_DEPTH, RedirectFocusDirection, } from "../enums/autofill-overlay.enum"; import AutofillField from "../models/autofill-field"; import AutofillForm from "../models/autofill-form"; import AutofillPageDetails from "../models/autofill-page-details"; -import { createAutofillFieldMock } from "../spec/autofill-mocks"; +import { createAutofillFieldMock, createPortSpyMock } from "../spec/autofill-mocks"; import { flushPromises, postWindowMessage, sendMockExtensionMessage } from "../spec/testing-utils"; import { ElementWithOpId, FillableFormFieldElement, FormFieldElement } from "../types"; @@ -25,6 +26,7 @@ const defaultDocumentVisibilityState = document.visibilityState; describe("AutofillOverlayContentService", () => { let autofillInit: AutofillInit; let inlineMenuFieldQualificationService: InlineMenuFieldQualificationService; + let overlayPort: chrome.runtime.Port; let autofillOverlayContentService: AutofillOverlayContentService; let sendExtensionMessageSpy: jest.SpyInstance; const sendResponseSpy = jest.fn(); @@ -39,8 +41,10 @@ describe("AutofillOverlayContentService", () => { }); beforeEach(() => { + overlayPort = createPortSpyMock(AutofillOverlayPort.ContentScript); inlineMenuFieldQualificationService = new InlineMenuFieldQualificationService(); autofillOverlayContentService = new AutofillOverlayContentService( + overlayPort, inlineMenuFieldQualificationService, ); autofillInit = new AutofillInit(autofillOverlayContentService); 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 b3c4ca6aee..12824e92b3 100644 --- a/apps/browser/src/autofill/services/autofill-overlay-content.service.ts +++ b/apps/browser/src/autofill/services/autofill-overlay-content.service.ts @@ -79,7 +79,10 @@ export class AutofillOverlayContentService implements AutofillOverlayContentServ destroyAutofillInlineMenuListeners: () => this.destroy(), }; - constructor(private inlineMenuFieldQualificationService: InlineMenuFieldQualificationService) {} + constructor( + private port: chrome.runtime.Port, + private inlineMenuFieldQualificationService: InlineMenuFieldQualificationService, + ) {} /** * Initializes the autofill overlay content service by setting up the mutation observers. diff --git a/apps/browser/src/autofill/services/collect-autofill-content.service.spec.ts b/apps/browser/src/autofill/services/collect-autofill-content.service.spec.ts index f67c0e88aa..23682a8577 100644 --- a/apps/browser/src/autofill/services/collect-autofill-content.service.spec.ts +++ b/apps/browser/src/autofill/services/collect-autofill-content.service.spec.ts @@ -1,8 +1,13 @@ import { mock } from "jest-mock-extended"; +import { AutofillOverlayPort } from "../enums/autofill-overlay.enum"; import AutofillField from "../models/autofill-field"; import AutofillForm from "../models/autofill-form"; -import { createAutofillFieldMock, createAutofillFormMock } from "../spec/autofill-mocks"; +import { + createAutofillFieldMock, + createAutofillFormMock, + createPortSpyMock, +} from "../spec/autofill-mocks"; import { mockQuerySelectorAllDefinedCall } from "../spec/testing-utils"; import { ElementWithOpId, @@ -28,9 +33,11 @@ const mockLoginForm = ` const waitForIdleCallback = () => new Promise((resolve) => globalThis.requestIdleCallback(resolve)); describe("CollectAutofillContentService", () => { + const overlayPort = createPortSpyMock(AutofillOverlayPort.ContentScript); const domElementVisibilityService = new DomElementVisibilityService(); const inlineMenuFieldQualificationService = mock(); const autofillOverlayContentService = new AutofillOverlayContentService( + overlayPort, inlineMenuFieldQualificationService, ); let collectAutofillContentService: CollectAutofillContentService; diff --git a/apps/browser/src/autofill/services/insert-autofill-content.service.spec.ts b/apps/browser/src/autofill/services/insert-autofill-content.service.spec.ts index ff0e82d664..65cbe3424f 100644 --- a/apps/browser/src/autofill/services/insert-autofill-content.service.spec.ts +++ b/apps/browser/src/autofill/services/insert-autofill-content.service.spec.ts @@ -2,7 +2,9 @@ import { mock } from "jest-mock-extended"; import { EVENTS } from "@bitwarden/common/autofill/constants"; +import { AutofillOverlayPort } from "../enums/autofill-overlay.enum"; import AutofillScript, { FillScript, FillScriptActions } from "../models/autofill-script"; +import { createPortSpyMock } from "../spec/autofill-mocks"; import { mockQuerySelectorAllDefinedCall } from "../spec/testing-utils"; import { FillableFormFieldElement, FormElementWithAttribute, FormFieldElement } from "../types"; @@ -67,9 +69,11 @@ function setMockWindowLocation({ } describe("InsertAutofillContentService", () => { + const overlayPort = createPortSpyMock(AutofillOverlayPort.ContentScript); const inlineMenuFieldQualificationService = mock(); const domElementVisibilityService = new DomElementVisibilityService(); const autofillOverlayContentService = new AutofillOverlayContentService( + overlayPort, inlineMenuFieldQualificationService, ); const collectAutofillContentService = new CollectAutofillContentService(