1
0
mirror of https://github.com/bitwarden/browser synced 2025-12-18 17:23:37 +00:00

[PM-5189] Refactoring implementation

This commit is contained in:
Cesar Gonzalez
2024-04-16 09:27:55 -05:00
parent 35af5b8bad
commit ac553bccc8
8 changed files with 92 additions and 96 deletions

View File

@@ -24,7 +24,7 @@ const notificationBarWindowMessageHandlers: NotificationBarWindowMessageHandlers
globalThis.addEventListener("load", load); globalThis.addEventListener("load", load);
function load() { function load() {
setupWindowMessageListener(); setupWindowMessageListener();
postMessageToConnector({ command: "initNotificationBar" }); postMessageToParent({ command: "initNotificationBar" });
} }
function initNotificationBar(message: NotificationBarWindowMessage) { function initNotificationBar(message: NotificationBarWindowMessage) {
@@ -392,6 +392,6 @@ function setNotificationBarTheme() {
document.documentElement.classList.add(`theme_${theme}`); document.documentElement.classList.add(`theme_${theme}`);
} }
function postMessageToConnector(message: NotificationBarWindowMessage) { function postMessageToParent(message: NotificationBarWindowMessage) {
globalThis.parent.postMessage(message, windowMessageOrigin || "*"); globalThis.parent.postMessage(message, windowMessageOrigin || "*");
} }

View File

@@ -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<string, string>;
ciphers?: OverlayCipherData[];
portName?: string;
};
export type AutofillOverlayMenuContainerWindowMessageHandlers = {
[key: string]: CallableFunction;
initAutofillOverlayList: (message: InitOverlayElementMessage) => void;
initAutofillOverlayButton: (message: InitOverlayElementMessage) => void;
};

View File

