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

[PM-5189] Reworking project structure to ensure we can better differentiate the inline menu feature from other features

This commit is contained in:
Cesar Gonzalez
2024-05-03 14:08:03 -05:00
parent 4aebbc0a64
commit 4038939f46
26 changed files with 725 additions and 203 deletions

View File

@@ -236,7 +236,7 @@ describe("OverlayBackground", () => {
expect(overlayBackground["getOverlayCipherData"]).toHaveBeenCalled(); expect(overlayBackground["getOverlayCipherData"]).toHaveBeenCalled();
}); });
it("posts an `updateOverlayListCiphers` message to the overlay list port with the updated ciphers", async () => { it("posts an `updateAutofillInlineMenuListCiphers` message to the overlay list port with the updated ciphers", async () => {
overlayBackground["inlineMenuListPort"] = mock<chrome.runtime.Port>(); overlayBackground["inlineMenuListPort"] = mock<chrome.runtime.Port>();
cipherService.getAllDecryptedForUrl.mockResolvedValue([cipher1, cipher2]); cipherService.getAllDecryptedForUrl.mockResolvedValue([cipher1, cipher2]);
cipherService.sortCiphersByLastUsedThenName.mockReturnValue(-1); cipherService.sortCiphersByLastUsedThenName.mockReturnValue(-1);
@@ -246,7 +246,7 @@ describe("OverlayBackground", () => {
await overlayBackground.updateOverlayCiphers(); await overlayBackground.updateOverlayCiphers();
expect(overlayBackground["inlineMenuListPort"].postMessage).toHaveBeenCalledWith({ expect(overlayBackground["inlineMenuListPort"].postMessage).toHaveBeenCalledWith({
command: "updateOverlayListCiphers", command: "updateAutofillInlineMenuListCiphers",
ciphers: [ ciphers: [
{ {
card: null, card: null,

View File

@@ -190,7 +190,10 @@ class OverlayBackground implements OverlayBackgroundInterface {
} }
const ciphers = await this.getOverlayCipherData(); const ciphers = await this.getOverlayCipherData();
this.inlineMenuListPort?.postMessage({ command: "updateOverlayListCiphers", ciphers }); this.inlineMenuListPort?.postMessage({
command: "updateAutofillInlineMenuListCiphers",
ciphers,
});
} }
/** /**

View File

@@ -1,13 +1,13 @@
const AutofillOverlayElement = { const AutofillOverlayElement = {
Button: "autofill-inline-menu-button", Button: "autofill-inline-menu-button",
List: "autofill-overlay-list", List: "autofill-inline-menu-list",
} as const; } as const;
const AutofillOverlayPort = { const AutofillOverlayPort = {
Button: "autofill-inline-menu-button-port", Button: "autofill-inline-menu-button-port",
ButtonMessageConnector: "autofill-inline-menu-button-message-connector", ButtonMessageConnector: "autofill-inline-menu-button-message-connector",
List: "autofill-overlay-list-port", List: "autofill-inline-menu-list-port",
ListMessageConnector: "autofill-overlay-list-message-connector", ListMessageConnector: "autofill-inline-menu-list-message-connector",
} as const; } as const;
const RedirectFocusDirection = { const RedirectFocusDirection = {

View File

@@ -1,18 +1,18 @@
import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status"; import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status";
type AutofillInlineMenuButtonMessage = { command: string; colorScheme?: string }; export type AutofillInlineMenuButtonMessage = { command: string; colorScheme?: string };
type UpdateAuthStatusMessage = AutofillInlineMenuButtonMessage & { export type UpdateAuthStatusMessage = AutofillInlineMenuButtonMessage & {
authStatus: AuthenticationStatus; authStatus: AuthenticationStatus;
}; };
type InitAutofillInlineMenuButtonMessage = UpdateAuthStatusMessage & { export type InitAutofillInlineMenuButtonMessage = UpdateAuthStatusMessage & {
styleSheetUrl: string; styleSheetUrl: string;
translations: Record<string, string>; translations: Record<string, string>;
portKey: string; portKey: string;
}; };
type AutofillInlineMenuButtonWindowMessageHandlers = { export type AutofillInlineMenuButtonWindowMessageHandlers = {
[key: string]: CallableFunction; [key: string]: CallableFunction;
initAutofillInlineMenuButton: ({ initAutofillInlineMenuButton: ({
message, message,
@@ -31,10 +31,3 @@ type AutofillInlineMenuButtonWindowMessageHandlers = {
message: AutofillInlineMenuButtonMessage; message: AutofillInlineMenuButtonMessage;
}) => void; }) => void;
}; };
export {
UpdateAuthStatusMessage,
AutofillInlineMenuButtonMessage,
InitAutofillInlineMenuButtonMessage,
AutofillInlineMenuButtonWindowMessageHandlers,
};

View File

@@ -2,12 +2,12 @@ import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authenticatio
import { OverlayCipherData } from "../../../background/abstractions/overlay.background"; import { OverlayCipherData } from "../../../background/abstractions/overlay.background";
type AutofillOverlayMenuContainerMessage = { type AutofillInlineMenuMenuContainerMessage = {
command: string; command: string;
portKey: string; portKey: string;
}; };
export type InitOverlayElementMessage = AutofillOverlayMenuContainerMessage & { export type InitInlineMenuElementMessage = AutofillInlineMenuMenuContainerMessage & {
iframeUrl?: string; iframeUrl?: string;
pageTitle?: string; pageTitle?: string;
authStatus?: AuthenticationStatus; authStatus?: AuthenticationStatus;
@@ -18,8 +18,8 @@ export type InitOverlayElementMessage = AutofillOverlayMenuContainerMessage & {
portName?: string; portName?: string;
}; };
export type AutofillOverlayMenuContainerWindowMessageHandlers = { export type AutofillInlineMenuMenuContainerWindowMessageHandlers = {
[key: string]: CallableFunction; [key: string]: CallableFunction;
initAutofillInlineMenuList: (message: InitOverlayElementMessage) => void; initAutofillInlineMenuList: (message: InitInlineMenuElementMessage) => void;
initAutofillInlineMenuButton: (message: InitOverlayElementMessage) => void; initAutofillInlineMenuButton: (message: InitInlineMenuElementMessage) => void;
}; };

View File

@@ -1,35 +1,25 @@
type AutofillOverlayIframeExtensionMessage = { export type AutofillInlineMenuIframeExtensionMessage = {
command: string; command: string;
styles?: Partial<CSSStyleDeclaration>; styles?: Partial<CSSStyleDeclaration>;
theme?: string; theme?: string;
portKey?: string; portKey?: string;
}; };
type AutofillOverlayIframeWindowMessageHandlers = { export type AutofillInlineMenuIframeExtensionMessageParam = {
[key: string]: CallableFunction; message: AutofillInlineMenuIframeExtensionMessage;
updateAutofillInlineMenuListHeight: (message: AutofillOverlayIframeExtensionMessage) => void;
}; };
type AutofillOverlayIframeExtensionMessageParam = { export type BackgroundPortMessageHandlers = {
message: AutofillOverlayIframeExtensionMessage;
};
type BackgroundPortMessageHandlers = {
[key: string]: CallableFunction; [key: string]: CallableFunction;
initAutofillInlineMenuButton: ({ message }: AutofillOverlayIframeExtensionMessageParam) => void; initAutofillInlineMenuButton: ({
initAutofillInlineMenuList: ({ message }: AutofillOverlayIframeExtensionMessageParam) => void; message,
updateIframePosition: ({ message }: AutofillOverlayIframeExtensionMessageParam) => void; }: AutofillInlineMenuIframeExtensionMessageParam) => void;
updateInlineMenuHidden: ({ message }: AutofillOverlayIframeExtensionMessageParam) => void; initAutofillInlineMenuList: ({ message }: AutofillInlineMenuIframeExtensionMessageParam) => void;
updateIframePosition: ({ message }: AutofillInlineMenuIframeExtensionMessageParam) => void;
updateInlineMenuHidden: ({ message }: AutofillInlineMenuIframeExtensionMessageParam) => void;
updateAutofillInlineMenuColorScheme: () => void; updateAutofillInlineMenuColorScheme: () => void;
}; };
interface AutofillInlineMenuIframeService { export interface AutofillInlineMenuIframeService {
initMenuIframe(): void; initMenuIframe(): void;
} }
export {
AutofillOverlayIframeExtensionMessage,
AutofillOverlayIframeWindowMessageHandlers,
BackgroundPortMessageHandlers,
AutofillInlineMenuIframeService,
};

View File

@@ -2,13 +2,13 @@ import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authenticatio
import { OverlayCipherData } from "../../../background/abstractions/overlay.background"; import { OverlayCipherData } from "../../../background/abstractions/overlay.background";
type OverlayListMessage = { command: string }; type AutofillInlineMenuListMessage = { command: string };
type UpdateOverlayListCiphersMessage = OverlayListMessage & { export type UpdateAutofillInlineMenuListCiphersMessage = AutofillInlineMenuListMessage & {
ciphers: OverlayCipherData[]; ciphers: OverlayCipherData[];
}; };
type InitAutofillOverlayListMessage = OverlayListMessage & { export type InitAutofillInlineMenuListMessage = AutofillInlineMenuListMessage & {
authStatus: AuthenticationStatus; authStatus: AuthenticationStatus;
styleSheetUrl: string; styleSheetUrl: string;
theme: string; theme: string;
@@ -17,16 +17,14 @@ type InitAutofillOverlayListMessage = OverlayListMessage & {
portKey: string; portKey: string;
}; };
type OverlayListWindowMessageHandlers = { export type AutofillInlineMenuListWindowMessageHandlers = {
[key: string]: CallableFunction; [key: string]: CallableFunction;
initAutofillInlineMenuList: ({ message }: { message: InitAutofillOverlayListMessage }) => void; initAutofillInlineMenuList: ({ message }: { message: InitAutofillInlineMenuListMessage }) => void;
checkAutofillInlineMenuListFocused: () => void; checkAutofillInlineMenuListFocused: () => void;
updateOverlayListCiphers: ({ message }: { message: UpdateOverlayListCiphersMessage }) => void; updateAutofillInlineMenuListCiphers: ({
message,
}: {
message: UpdateAutofillInlineMenuListCiphersMessage;
}) => void;
focusInlineMenuList: () => void; focusInlineMenuList: () => void;
}; };
export {
UpdateOverlayListCiphersMessage,
InitAutofillOverlayListMessage,
OverlayListWindowMessageHandlers,
};

View File

@@ -1,18 +1,13 @@
import { AutofillInlineMenuButtonWindowMessageHandlers } from "./autofill-inline-menu-button"; import { AutofillInlineMenuButtonWindowMessageHandlers } from "./autofill-inline-menu-button";
import { OverlayListWindowMessageHandlers } from "./autofill-inline-menu-list"; import { AutofillInlineMenuListWindowMessageHandlers } from "./autofill-inline-menu-list";
type AutofillInlineMenuPageElementWindowMessageHandlers = export type AutofillInlineMenuPageElementWindowMessageHandlers =
| AutofillInlineMenuButtonWindowMessageHandlers | AutofillInlineMenuButtonWindowMessageHandlers
| OverlayListWindowMessageHandlers; | AutofillInlineMenuListWindowMessageHandlers;
type AutofillInlineMenuPageElementWindowMessage = { export type AutofillInlineMenuPageElementWindowMessage = {
[key: string]: any; [key: string]: any;
command: string; command: string;
overlayCipherId?: string; overlayCipherId?: string;
height?: number; height?: number;
}; };
export {
AutofillInlineMenuPageElementWindowMessageHandlers,
AutofillInlineMenuPageElementWindowMessage,
};

View File

@@ -10,7 +10,7 @@ import {
AutofillInlineMenuContentService as AutofillInlineMenuContentServiceInterface, AutofillInlineMenuContentService as AutofillInlineMenuContentServiceInterface,
} from "../abstractions/autofill-inline-menu-content.service"; } from "../abstractions/autofill-inline-menu-content.service";
import { AutofillInlineMenuButtonIframe } from "../iframe-content/autofill-inline-menu-button-iframe"; import { AutofillInlineMenuButtonIframe } from "../iframe-content/autofill-inline-menu-button-iframe";
import AutofillInlineMenuListIframe from "../iframe-content/autofill-inline-menu-list-iframe"; import { AutofillInlineMenuListIframe } from "../iframe-content/autofill-inline-menu-list-iframe";
export class AutofillInlineMenuContentService implements AutofillInlineMenuContentServiceInterface { export class AutofillInlineMenuContentService implements AutofillInlineMenuContentServiceInterface {
private readonly sendExtensionMessage = sendExtensionMessage; private readonly sendExtensionMessage = sendExtensionMessage;

View File

@@ -1,5 +1,15 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP // Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`AutofillInlineMenuIframeService initMenuIframe sets up the iframe's attributes 1`] = `
<iframe
allowtransparency="true"
src="chrome-extension://id/overlay/menu.html"
style="all: initial !important; position: fixed !important; display: block !important; z-index: 2147483647 !important; line-height: 0 !important; overflow: hidden !important; transition: opacity 125ms ease-out 0s !important; visibility: visible !important; clip-path: none !important; pointer-events: auto !important; margin: 0px !important; padding: 0px !important; color-scheme: normal !important; opacity: 0 !important; height: 0px;"
tabindex="-1"
title="title"
/>
`;
exports[`AutofillOverlayIframeService initMenuIframe sets up the iframe's attributes 1`] = ` exports[`AutofillOverlayIframeService initMenuIframe sets up the iframe's attributes 1`] = `
<iframe <iframe
allowtransparency="true" allowtransparency="true"

View File

@@ -15,7 +15,7 @@ describe("AutofillInlineMenuIframeElement", () => {
this, this,
AutofillOverlayPort.Button, AutofillOverlayPort.Button,
{ background: "transparent", border: "none" }, { background: "transparent", border: "none" },
"bitwardenOverlayButton", "bitwardenInlineMenuButton",
); );
} }
}, },
@@ -41,7 +41,7 @@ describe("AutofillInlineMenuIframeElement", () => {
expect(iframe.shadowRoot).toBeNull(); expect(iframe.shadowRoot).toBeNull();
}); });
it("instantiates the autofill overlay iframe service for each attached custom element", () => { it("instantiates the autofill inline menu iframe service for each attached custom element", () => {
expect(AutofillInlineMenuIframeService).toHaveBeenCalledTimes(2); expect(AutofillInlineMenuIframeService).toHaveBeenCalledTimes(2);
}); });
}); });

View File

@@ -9,13 +9,13 @@ export class AutofillInlineMenuIframeElement {
ariaAlert?: string, ariaAlert?: string,
) { ) {
const shadow: ShadowRoot = element.attachShadow({ mode: "closed" }); const shadow: ShadowRoot = element.attachShadow({ mode: "closed" });
const autofillOverlayIframeService = new AutofillInlineMenuIframeService( const autofillInlineMenuIframeService = new AutofillInlineMenuIframeService(
shadow, shadow,
portName, portName,
initStyles, initStyles,
iframeTitle, iframeTitle,
ariaAlert, ariaAlert,
); );
autofillOverlayIframeService.initMenuIframe(); autofillInlineMenuIframeService.initMenuIframe();
} }
} }

View File

@@ -13,8 +13,8 @@ import {
import { AutofillInlineMenuIframeService } from "./autofill-inline-menu-iframe.service"; import { AutofillInlineMenuIframeService } from "./autofill-inline-menu-iframe.service";
describe("AutofillOverlayIframeService", () => { describe("AutofillInlineMenuIframeService", () => {
let autofillOverlayIframeService: AutofillInlineMenuIframeService; let autofillInlineMenuIframeService: AutofillInlineMenuIframeService;
let portSpy: chrome.runtime.Port; let portSpy: chrome.runtime.Port;
let shadowAppendSpy: jest.SpyInstance; let shadowAppendSpy: jest.SpyInstance;
let handlePortDisconnectSpy: jest.SpyInstance; let handlePortDisconnectSpy: jest.SpyInstance;
@@ -23,7 +23,7 @@ describe("AutofillOverlayIframeService", () => {
beforeEach(() => { beforeEach(() => {
const shadow = document.createElement("div").attachShadow({ mode: "open" }); const shadow = document.createElement("div").attachShadow({ mode: "open" });
autofillOverlayIframeService = new AutofillInlineMenuIframeService( autofillInlineMenuIframeService = new AutofillInlineMenuIframeService(
shadow, shadow,
AutofillOverlayPort.Button, AutofillOverlayPort.Button,
{ height: "0px" }, { height: "0px" },
@@ -32,15 +32,15 @@ describe("AutofillOverlayIframeService", () => {
); );
shadowAppendSpy = jest.spyOn(shadow, "appendChild"); shadowAppendSpy = jest.spyOn(shadow, "appendChild");
handlePortDisconnectSpy = jest.spyOn( handlePortDisconnectSpy = jest.spyOn(
autofillOverlayIframeService as any, autofillInlineMenuIframeService as any,
"handlePortDisconnect", "handlePortDisconnect",
); );
handlePortMessageSpy = jest.spyOn(autofillOverlayIframeService as any, "handlePortMessage"); handlePortMessageSpy = jest.spyOn(autofillInlineMenuIframeService as any, "handlePortMessage");
chrome.runtime.connect = jest.fn((connectInfo: chrome.runtime.ConnectInfo) => chrome.runtime.connect = jest.fn((connectInfo: chrome.runtime.ConnectInfo) =>
createPortSpyMock(connectInfo.name), createPortSpyMock(connectInfo.name),
) as unknown as typeof chrome.runtime.connect; ) as unknown as typeof chrome.runtime.connect;
sendExtensionMessageSpy = jest.spyOn( sendExtensionMessageSpy = jest.spyOn(
autofillOverlayIframeService as any, autofillInlineMenuIframeService as any,
"sendExtensionMessage", "sendExtensionMessage",
); );
}); });
@@ -51,44 +51,44 @@ describe("AutofillOverlayIframeService", () => {
describe("initMenuIframe", () => { describe("initMenuIframe", () => {
it("sets up the iframe's attributes", () => { it("sets up the iframe's attributes", () => {
autofillOverlayIframeService.initMenuIframe(); autofillInlineMenuIframeService.initMenuIframe();
expect(autofillOverlayIframeService["iframe"]).toMatchSnapshot(); expect(autofillInlineMenuIframeService["iframe"]).toMatchSnapshot();
}); });
it("appends the iframe to the shadowDom", () => { it("appends the iframe to the shadowDom", () => {
jest.spyOn(autofillOverlayIframeService["shadow"], "appendChild"); jest.spyOn(autofillInlineMenuIframeService["shadow"], "appendChild");
autofillOverlayIframeService.initMenuIframe(); autofillInlineMenuIframeService.initMenuIframe();
expect(autofillOverlayIframeService["shadow"].appendChild).toHaveBeenCalledWith( expect(autofillInlineMenuIframeService["shadow"].appendChild).toHaveBeenCalledWith(
autofillOverlayIframeService["iframe"], autofillInlineMenuIframeService["iframe"],
); );
}); });
// TODO CG - This test is brittle and failing due to how we are calling the private method. This needs to be reworked // TODO CG - This test is brittle and failing due to how we are calling the private method. This needs to be reworked
it.skip("creates an aria alert element if the ariaAlert param is passed", () => { it.skip("creates an aria alert element if the ariaAlert param is passed", () => {
const ariaAlert = "aria alert"; const ariaAlert = "aria alert";
jest.spyOn(autofillOverlayIframeService as any, "createAriaAlertElement"); jest.spyOn(autofillInlineMenuIframeService as any, "createAriaAlertElement");
autofillOverlayIframeService.initMenuIframe(); autofillInlineMenuIframeService.initMenuIframe();
expect(autofillOverlayIframeService["createAriaAlertElement"]).toHaveBeenCalledWith( expect(autofillInlineMenuIframeService["createAriaAlertElement"]).toHaveBeenCalledWith(
ariaAlert, ariaAlert,
); );
expect(autofillOverlayIframeService["ariaAlertElement"]).toMatchSnapshot(); expect(autofillInlineMenuIframeService["ariaAlertElement"]).toMatchSnapshot();
}); });
describe("on load of the iframe source", () => { describe("on load of the iframe source", () => {
beforeEach(() => { beforeEach(() => {
autofillOverlayIframeService.initMenuIframe(); autofillInlineMenuIframeService.initMenuIframe();
}); });
it("sets up and connects the port message listener to the extension background", () => { it("sets up and connects the port message listener to the extension background", () => {
jest.spyOn(globalThis, "addEventListener"); jest.spyOn(globalThis, "addEventListener");
autofillOverlayIframeService["iframe"].dispatchEvent(new Event(EVENTS.LOAD)); autofillInlineMenuIframeService["iframe"].dispatchEvent(new Event(EVENTS.LOAD));
portSpy = autofillOverlayIframeService["port"]; portSpy = autofillInlineMenuIframeService["port"];
expect(chrome.runtime.connect).toHaveBeenCalledWith({ name: AutofillOverlayPort.Button }); expect(chrome.runtime.connect).toHaveBeenCalledWith({ name: AutofillOverlayPort.Button });
expect(portSpy.onDisconnect.addListener).toHaveBeenCalledWith(handlePortDisconnectSpy); expect(portSpy.onDisconnect.addListener).toHaveBeenCalledWith(handlePortDisconnectSpy);
@@ -97,9 +97,9 @@ describe("AutofillOverlayIframeService", () => {
it("skips announcing the aria alert if the aria alert element is not populated", () => { it("skips announcing the aria alert if the aria alert element is not populated", () => {
jest.spyOn(globalThis, "setTimeout"); jest.spyOn(globalThis, "setTimeout");
autofillOverlayIframeService["ariaAlertElement"] = undefined; autofillInlineMenuIframeService["ariaAlertElement"] = undefined;
autofillOverlayIframeService["iframe"].dispatchEvent(new Event(EVENTS.LOAD)); autofillInlineMenuIframeService["iframe"].dispatchEvent(new Event(EVENTS.LOAD));
expect(globalThis.setTimeout).not.toHaveBeenCalled(); expect(globalThis.setTimeout).not.toHaveBeenCalled();
}); });
@@ -107,16 +107,16 @@ describe("AutofillOverlayIframeService", () => {
it("announces the aria alert if the aria alert element is populated", () => { it("announces the aria alert if the aria alert element is populated", () => {
jest.useFakeTimers(); jest.useFakeTimers();
jest.spyOn(globalThis, "setTimeout"); jest.spyOn(globalThis, "setTimeout");
autofillOverlayIframeService["ariaAlertElement"] = document.createElement("div"); autofillInlineMenuIframeService["ariaAlertElement"] = document.createElement("div");
autofillOverlayIframeService["ariaAlertTimeout"] = setTimeout(jest.fn(), 2000); autofillInlineMenuIframeService["ariaAlertTimeout"] = setTimeout(jest.fn(), 2000);
autofillOverlayIframeService["iframe"].dispatchEvent(new Event(EVENTS.LOAD)); autofillInlineMenuIframeService["iframe"].dispatchEvent(new Event(EVENTS.LOAD));
expect(globalThis.setTimeout).toHaveBeenCalled(); expect(globalThis.setTimeout).toHaveBeenCalled();
jest.advanceTimersByTime(2000); jest.advanceTimersByTime(2000);
expect(shadowAppendSpy).toHaveBeenCalledWith( expect(shadowAppendSpy).toHaveBeenCalledWith(
autofillOverlayIframeService["ariaAlertElement"], autofillInlineMenuIframeService["ariaAlertElement"],
); );
}); });
}); });
@@ -124,16 +124,16 @@ describe("AutofillOverlayIframeService", () => {
describe("event listeners", () => { describe("event listeners", () => {
beforeEach(() => { beforeEach(() => {
autofillOverlayIframeService.initMenuIframe(); autofillInlineMenuIframeService.initMenuIframe();
autofillOverlayIframeService["iframe"].dispatchEvent(new Event(EVENTS.LOAD)); autofillInlineMenuIframeService["iframe"].dispatchEvent(new Event(EVENTS.LOAD));
Object.defineProperty(autofillOverlayIframeService["iframe"], "contentWindow", { Object.defineProperty(autofillInlineMenuIframeService["iframe"], "contentWindow", {
value: { value: {
postMessage: jest.fn(), postMessage: jest.fn(),
}, },
writable: true, writable: true,
}); });
jest.spyOn(autofillOverlayIframeService["iframe"].contentWindow, "postMessage"); jest.spyOn(autofillInlineMenuIframeService["iframe"].contentWindow, "postMessage");
portSpy = autofillOverlayIframeService["port"]; portSpy = autofillInlineMenuIframeService["port"];
}); });
describe("handlePortDisconnect", () => { describe("handlePortDisconnect", () => {
@@ -141,15 +141,15 @@ describe("AutofillOverlayIframeService", () => {
portSpy.name = "wrong-port-name"; portSpy.name = "wrong-port-name";
triggerPortOnDisconnectEvent(portSpy); triggerPortOnDisconnectEvent(portSpy);
expect(autofillOverlayIframeService["port"]).not.toBeNull(); expect(autofillInlineMenuIframeService["port"]).not.toBeNull();
}); });
it("resets the iframe element's opacity, height, and display styles", () => { it("resets the iframe element's opacity, height, and display styles", () => {
triggerPortOnDisconnectEvent(portSpy); triggerPortOnDisconnectEvent(portSpy);
expect(autofillOverlayIframeService["iframe"].style.opacity).toBe("0"); expect(autofillInlineMenuIframeService["iframe"].style.opacity).toBe("0");
expect(autofillOverlayIframeService["iframe"].style.height).toBe("0px"); expect(autofillInlineMenuIframeService["iframe"].style.height).toBe("0px");
expect(autofillOverlayIframeService["iframe"].style.display).toBe("block"); expect(autofillInlineMenuIframeService["iframe"].style.display).toBe("block");
}); });
it("removes the port's onMessage listener", () => { it("removes the port's onMessage listener", () => {
@@ -168,7 +168,7 @@ describe("AutofillOverlayIframeService", () => {
triggerPortOnDisconnectEvent(portSpy); triggerPortOnDisconnectEvent(portSpy);
expect(portSpy.disconnect).toHaveBeenCalled(); expect(portSpy.disconnect).toHaveBeenCalled();
expect(autofillOverlayIframeService["port"]).toBeNull(); expect(autofillInlineMenuIframeService["port"]).toBeNull();
}); });
}); });
@@ -178,7 +178,7 @@ describe("AutofillOverlayIframeService", () => {
sendPortMessage(portSpy, {}); sendPortMessage(portSpy, {});
expect( expect(
autofillOverlayIframeService["iframe"].contentWindow.postMessage, autofillInlineMenuIframeService["iframe"].contentWindow.postMessage,
).not.toHaveBeenCalled(); ).not.toHaveBeenCalled();
}); });
@@ -188,22 +188,22 @@ describe("AutofillOverlayIframeService", () => {
sendPortMessage(portSpy, message); sendPortMessage(portSpy, message);
expect( expect(
autofillOverlayIframeService["iframe"].contentWindow.postMessage, autofillInlineMenuIframeService["iframe"].contentWindow.postMessage,
).toHaveBeenCalledWith(message, "*"); ).toHaveBeenCalledWith(message, "*");
}); });
it("handles port messages that are registered with the message handlers and does not pass the message on to the iframe", () => { it("handles port messages that are registered with the message handlers and does not pass the message on to the iframe", () => {
jest.spyOn(autofillOverlayIframeService as any, "updateIframePosition"); jest.spyOn(autofillInlineMenuIframeService as any, "updateIframePosition");
sendPortMessage(portSpy, { command: "updateIframePosition" }); sendPortMessage(portSpy, { command: "updateIframePosition" });
expect( expect(
autofillOverlayIframeService["iframe"].contentWindow.postMessage, autofillInlineMenuIframeService["iframe"].contentWindow.postMessage,
).not.toHaveBeenCalled(); ).not.toHaveBeenCalled();
}); });
describe("initializing the overlay button", () => { describe("initializing the inline menu button", () => {
it("sets the port key and posts the message to the overlay page iframe", () => { it("sets the port key and posts the message to the inline menu page iframe", () => {
const portKey = "portKey"; const portKey = "portKey";
const message = { const message = {
command: "initAutofillInlineMenuButton", command: "initAutofillInlineMenuButton",
@@ -212,19 +212,19 @@ describe("AutofillOverlayIframeService", () => {
sendPortMessage(portSpy, message); sendPortMessage(portSpy, message);
expect(autofillOverlayIframeService["portKey"]).toBe(portKey); expect(autofillInlineMenuIframeService["portKey"]).toBe(portKey);
expect( expect(
autofillOverlayIframeService["iframe"].contentWindow.postMessage, autofillInlineMenuIframeService["iframe"].contentWindow.postMessage,
).toHaveBeenCalledWith(message, "*"); ).toHaveBeenCalledWith(message, "*");
}); });
}); });
describe("initializing the overlay list", () => { describe("initializing the inline menu list", () => {
let updateElementStylesSpy: jest.SpyInstance; let updateElementStylesSpy: jest.SpyInstance;
beforeEach(() => { beforeEach(() => {
updateElementStylesSpy = jest.spyOn( updateElementStylesSpy = jest.spyOn(
autofillOverlayIframeService as any, autofillInlineMenuIframeService as any,
"updateElementStyles", "updateElementStyles",
); );
}); });
@@ -239,7 +239,7 @@ describe("AutofillOverlayIframeService", () => {
expect(updateElementStylesSpy).not.toHaveBeenCalled(); expect(updateElementStylesSpy).not.toHaveBeenCalled();
expect( expect(
autofillOverlayIframeService["iframe"].contentWindow.postMessage, autofillInlineMenuIframeService["iframe"].contentWindow.postMessage,
).toHaveBeenCalledWith(message, "*"); ).toHaveBeenCalledWith(message, "*");
}); });
@@ -254,7 +254,7 @@ describe("AutofillOverlayIframeService", () => {
expect(window.matchMedia).toHaveBeenCalledWith("(prefers-color-scheme: dark)"); expect(window.matchMedia).toHaveBeenCalledWith("(prefers-color-scheme: dark)");
expect( expect(
autofillOverlayIframeService["iframe"].contentWindow.postMessage, autofillInlineMenuIframeService["iframe"].contentWindow.postMessage,
).toHaveBeenCalledWith( ).toHaveBeenCalledWith(
{ {
command: "initAutofillInlineMenuList", command: "initAutofillInlineMenuList",
@@ -275,7 +275,7 @@ describe("AutofillOverlayIframeService", () => {
expect(window.matchMedia).toHaveBeenCalledWith("(prefers-color-scheme: dark)"); expect(window.matchMedia).toHaveBeenCalledWith("(prefers-color-scheme: dark)");
expect( expect(
autofillOverlayIframeService["iframe"].contentWindow.postMessage, autofillInlineMenuIframeService["iframe"].contentWindow.postMessage,
).toHaveBeenCalledWith( ).toHaveBeenCalledWith(
{ {
command: "initAutofillInlineMenuList", command: "initAutofillInlineMenuList",
@@ -294,7 +294,7 @@ describe("AutofillOverlayIframeService", () => {
sendPortMessage(portSpy, message); sendPortMessage(portSpy, message);
expect(updateElementStylesSpy).toHaveBeenCalledWith( expect(updateElementStylesSpy).toHaveBeenCalledWith(
autofillOverlayIframeService["iframe"], autofillInlineMenuIframeService["iframe"],
{ {
borderColor: "#4c525f", borderColor: "#4c525f",
}, },
@@ -310,7 +310,7 @@ describe("AutofillOverlayIframeService", () => {
sendPortMessage(portSpy, message); sendPortMessage(portSpy, message);
expect(updateElementStylesSpy).toHaveBeenCalledWith( expect(updateElementStylesSpy).toHaveBeenCalledWith(
autofillOverlayIframeService["iframe"], autofillInlineMenuIframeService["iframe"],
{ {
borderColor: "#2E3440", borderColor: "#2E3440",
}, },
@@ -326,7 +326,7 @@ describe("AutofillOverlayIframeService", () => {
sendPortMessage(portSpy, message); sendPortMessage(portSpy, message);
expect(updateElementStylesSpy).toHaveBeenCalledWith( expect(updateElementStylesSpy).toHaveBeenCalledWith(
autofillOverlayIframeService["iframe"], autofillInlineMenuIframeService["iframe"],
{ {
borderColor: "#073642", borderColor: "#073642",
}, },
@@ -340,7 +340,7 @@ describe("AutofillOverlayIframeService", () => {
}); });
it("ignores updating the iframe position if the document does not have focus", () => { it("ignores updating the iframe position if the document does not have focus", () => {
jest.spyOn(autofillOverlayIframeService as any, "updateElementStyles"); jest.spyOn(autofillInlineMenuIframeService as any, "updateElementStyles");
jest.spyOn(globalThis.document, "hasFocus").mockReturnValue(false); jest.spyOn(globalThis.document, "hasFocus").mockReturnValue(false);
sendPortMessage(portSpy, { sendPortMessage(portSpy, {
@@ -348,7 +348,7 @@ describe("AutofillOverlayIframeService", () => {
styles: { top: 100, left: 100 }, styles: { top: 100, left: 100 },
}); });
expect(autofillOverlayIframeService["updateElementStyles"]).not.toHaveBeenCalled(); expect(autofillInlineMenuIframeService["updateElementStyles"]).not.toHaveBeenCalled();
}); });
it("updates the iframe position if the document has focus", () => { it("updates the iframe position if the document has focus", () => {
@@ -359,8 +359,8 @@ describe("AutofillOverlayIframeService", () => {
styles, styles,
}); });
expect(autofillOverlayIframeService["iframe"].style.top).toBe(styles.top); expect(autofillInlineMenuIframeService["iframe"].style.top).toBe(styles.top);
expect(autofillOverlayIframeService["iframe"].style.left).toBe(styles.left); expect(autofillInlineMenuIframeService["iframe"].style.left).toBe(styles.left);
}); });
it("fades the iframe element in after positioning the element", () => { it("fades the iframe element in after positioning the element", () => {
@@ -372,9 +372,9 @@ describe("AutofillOverlayIframeService", () => {
styles, styles,
}); });
expect(autofillOverlayIframeService["iframe"].style.opacity).toBe("0"); expect(autofillInlineMenuIframeService["iframe"].style.opacity).toBe("0");
jest.advanceTimersByTime(10); jest.advanceTimersByTime(10);
expect(autofillOverlayIframeService["iframe"].style.opacity).toBe("1"); expect(autofillInlineMenuIframeService["iframe"].style.opacity).toBe("1");
}); });
it("announces the opening of the iframe using an aria alert", () => { it("announces the opening of the iframe using an aria alert", () => {
@@ -388,7 +388,7 @@ describe("AutofillOverlayIframeService", () => {
jest.advanceTimersByTime(2000); jest.advanceTimersByTime(2000);
expect(shadowAppendSpy).toHaveBeenCalledWith( expect(shadowAppendSpy).toHaveBeenCalledWith(
autofillOverlayIframeService["ariaAlertElement"], autofillInlineMenuIframeService["ariaAlertElement"],
); );
}); });
}); });
@@ -399,7 +399,7 @@ describe("AutofillOverlayIframeService", () => {
styles: { display: "none" }, styles: { display: "none" },
}); });
expect(autofillOverlayIframeService["iframe"].style.display).toBe("none"); expect(autofillInlineMenuIframeService["iframe"].style.display).toBe("none");
}); });
it("updates the button based on the web page's color scheme", () => { it("updates the button based on the web page's color scheme", () => {
@@ -408,7 +408,7 @@ describe("AutofillOverlayIframeService", () => {
}); });
expect( expect(
autofillOverlayIframeService["iframe"].contentWindow.postMessage, autofillInlineMenuIframeService["iframe"].contentWindow.postMessage,
).toHaveBeenCalledWith( ).toHaveBeenCalledWith(
{ {
command: "updateAutofillInlineMenuColorScheme", command: "updateAutofillInlineMenuColorScheme",
@@ -422,41 +422,41 @@ describe("AutofillOverlayIframeService", () => {
describe("mutation observer", () => { describe("mutation observer", () => {
beforeEach(() => { beforeEach(() => {
autofillOverlayIframeService.initMenuIframe(); autofillInlineMenuIframeService.initMenuIframe();
autofillOverlayIframeService["iframe"].dispatchEvent(new Event(EVENTS.LOAD)); autofillInlineMenuIframeService["iframe"].dispatchEvent(new Event(EVENTS.LOAD));
portSpy = autofillOverlayIframeService["port"]; portSpy = autofillInlineMenuIframeService["port"];
}); });
it("skips handling found mutations if excessive mutations are triggering", async () => { it("skips handling found mutations if excessive mutations are triggering", async () => {
jest.useFakeTimers(); jest.useFakeTimers();
jest jest
.spyOn( .spyOn(
autofillOverlayIframeService as any, autofillInlineMenuIframeService as any,
"isTriggeringExcessiveMutationObserverIterations", "isTriggeringExcessiveMutationObserverIterations",
) )
.mockReturnValue(true); .mockReturnValue(true);
jest.spyOn(autofillOverlayIframeService as any, "updateElementStyles"); jest.spyOn(autofillInlineMenuIframeService as any, "updateElementStyles");
autofillOverlayIframeService["iframe"].style.visibility = "hidden"; autofillInlineMenuIframeService["iframe"].style.visibility = "hidden";
await flushPromises(); await flushPromises();
expect(autofillOverlayIframeService["updateElementStyles"]).not.toHaveBeenCalled(); expect(autofillInlineMenuIframeService["updateElementStyles"]).not.toHaveBeenCalled();
}); });
it("reverts any styles changes made directly to the iframe", async () => { it("reverts any styles changes made directly to the iframe", async () => {
jest.useFakeTimers(); jest.useFakeTimers();
autofillOverlayIframeService["iframe"].style.visibility = "hidden"; autofillInlineMenuIframeService["iframe"].style.visibility = "hidden";
await flushPromises(); await flushPromises();
expect(autofillOverlayIframeService["iframe"].style.visibility).toBe("visible"); expect(autofillInlineMenuIframeService["iframe"].style.visibility).toBe("visible");
}); });
it("force closes the autofill overlay if more than 9 foreign mutations are triggered", async () => { it("force closes the autofill inline menu if more than 9 foreign mutations are triggered", async () => {
jest.useFakeTimers(); jest.useFakeTimers();
autofillOverlayIframeService["foreignMutationsCount"] = 10; autofillInlineMenuIframeService["foreignMutationsCount"] = 10;
autofillOverlayIframeService["iframe"].src = "http://malicious-site.com"; autofillInlineMenuIframeService["iframe"].src = "http://malicious-site.com";
await flushPromises(); await flushPromises();
expect(sendExtensionMessageSpy).toHaveBeenCalledWith("closeAutofillInlineMenu", { expect(sendExtensionMessageSpy).toHaveBeenCalledWith("closeAutofillInlineMenu", {
@@ -464,11 +464,11 @@ describe("AutofillOverlayIframeService", () => {
}); });
}); });
it("force closes the autofill overlay if excessive mutations are being triggered", async () => { it("force closes the autofill overinline menulay if excessive mutations are being triggered", async () => {
jest.useFakeTimers(); jest.useFakeTimers();
autofillOverlayIframeService["mutationObserverIterations"] = 20; autofillInlineMenuIframeService["mutationObserverIterations"] = 20;
autofillOverlayIframeService["iframe"].src = "http://malicious-site.com"; autofillInlineMenuIframeService["iframe"].src = "http://malicious-site.com";
await flushPromises(); await flushPromises();
expect(sendExtensionMessageSpy).toHaveBeenCalledWith("closeAutofillInlineMenu", { expect(sendExtensionMessageSpy).toHaveBeenCalledWith("closeAutofillInlineMenu", {
@@ -478,24 +478,24 @@ describe("AutofillOverlayIframeService", () => {
it("resets the excessive mutations and foreign mutation counters", async () => { it("resets the excessive mutations and foreign mutation counters", async () => {
jest.useFakeTimers(); jest.useFakeTimers();
autofillOverlayIframeService["foreignMutationsCount"] = 9; autofillInlineMenuIframeService["foreignMutationsCount"] = 9;
autofillOverlayIframeService["mutationObserverIterations"] = 19; autofillInlineMenuIframeService["mutationObserverIterations"] = 19;
autofillOverlayIframeService["iframe"].src = "http://malicious-site.com"; autofillInlineMenuIframeService["iframe"].src = "http://malicious-site.com";
jest.advanceTimersByTime(2001); jest.advanceTimersByTime(2001);
await flushPromises(); await flushPromises();
expect(autofillOverlayIframeService["foreignMutationsCount"]).toBe(0); expect(autofillInlineMenuIframeService["foreignMutationsCount"]).toBe(0);
expect(autofillOverlayIframeService["mutationObserverIterations"]).toBe(0); expect(autofillInlineMenuIframeService["mutationObserverIterations"]).toBe(0);
}); });
it("resets any mutated default attributes for the iframe", async () => { it("resets any mutated default attributes for the iframe", async () => {
jest.useFakeTimers(); jest.useFakeTimers();
autofillOverlayIframeService["iframe"].title = "some-other-title"; autofillInlineMenuIframeService["iframe"].title = "some-other-title";
await flushPromises(); await flushPromises();
expect(autofillOverlayIframeService["iframe"].title).toBe("title"); expect(autofillInlineMenuIframeService["iframe"].title).toBe("title");
}); });
}); });
}); });

View File

@@ -4,11 +4,11 @@ import { ThemeType } from "@bitwarden/common/platform/enums";
import { sendExtensionMessage, setElementStyles } from "../../../utils"; import { sendExtensionMessage, setElementStyles } from "../../../utils";
import { import {
BackgroundPortMessageHandlers, BackgroundPortMessageHandlers,
AutofillInlineMenuIframeService as AutofillOverlayIframeServiceInterface, AutofillInlineMenuIframeService as AutofillInlineMenuIframeServiceInterface,
AutofillOverlayIframeExtensionMessage, AutofillInlineMenuIframeExtensionMessage,
} from "../abstractions/autofill-inline-menu-iframe.service"; } from "../abstractions/autofill-inline-menu-iframe.service";
export class AutofillInlineMenuIframeService implements AutofillOverlayIframeServiceInterface { export class AutofillInlineMenuIframeService implements AutofillInlineMenuIframeServiceInterface {
private sendExtensionMessage = sendExtensionMessage; private sendExtensionMessage = sendExtensionMessage;
private port: chrome.runtime.Port | null = null; private port: chrome.runtime.Port | null = null;
private portKey: string; private portKey: string;
@@ -167,7 +167,7 @@ export class AutofillInlineMenuIframeService implements AutofillOverlayIframeSer
* @param port * @param port
*/ */
private handlePortMessage = ( private handlePortMessage = (
message: AutofillOverlayIframeExtensionMessage, message: AutofillInlineMenuIframeExtensionMessage,
port: chrome.runtime.Port, port: chrome.runtime.Port,
) => { ) => {
if (port.name !== this.portName) { if (port.name !== this.portName) {
@@ -183,13 +183,13 @@ export class AutofillInlineMenuIframeService implements AutofillOverlayIframeSer
}; };
/** /**
* Handles the initialization of the autofill overlay. This includes setting * Handles the initialization of the autofill inline menu. This includes setting
* the port key and sending a message to the iframe to initialize the overlay. * the port key and sending a message to the iframe to initialize the inline menu.
* *
* @param message * @param message
* @private * @private
*/ */
private initAutofillInlineMenu(message: AutofillOverlayIframeExtensionMessage) { private initAutofillInlineMenu(message: AutofillInlineMenuIframeExtensionMessage) {
this.portKey = message.portKey; this.portKey = message.portKey;
if (message.command === "initAutofillInlineMenuList") { if (message.command === "initAutofillInlineMenuList") {
this.initAutofillInlineMenuList(message); this.initAutofillInlineMenuList(message);
@@ -200,12 +200,12 @@ export class AutofillInlineMenuIframeService implements AutofillOverlayIframeSer
} }
/** /**
* Handles initialization of the autofill overlay list. This includes setting * Handles initialization of the autofill inline menu list. This includes setting
* the theme and sending a message to the iframe to initialize the overlay. * the theme and sending a message to the iframe to initialize the inline menu.
* *
* @param message - The message sent from the iframe * @param message - The message sent from the iframe
*/ */
private initAutofillInlineMenuList(message: AutofillOverlayIframeExtensionMessage) { private initAutofillInlineMenuList(message: AutofillInlineMenuIframeExtensionMessage) {
const { theme } = message; const { theme } = message;
let borderColor: string; let borderColor: string;
let verifiedTheme = theme; let verifiedTheme = theme;
@@ -320,7 +320,7 @@ export class AutofillInlineMenuIframeService implements AutofillOverlayIframeSer
}; };
/** /**
* Triggers a forced closure of the autofill overlay. This is used when the * Triggers a forced closure of the autofill inline menu. This is used when the
* mutation observer is triggered excessively. * mutation observer is triggered excessively.
*/ */
private forceCloseAutofillInlineMenu() { private forceCloseAutofillInlineMenu() {
@@ -378,7 +378,7 @@ export class AutofillInlineMenuIframeService implements AutofillOverlayIframeSer
/** /**
* Identifies if the mutation observer is triggering excessive iterations. * Identifies if the mutation observer is triggering excessive iterations.
* Will remove the autofill overlay if any set mutation observer is * Will remove the autofill inline menu if any set mutation observer is
* triggering excessive iterations. * triggering excessive iterations.
*/ */
private isTriggeringExcessiveMutationObserverIterations() { private isTriggeringExcessiveMutationObserverIterations() {

View File

@@ -1,8 +1,8 @@
import AutofillInlineMenuListIframe from "./autofill-inline-menu-list-iframe"; import { AutofillInlineMenuListIframe } from "./autofill-inline-menu-list-iframe";
describe("AutofillOverlayListIframe", () => { describe("AutofillInlineMenuListIframe", () => {
window.customElements.define( window.customElements.define(
"autofill-overlay-list-iframe", "autofill-inline-menu-list-iframe",
class extends HTMLElement { class extends HTMLElement {
constructor() { constructor() {
super(); super();
@@ -16,9 +16,10 @@ describe("AutofillOverlayListIframe", () => {
}); });
it("creates a custom element that is an instance of the AutofillIframeElement parent class", () => { it("creates a custom element that is an instance of the AutofillIframeElement parent class", () => {
document.body.innerHTML = "<autofill-overlay-list-iframe></autofill-overlay-list-iframe>"; document.body.innerHTML =
"<autofill-inline-menu-list-iframe></autofill-inline-menu-list-iframe>";
const iframe = document.querySelector("autofill-overlay-list-iframe"); const iframe = document.querySelector("autofill-inline-menu-list-iframe");
expect(iframe).toBeInstanceOf(HTMLElement); expect(iframe).toBeInstanceOf(HTMLElement);
expect(iframe.shadowRoot).toBeDefined(); expect(iframe.shadowRoot).toBeDefined();

View File

@@ -2,7 +2,7 @@ import { AutofillOverlayPort } from "../../../enums/autofill-overlay.enum";
import { AutofillInlineMenuIframeElement } from "./autofill-inline-menu-iframe-element"; import { AutofillInlineMenuIframeElement } from "./autofill-inline-menu-iframe-element";
class AutofillInlineMenuListIframe extends AutofillInlineMenuIframeElement { export class AutofillInlineMenuListIframe extends AutofillInlineMenuIframeElement {
constructor(element: HTMLElement) { constructor(element: HTMLElement) {
super( super(
element, element,
@@ -21,5 +21,3 @@ class AutofillInlineMenuListIframe extends AutofillInlineMenuIframeElement {
); );
} }
} }
export default AutofillInlineMenuListIframe;

View File

@@ -0,0 +1,536 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`AutofillOverlayList initAutofillInlineMenuList the list of ciphers for an authenticated user creates the view for a list of ciphers 1`] = `
<div
class="overlay-list-container theme_light"
>
<ul
class="overlay-actions-list"
role="list"
>
<li
class="overlay-actions-list-item"
role="listitem"
>
<div
class="cipher-container"
>
<button
aria-description="partialUsername, username1"
aria-label="fillCredentialsFor website login 1"
class="fill-cipher-button"
tabindex="-1"
>
<span
aria-hidden="true"
class="cipher-icon"
style="background-image: url(https://jest-testing-website.com/image.png);"
/>
<span
class="cipher-details"
>
<span
class="cipher-name"
title="website login 1"
>
website login 1
</span>
<span
class="cipher-user-login"
title="username1"
>
username1
</span>
</span>
</button>
<button
aria-label="view website login 1, opensInANewWindow"
class="view-cipher-button"
tabindex="-1"
>
<svg
aria-hidden="true"
fill="none"
height="20"
viewBox="0 0 20 20"
width="20"
xmlns="http://www.w3.org/2000/svg"
>
<g
clip-path="url(#a)"
>
<path
d="M16.587 7.932H5.9a.455.455 0 0 1-.31-.12.393.393 0 0 1-.127-.287c0-.108.046-.211.128-.288a.455.455 0 0 1 .309-.119h10.687c.117 0 .228.043.31.12.082.076.128.179.128.287a.393.393 0 0 1-.128.288.455.455 0 0 1-.31.119Zm0 2.474H5.9a.455.455 0 0 1-.31-.119.393.393 0 0 1-.127-.287c0-.108.046-.212.128-.288a.455.455 0 0 1 .309-.119h10.687c.117 0 .228.043.31.12.082.075.128.179.128.287a.393.393 0 0 1-.128.287.455.455 0 0 1-.31.12Zm0 2.468H5.9a.455.455 0 0 1-.31-.119.393.393 0 0 1-.127-.287c0-.108.046-.212.128-.288a.455.455 0 0 1 .309-.119h10.687c.117 0 .228.043.31.12.082.075.128.179.128.287a.393.393 0 0 1-.128.287.455.455 0 0 1-.31.12Zm2.163-8.103v10.457H1.25V4.771h17.5Zm0-1.162H1.25a1.3 1.3 0 0 0-.884.34A1.122 1.122 0 0 0 0 4.772v10.457c0 .308.132.604.366.822a1.3 1.3 0 0 0 .884.34h17.5a1.3 1.3 0 0 0 .884-.34c.234-.218.366-.514.366-.822V4.771c0-.308-.132-.603-.366-.821a1.3 1.3 0 0 0-.884-.34ZM3.213 8.01c.287 0 .52-.217.52-.484s-.234-.483-.52-.483c-.288 0-.52.216-.52.483s.233.483.52.483Zm0 4.903c.287 0 .52-.217.52-.484 0-.266-.234-.483-.52-.483-.287 0-.52.216-.52.483s.233.484.52.484Zm0-2.452c.287 0 .52-.216.52-.483 0-.268-.234-.484-.52-.484-.288 0-.52.216-.52.484 0 .267.233.483.52.483Z"
fill="#175DDC"
/>
</g>
<defs>
<clippath
id="a"
>
<path
d="M0 .113h20v19.773H0z"
fill="#fff"
/>
</clippath>
</defs>
</svg>
</button>
</div>
</li>
<li
class="overlay-actions-list-item"
role="listitem"
>
<div
class="cipher-container"
>
<button
aria-description="partialUsername, username2"
aria-label="fillCredentialsFor website login 2"
class="fill-cipher-button"
tabindex="-1"
>
<span
aria-hidden="true"
class="cipher-icon bwi bw-icon"
/>
<span
class="cipher-details"
>
<span
class="cipher-name"
title="website login 2"
>
website login 2
</span>
<span
class="cipher-user-login"
title="username2"
>
username2
</span>
</span>
</button>
<button
aria-label="view website login 2, opensInANewWindow"
class="view-cipher-button"
tabindex="-1"
>
<svg
aria-hidden="true"
fill="none"
height="20"
viewBox="0 0 20 20"
width="20"
xmlns="http://www.w3.org/2000/svg"
>
<g
clip-path="url(#a)"
>
<path
d="M16.587 7.932H5.9a.455.455 0 0 1-.31-.12.393.393 0 0 1-.127-.287c0-.108.046-.211.128-.288a.455.455 0 0 1 .309-.119h10.687c.117 0 .228.043.31.12.082.076.128.179.128.287a.393.393 0 0 1-.128.288.455.455 0 0 1-.31.119Zm0 2.474H5.9a.455.455 0 0 1-.31-.119.393.393 0 0 1-.127-.287c0-.108.046-.212.128-.288a.455.455 0 0 1 .309-.119h10.687c.117 0 .228.043.31.12.082.075.128.179.128.287a.393.393 0 0 1-.128.287.455.455 0 0 1-.31.12Zm0 2.468H5.9a.455.455 0 0 1-.31-.119.393.393 0 0 1-.127-.287c0-.108.046-.212.128-.288a.455.455 0 0 1 .309-.119h10.687c.117 0 .228.043.31.12.082.075.128.179.128.287a.393.393 0 0 1-.128.287.455.455 0 0 1-.31.12Zm2.163-8.103v10.457H1.25V4.771h17.5Zm0-1.162H1.25a1.3 1.3 0 0 0-.884.34A1.122 1.122 0 0 0 0 4.772v10.457c0 .308.132.604.366.822a1.3 1.3 0 0 0 .884.34h17.5a1.3 1.3 0 0 0 .884-.34c.234-.218.366-.514.366-.822V4.771c0-.308-.132-.603-.366-.821a1.3 1.3 0 0 0-.884-.34ZM3.213 8.01c.287 0 .52-.217.52-.484s-.234-.483-.52-.483c-.288 0-.52.216-.52.483s.233.483.52.483Zm0 4.903c.287 0 .52-.217.52-.484 0-.266-.234-.483-.52-.483-.287 0-.52.216-.52.483s.233.484.52.484Zm0-2.452c.287 0 .52-.216.52-.483 0-.268-.234-.484-.52-.484-.288 0-.52.216-.52.484 0 .267.233.483.52.483Z"
fill="#175DDC"
/>
</g>
<defs>
<clippath
id="a"
>
<path
d="M0 .113h20v19.773H0z"
fill="#fff"
/>
</clippath>
</defs>
</svg>
</button>
</div>
</li>
<li
class="overlay-actions-list-item"
role="listitem"
>
<div
class="cipher-container"
>
<button
aria-description="partialUsername, "
aria-label="fillCredentialsFor "
class="fill-cipher-button"
tabindex="-1"
>
<span
aria-hidden="true"
class="cipher-icon bwi bw-icon"
/>
<span
class="cipher-details"
/>
</button>
<button
aria-label="view , opensInANewWindow"
class="view-cipher-button"
tabindex="-1"
>
<svg
aria-hidden="true"
fill="none"
height="20"
viewBox="0 0 20 20"
width="20"
xmlns="http://www.w3.org/2000/svg"
>
<g
clip-path="url(#a)"
>
<path
d="M16.587 7.932H5.9a.455.455 0 0 1-.31-.12.393.393 0 0 1-.127-.287c0-.108.046-.211.128-.288a.455.455 0 0 1 .309-.119h10.687c.117 0 .228.043.31.12.082.076.128.179.128.287a.393.393 0 0 1-.128.288.455.455 0 0 1-.31.119Zm0 2.474H5.9a.455.455 0 0 1-.31-.119.393.393 0 0 1-.127-.287c0-.108.046-.212.128-.288a.455.455 0 0 1 .309-.119h10.687c.117 0 .228.043.31.12.082.075.128.179.128.287a.393.393 0 0 1-.128.287.455.455 0 0 1-.31.12Zm0 2.468H5.9a.455.455 0 0 1-.31-.119.393.393 0 0 1-.127-.287c0-.108.046-.212.128-.288a.455.455 0 0 1 .309-.119h10.687c.117 0 .228.043.31.12.082.075.128.179.128.287a.393.393 0 0 1-.128.287.455.455 0 0 1-.31.12Zm2.163-8.103v10.457H1.25V4.771h17.5Zm0-1.162H1.25a1.3 1.3 0 0 0-.884.34A1.122 1.122 0 0 0 0 4.772v10.457c0 .308.132.604.366.822a1.3 1.3 0 0 0 .884.34h17.5a1.3 1.3 0 0 0 .884-.34c.234-.218.366-.514.366-.822V4.771c0-.308-.132-.603-.366-.821a1.3 1.3 0 0 0-.884-.34ZM3.213 8.01c.287 0 .52-.217.52-.484s-.234-.483-.52-.483c-.288 0-.52.216-.52.483s.233.483.52.483Zm0 4.903c.287 0 .52-.217.52-.484 0-.266-.234-.483-.52-.483-.287 0-.52.216-.52.483s.233.484.52.484Zm0-2.452c.287 0 .52-.216.52-.483 0-.268-.234-.484-.52-.484-.288 0-.52.216-.52.484 0 .267.233.483.52.483Z"
fill="#175DDC"
/>
</g>
<defs>
<clippath
id="a"
>
<path
d="M0 .113h20v19.773H0z"
fill="#fff"
/>
</clippath>
</defs>
</svg>
</button>
</div>
</li>
<li
class="overlay-actions-list-item"
role="listitem"
>
<div
class="cipher-container"
>
<button
aria-description="partialUsername, username4"
aria-label="fillCredentialsFor website login 4"
class="fill-cipher-button"
tabindex="-1"
>
<span
aria-hidden="true"
class="cipher-icon"
>
<svg
aria-hidden="true"
fill="none"
height="25"
viewBox="0 0 24 25"
width="24"
xmlns="http://www.w3.org/2000/svg"
>
<path
clip-rule="evenodd"
d="M18.026 17.842c-1.418 1.739-3.517 2.84-5.86 2.84a7.364 7.364 0 0 1-3.431-.848l.062-.15.062-.151.063-.157c.081-.203.17-.426.275-.646.133-.28.275-.522.426-.68.026-.028.101-.075.275-.115.165-.037.376-.059.629-.073.138-.008.288-.014.447-.02.399-.016.847-.034 1.266-.092.314-.044.566-.131.755-.271a.884.884 0 0 0 .352-.555c.037-.2.008-.392-.03-.543-.035-.135-.084-.264-.12-.355l-.01-.03a4.26 4.26 0 0 0-.145-.33c-.126-.264-.237-.497-.288-1.085-.03-.344.09-.73.251-1.138l.089-.22c.05-.123.1-.247.14-.355.064-.171.129-.375.129-.566a1.51 1.51 0 0 0-.134-.569 2.573 2.573 0 0 0-.319-.547c-.246-.323-.635-.669-1.093-.669-.44 0-1.006.169-1.487.368-.246.102-.48.216-.68.33-.192.111-.372.235-.492.359-.93.96-1.48 1.239-1.81 1.258-.277.017-.478-.15-.736-.525a9.738 9.738 0 0 1-.19-.29l-.006-.01a11.568 11.568 0 0 0-.198-.305 2.76 2.76 0 0 0-.521-.6 1.39 1.39 0 0 0-1.088-.314 8.302 8.302 0 0 1 1.987-3.936c.055.342.146.626.272.856.23.42.561.64.926.716.406.086.857-.061 1.26-.216.125-.047.248-.097.372-.147.309-.125.618-.25.947-.341.26-.072.581-.057.959.012.264.049.529.118.8.19l.36.091c.379.094.782.178 1.135.148.374-.032.733-.197.934-.623a.874.874 0 0 0 .024-.752c-.087-.197-.24-.355-.35-.47-.26-.267-.412-.427-.412-.685 0-.125.037-.2.09-.263a.982.982 0 0 1 .303-.211c.059-.03.119-.058.183-.089l.036-.016a3.79 3.79 0 0 0 .236-.118c.047-.026.098-.056.148-.093 1.936.747 3.51 2.287 4.368 4.249a7.739 7.739 0 0 0-.031-.004c-.38-.047-.738-.056-1.063.061-.34.123-.603.368-.817.74-.122.211-.284.43-.463.67l-.095.129c-.207.281-.431.595-.58.92-.15.326-.245.705-.142 1.103.104.397.387.738.837 1.036.099.065.225.112.314.145l.02.008c.108.04.195.074.268.117.07.042.106.08.124.114.017.03.037.087.022.206-.047.376-.069.73-.052 1.034.017.292.071.59.218.809.118.174.12.421.108.786v.01a2.46 2.46 0 0 0 .021.518.809.809 0 0 0 .15.35Zm1.357.059a9.654 9.654 0 0 0 1.62-5.386c0-5.155-3.957-9.334-8.836-9.334-4.88 0-8.836 4.179-8.836 9.334 0 3.495 1.82 6.543 4.513 8.142v.093h.161a8.426 8.426 0 0 0 4.162 1.098c2.953 0 5.568-1.53 7.172-3.882a.569.569 0 0 0 .048-.062l-.004-.003ZM8.152 19.495a43.345 43.345 0 0 1 .098-.238l.057-.142c.082-.205.182-.455.297-.698.143-.301.323-.624.552-.864.163-.172.392-.254.602-.302.219-.05.473-.073.732-.088.162-.01.328-.016.495-.023.386-.015.782-.03 1.168-.084.255-.036.392-.099.461-.15.06-.045.076-.084.083-.12a.534.534 0 0 0-.02-.223 2.552 2.552 0 0 0-.095-.278l-.01-.027a3.128 3.128 0 0 0-.104-.232c-.134-.282-.31-.65-.374-1.381-.046-.533.138-1.063.3-1.472.035-.09.069-.172.1-.249.046-.11.086-.21.123-.31.062-.169.083-.264.083-.312a.812.812 0 0 0-.076-.283 1.867 1.867 0 0 0-.23-.394c-.21-.274-.428-.408-.577-.408-.315 0-.788.13-1.246.32a5.292 5.292 0 0 0-.603.293 1.727 1.727 0 0 0-.347.244c-.936.968-1.641 1.421-2.235 1.457-.646.04-1.036-.413-1.31-.813-.07-.103-.139-.21-.203-.311l-.005-.007c-.064-.101-.125-.197-.188-.29a2.098 2.098 0 0 0-.387-.453.748.748 0 0 0-.436-.18c-.1-.006-.22.005-.365.046a8.707 8.707 0 0 0-.056.992c0 2.957 1.488 5.547 3.716 6.98Zm10.362-2.316.003-.192.002-.046c.01-.305.026-.786-.232-1.169-.036-.054-.082-.189-.096-.444-.014-.243.003-.55.047-.9a1.051 1.051 0 0 0-.105-.649.987.987 0 0 0-.374-.374 2.285 2.285 0 0 0-.367-.166h-.003a1.243 1.243 0 0 1-.205-.088c-.369-.244-.505-.46-.549-.629-.044-.168-.015-.364.099-.61.115-.25.297-.511.507-.796l.089-.12c.178-.239.368-.495.512-.745.152-.263.302-.382.466-.441.18-.065.416-.073.77-.03.142.018.275.04.397.063.274.837.423 1.736.423 2.671a8.45 8.45 0 0 1-1.384 4.665Zm-4.632-12.63a7.362 7.362 0 0 0-1.715-.201c-1.89 0-3.621.716-4.965 1.905.025.54.12.887.24 1.105.13.238.295.34.482.38.2.042.484-.027.905-.188l.328-.13c.32-.13.681-.275 1.048-.377.398-.111.833-.075 1.24 0 .289.053.59.132.871.205l.326.084c.383.094.694.151.932.13.216-.017.326-.092.395-.237.039-.083.027-.114.014-.144-.027-.062-.088-.136-.212-.264l-.043-.044c-.218-.222-.567-.578-.567-1.142 0-.305.101-.547.262-.734.137-.159.308-.267.46-.347Z"
fill="#777"
fill-rule="evenodd"
/>
</svg>
</span>
<span
class="cipher-details"
>
<span
class="cipher-name"
title="website login 4"
>
website login 4
</span>
<span
class="cipher-user-login"
title="username4"
>
username4
</span>
</span>
</button>
<button
aria-label="view website login 4, opensInANewWindow"
class="view-cipher-button"
tabindex="-1"
>
<svg
aria-hidden="true"
fill="none"
height="20"
viewBox="0 0 20 20"
width="20"
xmlns="http://www.w3.org/2000/svg"
>
<g
clip-path="url(#a)"
>
<path
d="M16.587 7.932H5.9a.455.455 0 0 1-.31-.12.393.393 0 0 1-.127-.287c0-.108.046-.211.128-.288a.455.455 0 0 1 .309-.119h10.687c.117 0 .228.043.31.12.082.076.128.179.128.287a.393.393 0 0 1-.128.288.455.455 0 0 1-.31.119Zm0 2.474H5.9a.455.455 0 0 1-.31-.119.393.393 0 0 1-.127-.287c0-.108.046-.212.128-.288a.455.455 0 0 1 .309-.119h10.687c.117 0 .228.043.31.12.082.075.128.179.128.287a.393.393 0 0 1-.128.287.455.455 0 0 1-.31.12Zm0 2.468H5.9a.455.455 0 0 1-.31-.119.393.393 0 0 1-.127-.287c0-.108.046-.212.128-.288a.455.455 0 0 1 .309-.119h10.687c.117 0 .228.043.31.12.082.075.128.179.128.287a.393.393 0 0 1-.128.287.455.455 0 0 1-.31.12Zm2.163-8.103v10.457H1.25V4.771h17.5Zm0-1.162H1.25a1.3 1.3 0 0 0-.884.34A1.122 1.122 0 0 0 0 4.772v10.457c0 .308.132.604.366.822a1.3 1.3 0 0 0 .884.34h17.5a1.3 1.3 0 0 0 .884-.34c.234-.218.366-.514.366-.822V4.771c0-.308-.132-.603-.366-.821a1.3 1.3 0 0 0-.884-.34ZM3.213 8.01c.287 0 .52-.217.52-.484s-.234-.483-.52-.483c-.288 0-.52.216-.52.483s.233.483.52.483Zm0 4.903c.287 0 .52-.217.52-.484 0-.266-.234-.483-.52-.483-.287 0-.52.216-.52.483s.233.484.52.484Zm0-2.452c.287 0 .52-.216.52-.483 0-.268-.234-.484-.52-.484-.288 0-.52.216-.52.484 0 .267.233.483.52.483Z"
fill="#175DDC"
/>
</g>
<defs>
<clippath
id="a"
>
<path
d="M0 .113h20v19.773H0z"
fill="#fff"
/>
</clippath>
</defs>
</svg>
</button>
</div>
</li>
<li
class="overlay-actions-list-item"
role="listitem"
>
<div
class="cipher-container"
>
<button
aria-description="partialUsername, username5"
aria-label="fillCredentialsFor website login 5"
class="fill-cipher-button"
tabindex="-1"
>
<span
aria-hidden="true"
class="cipher-icon"
style="background-image: url(https://jest-testing-website.com/image.png);"
/>
<span
class="cipher-details"
>
<span
class="cipher-name"
title="website login 5"
>
website login 5
</span>
<span
class="cipher-user-login"
title="username5"
>
username5
</span>
</span>
</button>
<button
aria-label="view website login 5, opensInANewWindow"
class="view-cipher-button"
tabindex="-1"
>
<svg
aria-hidden="true"
fill="none"
height="20"
viewBox="0 0 20 20"
width="20"
xmlns="http://www.w3.org/2000/svg"
>
<g
clip-path="url(#a)"
>
<path
d="M16.587 7.932H5.9a.455.455 0 0 1-.31-.12.393.393 0 0 1-.127-.287c0-.108.046-.211.128-.288a.455.455 0 0 1 .309-.119h10.687c.117 0 .228.043.31.12.082.076.128.179.128.287a.393.393 0 0 1-.128.288.455.455 0 0 1-.31.119Zm0 2.474H5.9a.455.455 0 0 1-.31-.119.393.393 0 0 1-.127-.287c0-.108.046-.212.128-.288a.455.455 0 0 1 .309-.119h10.687c.117 0 .228.043.31.12.082.075.128.179.128.287a.393.393 0 0 1-.128.287.455.455 0 0 1-.31.12Zm0 2.468H5.9a.455.455 0 0 1-.31-.119.393.393 0 0 1-.127-.287c0-.108.046-.212.128-.288a.455.455 0 0 1 .309-.119h10.687c.117 0 .228.043.31.12.082.075.128.179.128.287a.393.393 0 0 1-.128.287.455.455 0 0 1-.31.12Zm2.163-8.103v10.457H1.25V4.771h17.5Zm0-1.162H1.25a1.3 1.3 0 0 0-.884.34A1.122 1.122 0 0 0 0 4.772v10.457c0 .308.132.604.366.822a1.3 1.3 0 0 0 .884.34h17.5a1.3 1.3 0 0 0 .884-.34c.234-.218.366-.514.366-.822V4.771c0-.308-.132-.603-.366-.821a1.3 1.3 0 0 0-.884-.34ZM3.213 8.01c.287 0 .52-.217.52-.484s-.234-.483-.52-.483c-.288 0-.52.216-.52.483s.233.483.52.483Zm0 4.903c.287 0 .52-.217.52-.484 0-.266-.234-.483-.52-.483-.287 0-.52.216-.52.483s.233.484.52.484Zm0-2.452c.287 0 .52-.216.52-.483 0-.268-.234-.484-.52-.484-.288 0-.52.216-.52.484 0 .267.233.483.52.483Z"
fill="#175DDC"
/>
</g>
<defs>
<clippath
id="a"
>
<path
d="M0 .113h20v19.773H0z"
fill="#fff"
/>
</clippath>
</defs>
</svg>
</button>
</div>
</li>
<li
class="overlay-actions-list-item"
role="listitem"
>
<div
class="cipher-container"
>
<button
aria-description="partialUsername, username6"
aria-label="fillCredentialsFor website login 6"
class="fill-cipher-button"
tabindex="-1"
>
<span
aria-hidden="true"
class="cipher-icon"
style="background-image: url(https://jest-testing-website.com/image.png);"
/>
<span
class="cipher-details"
>
<span
class="cipher-name"
title="website login 6"
>
website login 6
</span>
<span
class="cipher-user-login"
title="username6"
>
username6
</span>
</span>
</button>
<button
aria-label="view website login 6, opensInANewWindow"
class="view-cipher-button"
tabindex="-1"
>
<svg
aria-hidden="true"
fill="none"
height="20"
viewBox="0 0 20 20"
width="20"
xmlns="http://www.w3.org/2000/svg"
>
<g
clip-path="url(#a)"
>
<path
d="M16.587 7.932H5.9a.455.455 0 0 1-.31-.12.393.393 0 0 1-.127-.287c0-.108.046-.211.128-.288a.455.455 0 0 1 .309-.119h10.687c.117 0 .228.043.31.12.082.076.128.179.128.287a.393.393 0 0 1-.128.288.455.455 0 0 1-.31.119Zm0 2.474H5.9a.455.455 0 0 1-.31-.119.393.393 0 0 1-.127-.287c0-.108.046-.212.128-.288a.455.455 0 0 1 .309-.119h10.687c.117 0 .228.043.31.12.082.075.128.179.128.287a.393.393 0 0 1-.128.287.455.455 0 0 1-.31.12Zm0 2.468H5.9a.455.455 0 0 1-.31-.119.393.393 0 0 1-.127-.287c0-.108.046-.212.128-.288a.455.455 0 0 1 .309-.119h10.687c.117 0 .228.043.31.12.082.075.128.179.128.287a.393.393 0 0 1-.128.287.455.455 0 0 1-.31.12Zm2.163-8.103v10.457H1.25V4.771h17.5Zm0-1.162H1.25a1.3 1.3 0 0 0-.884.34A1.122 1.122 0 0 0 0 4.772v10.457c0 .308.132.604.366.822a1.3 1.3 0 0 0 .884.34h17.5a1.3 1.3 0 0 0 .884-.34c.234-.218.366-.514.366-.822V4.771c0-.308-.132-.603-.366-.821a1.3 1.3 0 0 0-.884-.34ZM3.213 8.01c.287 0 .52-.217.52-.484s-.234-.483-.52-.483c-.288 0-.52.216-.52.483s.233.483.52.483Zm0 4.903c.287 0 .52-.217.52-.484 0-.266-.234-.483-.52-.483-.287 0-.52.216-.52.483s.233.484.52.484Zm0-2.452c.287 0 .52-.216.52-.483 0-.268-.234-.484-.52-.484-.288 0-.52.216-.52.484 0 .267.233.483.52.483Z"
fill="#175DDC"
/>
</g>
<defs>
<clippath
id="a"
>
<path
d="M0 .113h20v19.773H0z"
fill="#fff"
/>
</clippath>
</defs>
</svg>
</button>
</div>
</li>
</ul>
</div>
`;
exports[`AutofillOverlayList initAutofillInlineMenuList the locked overlay for an unauthenticated user creates the views for the locked overlay 1`] = `
<div
class="overlay-list-container theme_light"
>
<div
class="locked-overlay overlay-list-message"
id="locked-overlay-description"
>
unlockYourAccount
</div>
<div
class="overlay-list-button-container"
>
<button
aria-label="unlockAccount, opensInANewWindow"
class="unlock-button overlay-list-button"
id="unlock-button"
tabindex="-1"
>
<svg
aria-hidden="true"
fill="none"
height="17"
viewBox="0 0 17 17"
width="17"
xmlns="http://www.w3.org/2000/svg"
>
<g
clip-path="url(#a)"
>
<path
d="M8.799 11.633a.68.68 0 0 0-.639.422.695.695 0 0 0-.054.264.682.682 0 0 0 .374.6v1.13a.345.345 0 1 0 .693 0v-1.17a.68.68 0 0 0 .315-.56.695.695 0 0 0-.204-.486.682.682 0 0 0-.485-.2Zm4.554-4.657h-7.11a.438.438 0 0 1-.406-.26A3.81 3.81 0 0 1 5.584 4.3c.112-.435.312-.842.588-1.195A3.196 3.196 0 0 1 7.19 2.25a3.468 3.468 0 0 1 3.225-.059A3.62 3.62 0 0 1 11.94 3.71l.327.59a.502.502 0 1 0 .885-.483l-.307-.552a4.689 4.689 0 0 0-2.209-2.078 4.466 4.466 0 0 0-3.936.185A4.197 4.197 0 0 0 5.37 2.49a4.234 4.234 0 0 0-.768 1.565 4.714 4.714 0 0 0 .162 2.682.182.182 0 0 1-.085.22.173.173 0 0 1-.082.02h-.353a1.368 1.368 0 0 0-1.277.842c-.07.168-.107.348-.109.53v7.1a1.392 1.392 0 0 0 .412.974 1.352 1.352 0 0 0 .974.394h9.117c.363.001.711-.142.97-.4a1.39 1.39 0 0 0 .407-.972v-7.1a1.397 1.397 0 0 0-.414-.973 1.368 1.368 0 0 0-.972-.396Zm.37 8.469a.373.373 0 0 1-.11.26.364.364 0 0 1-.26.107H4.246a.366.366 0 0 1-.26-.107.374.374 0 0 1-.11-.261V8.349a.374.374 0 0 1 .11-.26.366.366 0 0 1 .26-.108h9.116a.366.366 0 0 1 .37.367l-.008 7.097Z"
fill="#175DDC"
/>
</g>
<defs>
<clippath
id="a"
>
<path
d="M.798.817h16v16h-16z"
fill="#fff"
/>
</clippath>
</defs>
</svg>
unlockAccount
</button>
</div>
</div>
`;
exports[`AutofillOverlayList initAutofillInlineMenuList the overlay with an empty list of ciphers creates the views for the no results overlay 1`] = `
<div
class="overlay-list-container theme_light"
>
<div
class="no-items overlay-list-message"
>
noItemsToShow
</div>
<div
class="overlay-list-button-container"
>
<button
aria-label="addNewVaultItem, opensInANewWindow"
class="add-new-item-button overlay-list-button"
id="new-item-button"
tabindex="-1"
>
<svg
aria-hidden="true"
fill="none"
height="17"
viewBox="0 0 16 17"
width="16"
xmlns="http://www.w3.org/2000/svg"
>
<g
clip-path="url(#a)"
>
<path
d="M15.222 7.914H8.963a.471.471 0 0 1-.34-.147.512.512 0 0 1-.142-.353V.99c0-.133-.05-.26-.14-.354a.471.471 0 0 0-.68 0 .51.51 0 0 0-.142.354v6.424c0 .132-.051.26-.142.353a.474.474 0 0 1-.34.147H.777a.47.47 0 0 0-.34.146.5.5 0 0 0-.14.354.522.522 0 0 0 .14.353.48.48 0 0 0 .34.147h6.26c.128 0 .25.052.34.146.09.094.142.221.142.354v6.576c0 .132.05.26.14.353a.471.471 0 0 0 .68 0 .512.512 0 0 0 .142-.353V9.414c0-.133.051-.26.142-.354a.474.474 0 0 1 .34-.146h6.26c.127 0 .25-.053.34-.147a.511.511 0 0 0 0-.707.472.472 0 0 0-.34-.146Z"
fill="#175DDC"
/>
</g>
<defs>
<clippath
id="a"
>
<path
d="M0 .49h16v16H0z"
fill="#fff"
/>
</clippath>
</defs>
</svg>
newItem
</button>
</div>
</div>
`;

View File

@@ -5,22 +5,22 @@ import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authenticatio
import { createInitAutofillOverlayListMessageMock } from "../../../../spec/autofill-mocks"; import { createInitAutofillOverlayListMessageMock } from "../../../../spec/autofill-mocks";
import { flushPromises, postWindowMessage } from "../../../../spec/testing-utils"; import { flushPromises, postWindowMessage } from "../../../../spec/testing-utils";
import AutofillOverlayList from "./autofill-overlay-list"; import { AutofillInlineMenuList } from "./autofill-inline-menu-list";
describe("AutofillOverlayList", () => { describe("AutofillOverlayList", () => {
globalThis.customElements.define("autofill-overlay-list", AutofillOverlayList); globalThis.customElements.define("autofill-inline-menu-list", AutofillInlineMenuList);
global.ResizeObserver = jest.fn().mockImplementation(() => ({ global.ResizeObserver = jest.fn().mockImplementation(() => ({
observe: jest.fn(), observe: jest.fn(),
unobserve: jest.fn(), unobserve: jest.fn(),
disconnect: jest.fn(), disconnect: jest.fn(),
})); }));
let autofillOverlayList: AutofillOverlayList; let autofillOverlayList: AutofillInlineMenuList;
const portKey: string = "overlayListPortKey"; const portKey: string = "overlayListPortKey";
beforeEach(() => { beforeEach(() => {
document.body.innerHTML = `<autofill-overlay-list></autofill-overlay-list>`; document.body.innerHTML = `<autofill-inline-menu-list></autofill-inline-menu-list>`;
autofillOverlayList = document.querySelector("autofill-overlay-list"); autofillOverlayList = document.querySelector("autofill-inline-menu-list");
jest.spyOn(globalThis.document, "createElement"); jest.spyOn(globalThis.document, "createElement");
jest.spyOn(globalThis.parent, "postMessage"); jest.spyOn(globalThis.parent, "postMessage");
}); });
@@ -312,7 +312,7 @@ describe("AutofillOverlayList", () => {
postWindowMessage(createInitAutofillOverlayListMessageMock()); postWindowMessage(createInitAutofillOverlayListMessageMock());
const updateCiphersSpy = jest.spyOn(autofillOverlayList as any, "updateListItems"); const updateCiphersSpy = jest.spyOn(autofillOverlayList as any, "updateListItems");
postWindowMessage({ command: "updateOverlayListCiphers" }); postWindowMessage({ command: "updateAutofillInlineMenuListCiphers" });
expect(updateCiphersSpy).toHaveBeenCalled(); expect(updateCiphersSpy).toHaveBeenCalled();
}); });

View File

@@ -7,12 +7,12 @@ import { OverlayCipherData } from "../../../../background/abstractions/overlay.b
import { buildSvgDomElement } from "../../../../utils"; import { buildSvgDomElement } from "../../../../utils";
import { globeIcon, lockIcon, plusIcon, viewCipherIcon } from "../../../../utils/svg-icons"; import { globeIcon, lockIcon, plusIcon, viewCipherIcon } from "../../../../utils/svg-icons";
import { import {
InitAutofillOverlayListMessage, InitAutofillInlineMenuListMessage,
OverlayListWindowMessageHandlers, AutofillInlineMenuListWindowMessageHandlers,
} from "../../abstractions/autofill-inline-menu-list"; } from "../../abstractions/autofill-inline-menu-list";
import { AutofillInlineMenuPageElement } from "../shared/autofill-inline-menu-page-element"; import { AutofillInlineMenuPageElement } from "../shared/autofill-inline-menu-page-element";
class AutofillOverlayList extends AutofillInlineMenuPageElement { export class AutofillInlineMenuList extends AutofillInlineMenuPageElement {
private overlayListContainer: HTMLDivElement; private overlayListContainer: HTMLDivElement;
private resizeObserver: ResizeObserver; private resizeObserver: ResizeObserver;
private eventHandlersMemo: { [key: string]: EventListener } = {}; private eventHandlersMemo: { [key: string]: EventListener } = {};
@@ -22,10 +22,10 @@ class AutofillOverlayList extends AutofillInlineMenuPageElement {
private cipherListScrollDebounceTimeout: number | NodeJS.Timeout; private cipherListScrollDebounceTimeout: number | NodeJS.Timeout;
private currentCipherIndex = 0; private currentCipherIndex = 0;
private readonly showCiphersPerPage = 6; private readonly showCiphersPerPage = 6;
private readonly overlayListWindowMessageHandlers: OverlayListWindowMessageHandlers = { private readonly overlayListWindowMessageHandlers: AutofillInlineMenuListWindowMessageHandlers = {
initAutofillInlineMenuList: ({ message }) => this.initAutofillInlineMenuList(message), initAutofillInlineMenuList: ({ message }) => this.initAutofillInlineMenuList(message),
checkAutofillInlineMenuListFocused: () => this.checkInlineMenuListFocused(), checkAutofillInlineMenuListFocused: () => this.checkInlineMenuListFocused(),
updateOverlayListCiphers: ({ message }) => this.updateListItems(message.ciphers), updateAutofillInlineMenuListCiphers: ({ message }) => this.updateListItems(message.ciphers),
focusInlineMenuList: () => this.focusInlineMenuList(), focusInlineMenuList: () => this.focusInlineMenuList(),
}; };
@@ -53,7 +53,7 @@ class AutofillOverlayList extends AutofillInlineMenuPageElement {
authStatus, authStatus,
ciphers, ciphers,
portKey, portKey,
}: InitAutofillOverlayListMessage) { }: InitAutofillInlineMenuListMessage) {
const linkElement = await this.initAutofillInlineMenuPage( const linkElement = await this.initAutofillInlineMenuPage(
"list", "list",
styleSheetUrl, styleSheetUrl,
@@ -619,5 +619,3 @@ class AutofillOverlayList extends AutofillInlineMenuPageElement {
nextSibling?.focus(); nextSibling?.focus();
} }
} }
export default AutofillOverlayList;

View File

@@ -1,9 +1,9 @@
import { AutofillOverlayElement } from "../../../../enums/autofill-overlay.enum"; import { AutofillOverlayElement } from "../../../../enums/autofill-overlay.enum";
import AutofillOverlayList from "./autofill-overlay-list"; import { AutofillInlineMenuList } from "./autofill-inline-menu-list";
require("./list.scss"); require("./list.scss");
(function () { (function () {
globalThis.customElements.define(AutofillOverlayElement.List, AutofillOverlayList); globalThis.customElements.define(AutofillOverlayElement.List, AutofillInlineMenuList);
})(); })();

View File

@@ -7,6 +7,6 @@
<meta name="color-scheme" content="normal" /> <meta name="color-scheme" content="normal" />
</head> </head>
<body> <body>
<autofill-overlay-list></autofill-overlay-list> <autofill-inline-menu-list></autofill-inline-menu-list>
</body> </body>
</html> </html>

View File

@@ -2,11 +2,11 @@ import { EVENTS } from "@bitwarden/common/autofill/constants";
import { setElementStyles } from "../../../../utils"; import { setElementStyles } from "../../../../utils";
import { import {
InitOverlayElementMessage, InitInlineMenuElementMessage,
AutofillOverlayMenuContainerWindowMessageHandlers, AutofillInlineMenuMenuContainerWindowMessageHandlers,
} from "../../abstractions/autofill-inline-menu-container"; } from "../../abstractions/autofill-inline-menu-container";
export class AutofillOverlayMenuContainer { export class AutofillInlineMenuContainer {
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;
@@ -36,9 +36,9 @@ export class AutofillOverlayMenuContainer {
allowtransparency: "true", allowtransparency: "true",
tabIndex: "-1", tabIndex: "-1",
}; };
private windowMessageHandlers: AutofillOverlayMenuContainerWindowMessageHandlers = { private windowMessageHandlers: AutofillInlineMenuMenuContainerWindowMessageHandlers = {
initAutofillInlineMenuList: (message) => this.handleInitOverlayIframe(message), initAutofillInlineMenuList: (message) => this.handleInitInlineMenuIframe(message),
initAutofillInlineMenuButton: (message) => this.handleInitOverlayIframe(message), initAutofillInlineMenuButton: (message) => this.handleInitInlineMenuIframe(message),
}; };
constructor() { constructor() {
@@ -50,7 +50,7 @@ export class AutofillOverlayMenuContainer {
globalThis.addEventListener("message", this.handleWindowMessage); globalThis.addEventListener("message", this.handleWindowMessage);
} }
private handleInitOverlayIframe(message: InitOverlayElementMessage) { private handleInitInlineMenuIframe(message: InitInlineMenuElementMessage) {
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;
@@ -67,7 +67,7 @@ export class AutofillOverlayMenuContainer {
globalThis.document.body.appendChild(this.overlayPageIframe); globalThis.document.body.appendChild(this.overlayPageIframe);
} }
private setupPortMessageListener = (message: InitOverlayElementMessage) => { private setupPortMessageListener = (message: InitInlineMenuElementMessage) => {
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);

View File

@@ -0,0 +1,3 @@
import { AutofillInlineMenuContainer } from "./autofill-inline-menu-container";
(() => new AutofillInlineMenuContainer())();

View File

@@ -1,3 +0,0 @@
import { AutofillOverlayMenuContainer } from "./autofill-overlay-menu-container";
(() => new AutofillOverlayMenuContainer())();

View File

@@ -13,7 +13,7 @@ import AutofillForm from "../models/autofill-form";
import AutofillPageDetails from "../models/autofill-page-details"; import AutofillPageDetails from "../models/autofill-page-details";
import AutofillScript, { FillScript } from "../models/autofill-script"; import AutofillScript, { FillScript } from "../models/autofill-script";
import { InitAutofillInlineMenuButtonMessage } from "../overlay/inline-menu/abstractions/autofill-inline-menu-button"; import { InitAutofillInlineMenuButtonMessage } from "../overlay/inline-menu/abstractions/autofill-inline-menu-button";
import { InitAutofillOverlayListMessage } from "../overlay/inline-menu/abstractions/autofill-inline-menu-list"; import { InitAutofillInlineMenuListMessage } from "../overlay/inline-menu/abstractions/autofill-inline-menu-list";
import { GenerateFillScriptOptions, PageDetail } from "../services/abstractions/autofill.service"; import { GenerateFillScriptOptions, PageDetail } from "../services/abstractions/autofill.service";
function createAutofillFormMock(customFields = {}): AutofillForm { function createAutofillFormMock(customFields = {}): AutofillForm {
@@ -197,7 +197,7 @@ function createAutofillOverlayCipherDataMock(index: number, customFields = {}):
function createInitAutofillOverlayListMessageMock( function createInitAutofillOverlayListMessageMock(
customFields = {}, customFields = {},
): InitAutofillOverlayListMessage { ): InitAutofillInlineMenuListMessage {
return { return {
command: "initAutofillInlineMenuList", command: "initAutofillInlineMenuList",
translations: overlayPagesTranslations, translations: overlayPagesTranslations,

View File

@@ -177,9 +177,9 @@ const mainConfig = {
"overlay/button": "overlay/button":
"./src/autofill/overlay/inline-menu/pages/button/bootstrap-autofill-inline-menu-button.ts", "./src/autofill/overlay/inline-menu/pages/button/bootstrap-autofill-inline-menu-button.ts",
"overlay/list": "overlay/list":
"./src/autofill/overlay/inline-menu/pages/list/bootstrap-autofill-overlay-list.ts", "./src/autofill/overlay/inline-menu/pages/list/bootstrap-autofill-inline-menu-list.ts",
"overlay/menu": "overlay/menu":
"./src/autofill/overlay/inline-menu/pages/menu/bootstrap-autofill-overlay-menu-container.ts", "./src/autofill/overlay/inline-menu/pages/menu/bootstrap-autofill-inline-menu-container.ts",
"encrypt-worker": "../../libs/common/src/platform/services/cryptography/encrypt.worker.ts", "encrypt-worker": "../../libs/common/src/platform/services/cryptography/encrypt.worker.ts",
"content/lp-fileless-importer": "./src/tools/content/lp-fileless-importer.ts", "content/lp-fileless-importer": "./src/tools/content/lp-fileless-importer.ts",
"content/send-on-installed-message": "./src/vault/content/send-on-installed-message.ts", "content/send-on-installed-message": "./src/vault/content/send-on-installed-message.ts",