mirror of
https://github.com/bitwarden/browser
synced 2025-12-16 08:13:42 +00:00
[PM-5189] Implementing jest tests for AutofillInlineMenuContentService
This commit is contained in:
@@ -2,7 +2,7 @@ import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authenticatio
|
||||
|
||||
import { InlineMenuCipherData } from "../../../background/abstractions/overlay.background";
|
||||
|
||||
type AutofillInlineMenuContainerMessage = {
|
||||
export type AutofillInlineMenuContainerMessage = {
|
||||
command: string;
|
||||
portKey: string;
|
||||
};
|
||||
@@ -18,8 +18,14 @@ export type InitAutofillInlineMenuElementMessage = AutofillInlineMenuContainerMe
|
||||
portName: string;
|
||||
};
|
||||
|
||||
export type AutofillInlineMenuContainerWindowMessage = AutofillInlineMenuContainerMessage &
|
||||
Record<string, unknown>;
|
||||
|
||||
export type AutofillInlineMenuContainerPortMessage = AutofillInlineMenuContainerMessage &
|
||||
Record<string, unknown>;
|
||||
|
||||
export type AutofillInlineMenuContainerWindowMessageHandlers = {
|
||||
[key: string]: CallableFunction;
|
||||
initAutofillInlineMenuList: (message: InitAutofillInlineMenuElementMessage) => void;
|
||||
initAutofillInlineMenuButton: (message: InitAutofillInlineMenuElementMessage) => void;
|
||||
initAutofillInlineMenuList: (message: InitAutofillInlineMenuElementMessage) => void;
|
||||
};
|
||||
|
||||
@@ -0,0 +1,130 @@
|
||||
import { AutofillOverlayPort } from "../../../../enums/autofill-overlay.enum";
|
||||
import { createPortSpyMock } from "../../../../spec/autofill-mocks";
|
||||
import { postWindowMessage } from "../../../../spec/testing-utils";
|
||||
|
||||
import { AutofillInlineMenuContainer } from "./autofill-inline-menu-container";
|
||||
|
||||
describe("AutofillInlineMenuContainer", () => {
|
||||
const portKey = "testPortKey";
|
||||
const iframeUrl = "https://example.com";
|
||||
const pageTitle = "Example";
|
||||
let autofillInlineMenuContainer: AutofillInlineMenuContainer;
|
||||
|
||||
beforeEach(() => {
|
||||
autofillInlineMenuContainer = new AutofillInlineMenuContainer();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
describe("initializing the inline menu iframe", () => {
|
||||
it("sets the default iframe attributes to the message values", () => {
|
||||
const message = {
|
||||
command: "initAutofillInlineMenuList",
|
||||
iframeUrl,
|
||||
pageTitle,
|
||||
portKey,
|
||||
portName: AutofillOverlayPort.List,
|
||||
};
|
||||
|
||||
postWindowMessage(message);
|
||||
|
||||
expect(autofillInlineMenuContainer["defaultIframeAttributes"].src).toBe(message.iframeUrl);
|
||||
expect(autofillInlineMenuContainer["defaultIframeAttributes"].title).toBe(message.pageTitle);
|
||||
expect(autofillInlineMenuContainer["portName"]).toBe(message.portName);
|
||||
});
|
||||
|
||||
it("sets up a onLoad listener on the iframe that sets up the background port message listener", async () => {
|
||||
const message = {
|
||||
command: "initAutofillInlineMenuButton",
|
||||
iframeUrl,
|
||||
pageTitle,
|
||||
portKey,
|
||||
portName: AutofillOverlayPort.Button,
|
||||
};
|
||||
|
||||
postWindowMessage(message);
|
||||
|
||||
jest.spyOn(autofillInlineMenuContainer["inlineMenuPageIframe"].contentWindow, "postMessage");
|
||||
autofillInlineMenuContainer["inlineMenuPageIframe"].dispatchEvent(new Event("load"));
|
||||
|
||||
expect(chrome.runtime.connect).toHaveBeenCalledWith({ name: message.portName });
|
||||
expect(
|
||||
autofillInlineMenuContainer["inlineMenuPageIframe"].contentWindow.postMessage,
|
||||
).toHaveBeenCalledWith(message, "*");
|
||||
});
|
||||
});
|
||||
|
||||
describe("handling window messages", () => {
|
||||
let iframe: HTMLIFrameElement;
|
||||
let port: chrome.runtime.Port;
|
||||
|
||||
beforeEach(() => {
|
||||
const message = {
|
||||
command: "initAutofillInlineMenuButton",
|
||||
iframeUrl,
|
||||
pageTitle,
|
||||
portKey,
|
||||
portName: AutofillOverlayPort.Button,
|
||||
};
|
||||
|
||||
postWindowMessage(message);
|
||||
|
||||
iframe = autofillInlineMenuContainer["inlineMenuPageIframe"];
|
||||
jest.spyOn(iframe.contentWindow, "postMessage");
|
||||
port = createPortSpyMock(AutofillOverlayPort.Button);
|
||||
autofillInlineMenuContainer["port"] = port;
|
||||
});
|
||||
|
||||
it("ignores messages that do not contain a portKey", () => {
|
||||
const message = { command: "checkInlineMenuButtonFocused" };
|
||||
|
||||
postWindowMessage(message, "*", iframe.contentWindow as any);
|
||||
|
||||
expect(port.postMessage).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("ignores messages if the inline menu iframe has not been created", () => {
|
||||
autofillInlineMenuContainer["inlineMenuPageIframe"] = null;
|
||||
const message = { command: "checkInlineMenuButtonFocused", portKey };
|
||||
|
||||
postWindowMessage(message, "*", iframe.contentWindow as any);
|
||||
|
||||
expect(port.postMessage).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("ignores messages that do not come from either the parent frame or the inline menu iframe", () => {
|
||||
const randomIframe = document.createElement("iframe");
|
||||
const message = { command: "checkInlineMenuButtonFocused", portKey };
|
||||
|
||||
postWindowMessage(message, "*", randomIframe.contentWindow as any);
|
||||
|
||||
expect(port.postMessage).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("ignores messages that come from an invalid origin", () => {
|
||||
const message = { command: "checkInlineMenuButtonFocused", portKey };
|
||||
|
||||
postWindowMessage(message, "https://example.com", iframe.contentWindow as any);
|
||||
|
||||
expect(port.postMessage).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("posts a message to the background from the inline menu iframe", () => {
|
||||
const message = { command: "checkInlineMenuButtonFocused", portKey };
|
||||
|
||||
postWindowMessage(message, "null", iframe.contentWindow as any);
|
||||
|
||||
expect(port.postMessage).toHaveBeenCalledWith(message);
|
||||
});
|
||||
|
||||
it("posts a message to the inline menu iframe from the parent", () => {
|
||||
const message = { command: "checkInlineMenuButtonFocused", portKey };
|
||||
|
||||
postWindowMessage(message);
|
||||
|
||||
expect(iframe.contentWindow.postMessage).toHaveBeenCalledWith(message, "*");
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -4,9 +4,12 @@ import { setElementStyles } from "../../../../utils";
|
||||
import {
|
||||
InitAutofillInlineMenuElementMessage,
|
||||
AutofillInlineMenuContainerWindowMessageHandlers,
|
||||
AutofillInlineMenuContainerWindowMessage,
|
||||
AutofillInlineMenuContainerPortMessage,
|
||||
} from "../../abstractions/autofill-inline-menu-container";
|
||||
|
||||
export class AutofillInlineMenuContainer {
|
||||
private setElementStyles = setElementStyles;
|
||||
private extensionOriginsSet: Set<string>;
|
||||
private port: chrome.runtime.Port | null = null;
|
||||
private portName: string;
|
||||
@@ -37,8 +40,8 @@ export class AutofillInlineMenuContainer {
|
||||
tabIndex: "-1",
|
||||
};
|
||||
private windowMessageHandlers: AutofillInlineMenuContainerWindowMessageHandlers = {
|
||||
initAutofillInlineMenuList: (message) => this.handleInitInlineMenuIframe(message),
|
||||
initAutofillInlineMenuButton: (message) => this.handleInitInlineMenuIframe(message),
|
||||
initAutofillInlineMenuList: (message) => this.handleInitInlineMenuIframe(message),
|
||||
};
|
||||
|
||||
constructor() {
|
||||
@@ -50,44 +53,67 @@ export class AutofillInlineMenuContainer {
|
||||
globalThis.addEventListener("message", this.handleWindowMessage);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles initialization of the iframe used to display the inline menu.
|
||||
*
|
||||
* @param message - The message containing the iframe url and page title.
|
||||
*/
|
||||
private handleInitInlineMenuIframe(message: InitAutofillInlineMenuElementMessage) {
|
||||
this.defaultIframeAttributes.src = message.iframeUrl;
|
||||
this.defaultIframeAttributes.title = message.pageTitle;
|
||||
this.portName = message.portName;
|
||||
|
||||
this.inlineMenuPageIframe = globalThis.document.createElement("iframe");
|
||||
setElementStyles(this.inlineMenuPageIframe, this.iframeStyles, true);
|
||||
this.setElementStyles(this.inlineMenuPageIframe, this.iframeStyles, true);
|
||||
for (const [attribute, value] of Object.entries(this.defaultIframeAttributes)) {
|
||||
this.inlineMenuPageIframe.setAttribute(attribute, value);
|
||||
}
|
||||
this.inlineMenuPageIframe.addEventListener(EVENTS.LOAD, () =>
|
||||
this.setupPortMessageListener(message),
|
||||
);
|
||||
const handleInlineMenuPageIframeLoad = () => {
|
||||
this.inlineMenuPageIframe.removeEventListener(EVENTS.LOAD, handleInlineMenuPageIframeLoad);
|
||||
this.setupPortMessageListener(message);
|
||||
};
|
||||
this.inlineMenuPageIframe.addEventListener(EVENTS.LOAD, handleInlineMenuPageIframeLoad);
|
||||
|
||||
globalThis.document.body.appendChild(this.inlineMenuPageIframe);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets up the port message listener for the inline menu page.
|
||||
*
|
||||
* @param message - The message containing the port name.
|
||||
*/
|
||||
private setupPortMessageListener = (message: InitAutofillInlineMenuElementMessage) => {
|
||||
this.port = chrome.runtime.connect({ name: this.portName });
|
||||
this.postMessageToInlineMenuPage(message);
|
||||
};
|
||||
|
||||
private postMessageToInlineMenuPage(message: any) {
|
||||
if (!this.inlineMenuPageIframe?.contentWindow) {
|
||||
return;
|
||||
/**
|
||||
* Posts a message to the inline menu page iframe.
|
||||
*
|
||||
* @param message - The message to post.
|
||||
*/
|
||||
private postMessageToInlineMenuPage(message: AutofillInlineMenuContainerWindowMessage) {
|
||||
if (this.inlineMenuPageIframe?.contentWindow) {
|
||||
this.inlineMenuPageIframe.contentWindow.postMessage(message, "*");
|
||||
}
|
||||
|
||||
this.inlineMenuPageIframe.contentWindow.postMessage(message, "*");
|
||||
}
|
||||
|
||||
private postMessageToBackground(message: any) {
|
||||
if (!this.port) {
|
||||
return;
|
||||
/**
|
||||
* Posts a message from the inline menu iframe to the background script.
|
||||
*
|
||||
* @param message - The message to post.
|
||||
*/
|
||||
private postMessageToBackground(message: AutofillInlineMenuContainerPortMessage) {
|
||||
if (this.port) {
|
||||
this.port.postMessage(message);
|
||||
}
|
||||
|
||||
this.port.postMessage(message);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles window messages, routing them to the appropriate handler.
|
||||
*
|
||||
* @param event - The message event.
|
||||
*/
|
||||
private handleWindowMessage = (event: MessageEvent) => {
|
||||
const message = event.data;
|
||||
if (this.isForeignWindowMessage(event)) {
|
||||
@@ -107,6 +133,13 @@ export class AutofillInlineMenuContainer {
|
||||
this.postMessageToBackground(message);
|
||||
};
|
||||
|
||||
/**
|
||||
* Identifies if the message is from a foreign window. A foreign window message is
|
||||
* considered as any message that does not have a portKey, is not from the parent window,
|
||||
* or is not from the inline menu page iframe.
|
||||
*
|
||||
* @param event - The message event.
|
||||
*/
|
||||
private isForeignWindowMessage(event: MessageEvent) {
|
||||
if (!event.data.portKey) {
|
||||
return true;
|
||||
@@ -119,10 +152,20 @@ export class AutofillInlineMenuContainer {
|
||||
return !this.isMessageFromInlineMenuPageIframe(event);
|
||||
}
|
||||
|
||||
/**
|
||||
* Identifies if the message is from the parent window.
|
||||
*
|
||||
* @param event - The message event.
|
||||
*/
|
||||
private isMessageFromParentWindow(event: MessageEvent): boolean {
|
||||
return globalThis.parent === event.source;
|
||||
}
|
||||
|
||||
/**
|
||||
* Identifies if the message is from the inline menu page iframe.
|
||||
*
|
||||
* @param event - The message event.
|
||||
*/
|
||||
private isMessageFromInlineMenuPageIframe(event: MessageEvent): boolean {
|
||||
if (!this.inlineMenuPageIframe) {
|
||||
return false;
|
||||
|
||||
Reference in New Issue
Block a user