mirror of
https://github.com/bitwarden/browser
synced 2025-12-16 16:23:44 +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";
|
import { InlineMenuCipherData } from "../../../background/abstractions/overlay.background";
|
||||||
|
|
||||||
type AutofillInlineMenuContainerMessage = {
|
export type AutofillInlineMenuContainerMessage = {
|
||||||
command: string;
|
command: string;
|
||||||
portKey: string;
|
portKey: string;
|
||||||
};
|
};
|
||||||
@@ -18,8 +18,14 @@ export type InitAutofillInlineMenuElementMessage = AutofillInlineMenuContainerMe
|
|||||||
portName: string;
|
portName: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type AutofillInlineMenuContainerWindowMessage = AutofillInlineMenuContainerMessage &
|
||||||
|
Record<string, unknown>;
|
||||||
|
|
||||||
|
export type AutofillInlineMenuContainerPortMessage = AutofillInlineMenuContainerMessage &
|
||||||
|
Record<string, unknown>;
|
||||||
|
|
||||||
export type AutofillInlineMenuContainerWindowMessageHandlers = {
|
export type AutofillInlineMenuContainerWindowMessageHandlers = {
|
||||||
[key: string]: CallableFunction;
|
[key: string]: CallableFunction;
|
||||||
initAutofillInlineMenuList: (message: InitAutofillInlineMenuElementMessage) => void;
|
|
||||||
initAutofillInlineMenuButton: (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 {
|
import {
|
||||||
InitAutofillInlineMenuElementMessage,
|
InitAutofillInlineMenuElementMessage,
|
||||||
AutofillInlineMenuContainerWindowMessageHandlers,
|
AutofillInlineMenuContainerWindowMessageHandlers,
|
||||||
|
AutofillInlineMenuContainerWindowMessage,
|
||||||
|
AutofillInlineMenuContainerPortMessage,
|
||||||
} from "../../abstractions/autofill-inline-menu-container";
|
} from "../../abstractions/autofill-inline-menu-container";
|
||||||
|
|
||||||
export class AutofillInlineMenuContainer {
|
export class AutofillInlineMenuContainer {
|
||||||
|
private setElementStyles = setElementStyles;
|
||||||
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;
|
||||||
@@ -37,8 +40,8 @@ export class AutofillInlineMenuContainer {
|
|||||||
tabIndex: "-1",
|
tabIndex: "-1",
|
||||||
};
|
};
|
||||||
private windowMessageHandlers: AutofillInlineMenuContainerWindowMessageHandlers = {
|
private windowMessageHandlers: AutofillInlineMenuContainerWindowMessageHandlers = {
|
||||||
initAutofillInlineMenuList: (message) => this.handleInitInlineMenuIframe(message),
|
|
||||||
initAutofillInlineMenuButton: (message) => this.handleInitInlineMenuIframe(message),
|
initAutofillInlineMenuButton: (message) => this.handleInitInlineMenuIframe(message),
|
||||||
|
initAutofillInlineMenuList: (message) => this.handleInitInlineMenuIframe(message),
|
||||||
};
|
};
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
@@ -50,44 +53,67 @@ export class AutofillInlineMenuContainer {
|
|||||||
globalThis.addEventListener("message", this.handleWindowMessage);
|
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) {
|
private handleInitInlineMenuIframe(message: InitAutofillInlineMenuElementMessage) {
|
||||||
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.inlineMenuPageIframe = globalThis.document.createElement("iframe");
|
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)) {
|
for (const [attribute, value] of Object.entries(this.defaultIframeAttributes)) {
|
||||||
this.inlineMenuPageIframe.setAttribute(attribute, value);
|
this.inlineMenuPageIframe.setAttribute(attribute, value);
|
||||||
}
|
}
|
||||||
this.inlineMenuPageIframe.addEventListener(EVENTS.LOAD, () =>
|
const handleInlineMenuPageIframeLoad = () => {
|
||||||
this.setupPortMessageListener(message),
|
this.inlineMenuPageIframe.removeEventListener(EVENTS.LOAD, handleInlineMenuPageIframeLoad);
|
||||||
);
|
this.setupPortMessageListener(message);
|
||||||
|
};
|
||||||
|
this.inlineMenuPageIframe.addEventListener(EVENTS.LOAD, handleInlineMenuPageIframeLoad);
|
||||||
|
|
||||||
globalThis.document.body.appendChild(this.inlineMenuPageIframe);
|
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) => {
|
private setupPortMessageListener = (message: InitAutofillInlineMenuElementMessage) => {
|
||||||
this.port = chrome.runtime.connect({ name: this.portName });
|
this.port = chrome.runtime.connect({ name: this.portName });
|
||||||
this.postMessageToInlineMenuPage(message);
|
this.postMessageToInlineMenuPage(message);
|
||||||
};
|
};
|
||||||
|
|
||||||
private postMessageToInlineMenuPage(message: any) {
|
/**
|
||||||
if (!this.inlineMenuPageIframe?.contentWindow) {
|
* Posts a message to the inline menu page iframe.
|
||||||
return;
|
*
|
||||||
|
* @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) {
|
* Posts a message from the inline menu iframe to the background script.
|
||||||
return;
|
*
|
||||||
|
* @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) => {
|
private handleWindowMessage = (event: MessageEvent) => {
|
||||||
const message = event.data;
|
const message = event.data;
|
||||||
if (this.isForeignWindowMessage(event)) {
|
if (this.isForeignWindowMessage(event)) {
|
||||||
@@ -107,6 +133,13 @@ export class AutofillInlineMenuContainer {
|
|||||||
this.postMessageToBackground(message);
|
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) {
|
private isForeignWindowMessage(event: MessageEvent) {
|
||||||
if (!event.data.portKey) {
|
if (!event.data.portKey) {
|
||||||
return true;
|
return true;
|
||||||
@@ -119,10 +152,20 @@ export class AutofillInlineMenuContainer {
|
|||||||
return !this.isMessageFromInlineMenuPageIframe(event);
|
return !this.isMessageFromInlineMenuPageIframe(event);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Identifies if the message is from the parent window.
|
||||||
|
*
|
||||||
|
* @param event - The message event.
|
||||||
|
*/
|
||||||
private isMessageFromParentWindow(event: MessageEvent): boolean {
|
private isMessageFromParentWindow(event: MessageEvent): boolean {
|
||||||
return globalThis.parent === event.source;
|
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 {
|
private isMessageFromInlineMenuPageIframe(event: MessageEvent): boolean {
|
||||||
if (!this.inlineMenuPageIframe) {
|
if (!this.inlineMenuPageIframe) {
|
||||||
return false;
|
return false;
|
||||||
|
|||||||
Reference in New Issue
Block a user