From ac553bccc85d89e19e662fd6fe5e6495a7c72f97 Mon Sep 17 00:00:00 2001 From: Cesar Gonzalez Date: Tue, 16 Apr 2024 09:27:55 -0500 Subject: [PATCH] [PM-5189] Refactoring implementation --- apps/browser/src/autofill/notification/bar.ts | 4 +- .../autofill-overlay-menu-container.ts | 25 ++++++ .../autofill-overlay-iframe.service.ts | 8 +- .../pages/button/autofill-overlay-button.ts | 6 +- .../pages/list/autofill-overlay-list.ts | 13 ++- .../menu/autofill-overlay-menu-container.ts | 83 ++++++++++--------- .../autofill-overlay-page-element.spec.ts | 4 +- .../shared/autofill-overlay-page-element.ts | 45 +--------- 8 files changed, 92 insertions(+), 96 deletions(-) diff --git a/apps/browser/src/autofill/notification/bar.ts b/apps/browser/src/autofill/notification/bar.ts index 61ea034fb0b..a730ee1ebac 100644 --- a/apps/browser/src/autofill/notification/bar.ts +++ b/apps/browser/src/autofill/notification/bar.ts @@ -24,7 +24,7 @@ const notificationBarWindowMessageHandlers: NotificationBarWindowMessageHandlers globalThis.addEventListener("load", load); function load() { setupWindowMessageListener(); - postMessageToConnector({ command: "initNotificationBar" }); + postMessageToParent({ command: "initNotificationBar" }); } function initNotificationBar(message: NotificationBarWindowMessage) { @@ -392,6 +392,6 @@ function setNotificationBarTheme() { document.documentElement.classList.add(`theme_${theme}`); } -function postMessageToConnector(message: NotificationBarWindowMessage) { +function postMessageToParent(message: NotificationBarWindowMessage) { globalThis.parent.postMessage(message, windowMessageOrigin || "*"); } diff --git a/apps/browser/src/autofill/overlay/abstractions/autofill-overlay-menu-container.ts b/apps/browser/src/autofill/overlay/abstractions/autofill-overlay-menu-container.ts index e69de29bb2d..121953c4ebd 100644 --- a/apps/browser/src/autofill/overlay/abstractions/autofill-overlay-menu-container.ts +++ b/apps/browser/src/autofill/overlay/abstractions/autofill-overlay-menu-container.ts @@ -0,0 +1,25 @@ +import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status"; + +import { OverlayCipherData } from "../../background/abstractions/overlay.background"; + +type AutofillOverlayMenuContainerMessage = { + command: string; + portKey: string; +}; + +export type InitOverlayElementMessage = AutofillOverlayMenuContainerMessage & { + iframeUrl?: string; + pageTitle?: string; + authStatus?: AuthenticationStatus; + styleSheetUrl?: string; + theme?: string; + translations?: Record; + ciphers?: OverlayCipherData[]; + portName?: string; +}; + +export type AutofillOverlayMenuContainerWindowMessageHandlers = { + [key: string]: CallableFunction; + initAutofillOverlayList: (message: InitOverlayElementMessage) => void; + initAutofillOverlayButton: (message: InitOverlayElementMessage) => void; +}; diff --git a/apps/browser/src/autofill/overlay/iframe-content/autofill-overlay-iframe.service.ts b/apps/browser/src/autofill/overlay/iframe-content/autofill-overlay-iframe.service.ts index 25e54fcd5b8..ee1b9867d0d 100644 --- a/apps/browser/src/autofill/overlay/iframe-content/autofill-overlay-iframe.service.ts +++ b/apps/browser/src/autofill/overlay/iframe-content/autofill-overlay-iframe.service.ts @@ -43,7 +43,7 @@ class AutofillOverlayIframeService implements AutofillOverlayIframeServiceInterf private mutationObserverIterationsResetTimeout: number | NodeJS.Timeout; private readonly backgroundPortMessageHandlers: BackgroundPortMessageHandlers = { initAutofillOverlayButton: ({ message }) => this.initAutofillOverlay(message), - initAutofillOverlayList: ({ message }) => this.initAutofillOverlayList(message), + initAutofillOverlayList: ({ message }) => this.initAutofillOverlay(message), updateIframePosition: ({ message }) => this.updateIframePosition(message.styles), updateOverlayHidden: ({ message }) => this.updateElementStyles(this.iframe, message.styles), updateOverlayPageColorScheme: () => this.updateOverlayPageColorScheme(), @@ -184,6 +184,11 @@ class AutofillOverlayIframeService implements AutofillOverlayIframeServiceInterf private initAutofillOverlay(message: AutofillOverlayIframeExtensionMessage) { this.portKey = message.portKey; + if (message.command === "initAutofillOverlayList") { + this.initAutofillOverlayList(message); + return; + } + this.postMessageToIFrame(message); } @@ -195,7 +200,6 @@ class AutofillOverlayIframeService implements AutofillOverlayIframeServiceInterf */ private initAutofillOverlayList(message: AutofillOverlayIframeExtensionMessage) { const { theme } = message; - this.portKey = message.portKey; let borderColor: string; let verifiedTheme = theme; if (verifiedTheme === ThemeType.System) { diff --git a/apps/browser/src/autofill/overlay/pages/button/autofill-overlay-button.ts b/apps/browser/src/autofill/overlay/pages/button/autofill-overlay-button.ts index 1921823dd7a..802ab51d218 100644 --- a/apps/browser/src/autofill/overlay/pages/button/autofill-overlay-button.ts +++ b/apps/browser/src/autofill/overlay/pages/button/autofill-overlay-button.ts @@ -64,7 +64,7 @@ class AutofillOverlayButton extends AutofillOverlayPageElement { this.getTranslation("toggleBitwardenVaultOverlay"), ); this.buttonElement.addEventListener(EVENTS.CLICK, this.handleButtonElementClick); - this.postMessageToConnector({ command: "updateOverlayPageColorScheme" }); + this.postMessageToParent({ command: "updateOverlayPageColorScheme" }); this.updateAuthStatus(authStatus); @@ -104,7 +104,7 @@ class AutofillOverlayButton extends AutofillOverlayPageElement { * parent window indicating that the button was clicked. */ private handleButtonElementClick = () => { - this.postMessageToConnector({ command: "overlayButtonClicked" }); + this.postMessageToParent({ command: "overlayButtonClicked" }); }; /** @@ -116,7 +116,7 @@ class AutofillOverlayButton extends AutofillOverlayPageElement { return; } - this.postMessageToConnector({ command: "closeAutofillOverlay" }); + this.postMessageToParent({ command: "closeAutofillOverlay" }); } } diff --git a/apps/browser/src/autofill/overlay/pages/list/autofill-overlay-list.ts b/apps/browser/src/autofill/overlay/pages/list/autofill-overlay-list.ts index 94b3fc994de..9838c75d2fb 100644 --- a/apps/browser/src/autofill/overlay/pages/list/autofill-overlay-list.ts +++ b/apps/browser/src/autofill/overlay/pages/list/autofill-overlay-list.ts @@ -107,7 +107,7 @@ class AutofillOverlayList extends AutofillOverlayPageElement { * Sends a message to the parent window to unlock the vault. */ private handleUnlockButtonClick = () => { - this.postMessageToConnector({ command: "unlockVault" }); + this.postMessageToParent({ command: "unlockVault" }); }; /** @@ -171,7 +171,7 @@ class AutofillOverlayList extends AutofillOverlayPageElement { * Sends a message to the parent window to add a new vault item. */ private handeNewItemButtonClick = () => { - this.postMessageToConnector({ command: "addNewVaultItem" }); + this.postMessageToParent({ command: "addNewVaultItem" }); }; /** @@ -278,7 +278,7 @@ class AutofillOverlayList extends AutofillOverlayPageElement { private handleFillCipherClickEvent = (cipher: OverlayCipherData) => { return this.useEventHandlersMemo( () => - this.postMessageToConnector({ + this.postMessageToParent({ command: "fillSelectedListItem", overlayCipherId: cipher.id, }), @@ -343,8 +343,7 @@ class AutofillOverlayList extends AutofillOverlayPageElement { */ private handleViewCipherClickEvent = (cipher: OverlayCipherData) => { return this.useEventHandlersMemo( - () => - this.postMessageToConnector({ command: "viewSelectedCipher", overlayCipherId: cipher.id }), + () => this.postMessageToParent({ command: "viewSelectedCipher", overlayCipherId: cipher.id }), `${cipher.id}-view-cipher-button-click-handler`, ); }; @@ -479,7 +478,7 @@ class AutofillOverlayList extends AutofillOverlayPageElement { return; } - this.postMessageToConnector({ command: "checkAutofillOverlayButtonFocused" }); + this.postMessageToParent({ command: "checkAutofillOverlayButtonFocused" }); } /** @@ -536,7 +535,7 @@ class AutofillOverlayList extends AutofillOverlayPageElement { } const { height } = entry.contentRect; - this.postMessageToConnector({ + this.postMessageToParent({ command: "updateAutofillOverlayListHeight", styles: { height: `${height}px` }, }); diff --git a/apps/browser/src/autofill/overlay/pages/menu/autofill-overlay-menu-container.ts b/apps/browser/src/autofill/overlay/pages/menu/autofill-overlay-menu-container.ts index 1d74cf6b6f7..e0f5e14dc49 100644 --- a/apps/browser/src/autofill/overlay/pages/menu/autofill-overlay-menu-container.ts +++ b/apps/browser/src/autofill/overlay/pages/menu/autofill-overlay-menu-container.ts @@ -1,13 +1,16 @@ import { EVENTS } from "@bitwarden/common/autofill/constants"; import { setElementStyles } from "../../../utils"; +import { + InitOverlayElementMessage, + AutofillOverlayMenuContainerWindowMessageHandlers, +} from "../../abstractions/autofill-overlay-menu-container"; export class AutofillOverlayMenuContainer { - private initMessage: any; private extensionOriginsSet: Set; private port: chrome.runtime.Port | null = null; private portName: string; - private iframe: HTMLIFrameElement; + private overlayPageIframe: HTMLIFrameElement; private iframeStyles: Partial = { all: "initial", position: "fixed", @@ -33,11 +36,10 @@ export class AutofillOverlayMenuContainer { allowtransparency: "true", tabIndex: "-1", }; - private windowMessageHandlers: Record void> = { - initAutofillOverlayList: (message: any) => this.handleInitOverlayIframe(message), - initAutofillOverlayButton: (message: any) => this.handleInitOverlayIframe(message), + private windowMessageHandlers: AutofillOverlayMenuContainerWindowMessageHandlers = { + initAutofillOverlayList: (message) => this.handleInitOverlayIframe(message), + initAutofillOverlayButton: (message) => this.handleInitOverlayIframe(message), }; - private backgroundPortMessageHandlers: Record void> = {}; constructor() { this.extensionOriginsSet = new Set([ @@ -48,31 +50,44 @@ export class AutofillOverlayMenuContainer { globalThis.addEventListener("message", this.handleWindowMessage); } - private handleInitOverlayIframe(message: any) { - this.initMessage = message; + private handleInitOverlayIframe(message: InitOverlayElementMessage) { this.defaultIframeAttributes.src = message.iframeUrl; this.defaultIframeAttributes.title = message.pageTitle; this.portName = message.portName; - this.iframe = globalThis.document.createElement("iframe"); - setElementStyles(this.iframe, this.iframeStyles, true); + this.overlayPageIframe = globalThis.document.createElement("iframe"); + setElementStyles(this.overlayPageIframe, this.iframeStyles, true); for (const [attribute, value] of Object.entries(this.defaultIframeAttributes)) { - this.iframe.setAttribute(attribute, value); + this.overlayPageIframe.setAttribute(attribute, value); } - this.iframe.addEventListener(EVENTS.LOAD, this.setupPortMessageListener); + this.overlayPageIframe.addEventListener(EVENTS.LOAD, () => + this.setupPortMessageListener(message), + ); - globalThis.document.body.appendChild(this.iframe); + globalThis.document.body.appendChild(this.overlayPageIframe); } - private setupPortMessageListener = () => { + private setupPortMessageListener = (message: InitOverlayElementMessage) => { this.port = chrome.runtime.connect({ name: this.portName }); this.port.onMessage.addListener(this.handlePortMessage); - this.postMessageToIframe(this.initMessage); + this.postMessageToOverlayPage(message); }; - private postMessageToIframe(message: any) { - this.iframe?.contentWindow?.postMessage(message, "*"); + private postMessageToOverlayPage(message: any) { + if (!this.overlayPageIframe?.contentWindow) { + return; + } + + this.overlayPageIframe.contentWindow.postMessage(message, "*"); + } + + private postMessageToBackground(message: any) { + if (!this.port) { + return; + } + + this.port.postMessage(message); } private handlePortMessage = (message: any, port: chrome.runtime.Port) => { @@ -80,12 +95,7 @@ export class AutofillOverlayMenuContainer { return; } - if (this.backgroundPortMessageHandlers[message.command]) { - this.backgroundPortMessageHandlers[message.command]({ message, port }); - return; - } - - this.iframe.contentWindow?.postMessage(message, "*"); + this.postMessageToOverlayPage(message); }; private handleWindowMessage = (event: MessageEvent) => { @@ -100,11 +110,11 @@ export class AutofillOverlayMenuContainer { } if (event.source === globalThis.parent) { - this.iframe?.contentWindow?.postMessage(message, "*"); + this.postMessageToOverlayPage(message); return; } - this.port?.postMessage(message); + this.postMessageToBackground(message); }; private isForeignWindowMessage(event: MessageEvent) { @@ -116,20 +126,17 @@ export class AutofillOverlayMenuContainer { return false; } - return ( - this.iframe?.contentWindow !== event.source || - !this.isFromExtensionOrigin(event.origin.toLowerCase()) - ); + return !this.isMessageFromOverlayPageIframe(event); } - /** - * Chrome returns null for any sandboxed iframe sources. - * Firefox references the extension URI as its origin. - * Any other origin value is a security risk. - * - * @param messageOrigin - The origin of the window message - */ - private isFromExtensionOrigin(messageOrigin: string): boolean { - return this.extensionOriginsSet.has(messageOrigin); + private isMessageFromOverlayPageIframe(event: MessageEvent): boolean { + if (!this.overlayPageIframe) { + return false; + } + + return ( + this.overlayPageIframe.contentWindow === event.source && + this.extensionOriginsSet.has(event.origin.toLowerCase()) + ); } } diff --git a/apps/browser/src/autofill/overlay/pages/shared/autofill-overlay-page-element.spec.ts b/apps/browser/src/autofill/overlay/pages/shared/autofill-overlay-page-element.spec.ts index 7c0585778fc..2622e02d79b 100644 --- a/apps/browser/src/autofill/overlay/pages/shared/autofill-overlay-page-element.spec.ts +++ b/apps/browser/src/autofill/overlay/pages/shared/autofill-overlay-page-element.spec.ts @@ -61,9 +61,9 @@ describe("AutofillOverlayPageElement", () => { }); }); - describe("postMessageToConnector", () => { + describe("postMessageToParent", () => { it("posts a message to the parent", () => { - autofillOverlayPageElement["postMessageToConnector"]({ command: "test" }); + autofillOverlayPageElement["postMessageToParent"]({ command: "test" }); expect( autofillOverlayPageElement["messageConnectorIframe"].contentWindow.postMessage, diff --git a/apps/browser/src/autofill/overlay/pages/shared/autofill-overlay-page-element.ts b/apps/browser/src/autofill/overlay/pages/shared/autofill-overlay-page-element.ts index 6ce0d7685a6..10ecd591f6f 100644 --- a/apps/browser/src/autofill/overlay/pages/shared/autofill-overlay-page-element.ts +++ b/apps/browser/src/autofill/overlay/pages/shared/autofill-overlay-page-element.ts @@ -49,52 +49,13 @@ class AutofillOverlayPageElement extends HTMLElement { return linkElement; } - /** - * Initializes an iframe that acts as the communication bridge between - * the inline menu UI and the background script. Awaits the load event - * of the iframe before initializing the inline menu element to ensure - * messages from the inline menu are not missed. - * - * @param messageConnectorUrl - The URL of the message connector to use - * @param elementName - The name of the element, e.g. "button" or "list" - */ - // private initMessageConnector(messageConnectorUrl: string, elementName: "button" | "list") { - // this.messageConnectorIframe = globalThis.document.createElement("iframe"); - // this.messageConnectorIframe.src = messageConnectorUrl; - // this.messageConnectorIframe.style.opacity = "0"; - // this.messageConnectorIframe.style.position = "absolute"; - // this.messageConnectorIframe.style.width = "0"; - // this.messageConnectorIframe.style.height = "0"; - // this.messageConnectorIframe.style.border = "none"; - // this.messageConnectorIframe.style.pointerEvents = "none"; - // globalThis.document.body.appendChild(this.messageConnectorIframe); - // - // return new Promise((resolve) => { - // this.messageConnectorIframe.addEventListener(EVENTS.LOAD, () => { - // this.postMessageToConnector({ - // command: `initAutofillOverlayPort`, - // portName: - // elementName === "list" - // ? AutofillOverlayPort.ListMessageConnector - // : AutofillOverlayPort.ButtonMessageConnector, - // }); - // resolve(); - // }); - // }); - // } - /** * Posts a window message to the parent window. * * @param message - The message to post */ - protected postMessageToConnector(message: AutofillOverlayPageElementWindowMessage) { + protected postMessageToParent(message: AutofillOverlayPageElementWindowMessage) { globalThis.parent.postMessage({ portKey: this.portKey, ...message }, "*"); - - // this.messageConnectorIframe.contentWindow.postMessage( - // { portKey: this.portKey, ...message }, - // "*", - // ); } /** @@ -152,7 +113,7 @@ class AutofillOverlayPageElement extends HTMLElement { * Handles the window blur event. */ private handleWindowBlurEvent = () => { - this.postMessageToConnector({ command: "overlayPageBlurred" }); + this.postMessageToParent({ command: "overlayPageBlurred" }); }; /** @@ -189,7 +150,7 @@ class AutofillOverlayPageElement extends HTMLElement { * @param direction - The direction to redirect the focus out */ private redirectOverlayFocusOutMessage(direction: string) { - this.postMessageToConnector({ command: "redirectOverlayFocusOut", direction }); + this.postMessageToParent({ command: "redirectOverlayFocusOut", direction }); } }