@@ -43,7 +43,7 @@ class AutofillOverlayIframeService implements AutofillOverlayIframeServiceInterf
private mutationObserverIterationsResetTimeout: number | NodeJS.Timeout; private mutationObserverIterationsResetTimeout: number | NodeJS.Timeout;
private readonly backgroundPortMessageHandlers: BackgroundPortMessageHandlers = { private readonly backgroundPortMessageHandlers: BackgroundPortMessageHandlers = {
initAutofillOverlayButton: ({ message }) => this.initAutofillOverlay(message), initAutofillOverlayButton: ({ message }) => this.initAutofillOverlay(message),
initAutofillOverlayList: ({ message }) => this.initAutofillOverlayList(message), initAutofillOverlayList: ({ message }) => this.initAutofillOverlay(message),
updateIframePosition: ({ message }) => this.updateIframePosition(message.styles), updateIframePosition: ({ message }) => this.updateIframePosition(message.styles),
updateOverlayHidden: ({ message }) => this.updateElementStyles(this.iframe, message.styles), updateOverlayHidden: ({ message }) => this.updateElementStyles(this.iframe, message.styles),
updateOverlayPageColorScheme: () => this.updateOverlayPageColorScheme(), updateOverlayPageColorScheme: () => this.updateOverlayPageColorScheme(),
@@ -184,6 +184,11 @@ class AutofillOverlayIframeService implements AutofillOverlayIframeServiceInterf
private initAutofillOverlay(message: AutofillOverlayIframeExtensionMessage) { private initAutofillOverlay(message: AutofillOverlayIframeExtensionMessage) {
this.portKey = message.portKey; this.portKey = message.portKey;
if (message.command === "initAutofillOverlayList") {
this.initAutofillOverlayList(message);
return;
}
this.postMessageToIFrame(message); this.postMessageToIFrame(message);
} }
@@ -195,7 +200,6 @@ class AutofillOverlayIframeService implements AutofillOverlayIframeServiceInterf
*/ */
private initAutofillOverlayList(message: AutofillOverlayIframeExtensionMessage) { private initAutofillOverlayList(message: AutofillOverlayIframeExtensionMessage) {
const { theme } = message; const { theme } = message;
this.portKey = message.portKey;
let borderColor: string; let borderColor: string;
let verifiedTheme = theme; let verifiedTheme = theme;
if (verifiedTheme === ThemeType.System) { if (verifiedTheme === ThemeType.System) {

View File

@@ -64,7 +64,7 @@ class AutofillOverlayButton extends AutofillOverlayPageElement {
this.getTranslation("toggleBitwardenVaultOverlay"), this.getTranslation("toggleBitwardenVaultOverlay"),
); );
this.buttonElement.addEventListener(EVENTS.CLICK, this.handleButtonElementClick); this.buttonElement.addEventListener(EVENTS.CLICK, this.handleButtonElementClick);
this.postMessageToConnector({ command: "updateOverlayPageColorScheme" }); this.postMessageToParent({ command: "updateOverlayPageColorScheme" });
this.updateAuthStatus(authStatus); this.updateAuthStatus(authStatus);
@@ -104,7 +104,7 @@ class AutofillOverlayButton extends AutofillOverlayPageElement {
* parent window indicating that the button was clicked. * parent window indicating that the button was clicked.
*/ */
private handleButtonElementClick = () => { private handleButtonElementClick = () => {
this.postMessageToConnector({ command: "overlayButtonClicked" }); this.postMessageToParent({ command: "overlayButtonClicked" });
}; };
/** /**
@@ -116,7 +116,7 @@ class AutofillOverlayButton extends AutofillOverlayPageElement {
return; return;
} }
this.postMessageToConnector({ command: "closeAutofillOverlay" }); this.postMessageToParent({ command: "closeAutofillOverlay" });
} }
} }

View File

@@ -107,7 +107,7 @@ class AutofillOverlayList extends AutofillOverlayPageElement {
* Sends a message to the parent window to unlock the vault. * Sends a message to the parent window to unlock the vault.
*/ */
private handleUnlockButtonClick = () => { 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. * Sends a message to the parent window to add a new vault item.
*/ */
private handeNewItemButtonClick = () => { private handeNewItemButtonClick = () => {
this.postMessageToConnector({ command: "addNewVaultItem" }); this.postMessageToParent({ command: "addNewVaultItem" });
}; };
/** /**
@@ -278,7 +278,7 @@ class AutofillOverlayList extends AutofillOverlayPageElement {
private handleFillCipherClickEvent = (cipher: OverlayCipherData) => { private handleFillCipherClickEvent = (cipher: OverlayCipherData) => {
return this.useEventHandlersMemo( return this.useEventHandlersMemo(
() => () =>
this.postMessageToConnector({ this.postMessageToParent({
command: "fillSelectedListItem", command: "fillSelectedListItem",
overlayCipherId: cipher.id, overlayCipherId: cipher.id,
}), }),
@@ -343,8 +343,7 @@ class AutofillOverlayList extends AutofillOverlayPageElement {
*/ */
private handleViewCipherClickEvent = (cipher: OverlayCipherData) => { private handleViewCipherClickEvent = (cipher: OverlayCipherData) => {
return this.useEventHandlersMemo( return this.useEventHandlersMemo(
() => () => this.postMessageToParent({ command: "viewSelectedCipher", overlayCipherId: cipher.id }),
this.postMessageToConnector({ command: "viewSelectedCipher", overlayCipherId: cipher.id }),
`${cipher.id}-view-cipher-button-click-handler`, `${cipher.id}-view-cipher-button-click-handler`,
); );
}; };
@@ -479,7 +478,7 @@ class AutofillOverlayList extends AutofillOverlayPageElement {
return; return;
} }
this.postMessageToConnector({ command: "checkAutofillOverlayButtonFocused" }); this.postMessageToParent({ command: "checkAutofillOverlayButtonFocused" });
} }
/** /**
@@ -536,7 +535,7 @@ class AutofillOverlayList extends AutofillOverlayPageElement {
} }
const { height } = entry.contentRect; const { height } = entry.contentRect;
this.postMessageToConnector({ this.postMessageToParent({
command: "updateAutofillOverlayListHeight", command: "updateAutofillOverlayListHeight",
styles: { height: `${height}px` }, styles: { height: `${height}px` },
}); });

View File

@@ -1,13 +1,16 @@
import { EVENTS } from "@bitwarden/common/autofill/constants"; import { EVENTS } from "@bitwarden/common/autofill/constants";
import { setElementStyles } from "../../../utils"; import { setElementStyles } from "../../../utils";
import {
InitOverlayElementMessage,
AutofillOverlayMenuContainerWindowMessageHandlers,
} from "../../abstractions/autofill-overlay-menu-container";
export class AutofillOverlayMenuContainer { export class AutofillOverlayMenuContainer {
private initMessage: any;
private extensionOriginsSet: Set<string>; private extensionOriginsSet: Set<string>;
private port: chrome.runtime.Port | null = null; private port: chrome.runtime.Port | null = null;
private portName: string; private portName: string;
private iframe: HTMLIFrameElement; private overlayPageIframe: HTMLIFrameElement;
private iframeStyles: Partial<CSSStyleDeclaration> = { private iframeStyles: Partial<CSSStyleDeclaration> = {
all: "initial", all: "initial",
position: "fixed", position: "fixed",
@@ -33,11 +36,10 @@ export class AutofillOverlayMenuContainer {
allowtransparency: "true", allowtransparency: "true",
tabIndex: "-1", tabIndex: "-1",
}; };
private windowMessageHandlers: Record<string, (message: any) => void> = { private windowMessageHandlers: AutofillOverlayMenuContainerWindowMessageHandlers = {
initAutofillOverlayList: (message: any) => this.handleInitOverlayIframe(message), initAutofillOverlayList: (message) => this.handleInitOverlayIframe(message),
initAutofillOverlayButton: (message: any) => this.handleInitOverlayIframe(message), initAutofillOverlayButton: (message) => this.handleInitOverlayIframe(message),
}; };
private backgroundPortMessageHandlers: Record<string, (message: any) => void> = {};
constructor() { constructor() {
this.extensionOriginsSet = new Set([ this.extensionOriginsSet = new Set([
@@ -48,31 +50,44 @@ export class AutofillOverlayMenuContainer {
globalThis.addEventListener("message", this.handleWindowMessage); globalThis.addEventListener("message", this.handleWindowMessage);
} }
private handleInitOverlayIframe(message: any) { private handleInitOverlayIframe(message: InitOverlayElementMessage) {
this.initMessage = message;
this.defaultIframeAttributes.src = message.iframeUrl; this.defaultIframeAttributes.src = message.iframeUrl;
this.defaultIframeAttributes.title = message.pageTitle; this.defaultIframeAttributes.title = message.pageTitle;
this.portName = message.portName; this.portName = message.portName;
this.iframe = globalThis.document.createElement("iframe"); this.overlayPageIframe = globalThis.document.createElement("iframe");
setElementStyles(this.iframe, this.iframeStyles, true); setElementStyles(this.overlayPageIframe, this.iframeStyles, true);
for (const [attribute, value] of Object.entries(this.defaultIframeAttributes)) { 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 = chrome.runtime.connect({ name: this.portName });
this.port.onMessage.addListener(this.handlePortMessage); this.port.onMessage.addListener(this.handlePortMessage);
this.postMessageToIframe(this.initMessage); this.postMessageToOverlayPage(message);
}; };
private postMessageToIframe(message: any) { private postMessageToOverlayPage(message: any) {
this.iframe?.contentWindow?.postMessage(message, "*"); 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) => { private handlePortMessage = (message: any, port: chrome.runtime.Port) => {
@@ -80,12 +95,7 @@ export class AutofillOverlayMenuContainer {
return; return;
} }
if (this.backgroundPortMessageHandlers[message.command]) { this.postMessageToOverlayPage(message);
this.backgroundPortMessageHandlers[message.command]({ message, port });
return;
}
this.iframe.contentWindow?.postMessage(message, "*");
}; };
private handleWindowMessage = (event: MessageEvent) => { private handleWindowMessage = (event: MessageEvent) => {
@@ -100,11 +110,11 @@ export class AutofillOverlayMenuContainer {
} }
if (event.source === globalThis.parent) { if (event.source === globalThis.parent) {
this.iframe?.contentWindow?.postMessage(message, "*"); this.postMessageToOverlayPage(message);
return; return;
} }
this.port?.postMessage(message); this.postMessageToBackground(message);
}; };
private isForeignWindowMessage(event: MessageEvent) { private isForeignWindowMessage(event: MessageEvent) {
@@ -116,20 +126,17 @@ export class AutofillOverlayMenuContainer {
return false; return false;
} }
return ( return !this.isMessageFromOverlayPageIframe(event);
this.iframe?.contentWindow !== event.source ||
!this.isFromExtensionOrigin(event.origin.toLowerCase())
);
} }
/** private isMessageFromOverlayPageIframe(event: MessageEvent): boolean {
* Chrome returns null for any sandboxed iframe sources. if (!this.overlayPageIframe) {
* Firefox references the extension URI as its origin. return false;
* Any other origin value is a security risk. }
*
* @param messageOrigin - The origin of the window message return (
*/ this.overlayPageIframe.contentWindow === event.source &&
private isFromExtensionOrigin(messageOrigin: string): boolean { this.extensionOriginsSet.has(event.origin.toLowerCase())
return this.extensionOriginsSet.has(messageOrigin); );
} }
} }

View File

@@ -61,9 +61,9 @@ describe("AutofillOverlayPageElement", () => {
}); });
}); });
describe("postMessageToConnector", () => { describe("postMessageToParent", () => {
it("posts a message to the parent", () => { it("posts a message to the parent", () => {
autofillOverlayPageElement["postMessageToConnector"]({ command: "test" }); autofillOverlayPageElement["postMessageToParent"]({ command: "test" });
expect( expect(
autofillOverlayPageElement["messageConnectorIframe"].contentWindow.postMessage, autofillOverlayPageElement["messageConnectorIframe"].contentWindow.postMessage,

View File

@@ -49,52 +49,13 @@ class AutofillOverlayPageElement extends HTMLElement {
return linkElement; 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<void>((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. * Posts a window message to the parent window.
* *
* @param message - The message to post * @param message - The message to post
*/ */
protected postMessageToConnector(message: AutofillOverlayPageElementWindowMessage) { protected postMessageToParent(message: AutofillOverlayPageElementWindowMessage) {
globalThis.parent.postMessage({ portKey: this.portKey, ...message }, "*"); 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. * Handles the window blur event.
*/ */
private handleWindowBlurEvent = () => { 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 * @param direction - The direction to redirect the focus out
*/ */
private redirectOverlayFocusOutMessage(direction: string) { private redirectOverlayFocusOutMessage(direction: string) {
this.postMessageToConnector({ command: "redirectOverlayFocusOut", direction }); this.postMessageToParent({ command: "redirectOverlayFocusOut", direction });
} }
} }