1
0
mirror of https://github.com/bitwarden/browser synced 2025-12-17 16:53:34 +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 12:14:33 -05:00
parent 85e71bc297
commit 4aebbc0a64
40 changed files with 966 additions and 237 deletions

View File

@@ -2,7 +2,7 @@ import { mock, MockProxy } from "jest-mock-extended";
import AutofillPageDetails from "../models/autofill-page-details";
import AutofillScript from "../models/autofill-script";
import { AutofillOverlayInlineMenuElements } from "../overlay/inline-menu/content/autofill-overlay-inline-menu-elements";
import { AutofillInlineMenuContentService } from "../overlay/inline-menu/content/autofill-inline-menu-content.service";
import AutofillOverlayContentService from "../services/autofill-overlay-content.service";
import { flushPromises, sendMockExtensionMessage } from "../spec/testing-utils";
@@ -10,7 +10,7 @@ import { AutofillExtensionMessage } from "./abstractions/autofill-init";
import AutofillInit from "./autofill-init";
describe("AutofillInit", () => {
let inlineMenuElements: MockProxy<AutofillOverlayInlineMenuElements>;
let inlineMenuElements: MockProxy<AutofillInlineMenuContentService>;
let autofillOverlayContentService: MockProxy<AutofillOverlayContentService>;
let autofillInit: AutofillInit;
const originalDocumentReadyState = document.readyState;
@@ -22,7 +22,7 @@ describe("AutofillInit", () => {
addListener: jest.fn(),
},
});
inlineMenuElements = mock<AutofillOverlayInlineMenuElements>();
inlineMenuElements = mock<AutofillInlineMenuContentService>();
autofillOverlayContentService = mock<AutofillOverlayContentService>();
autofillInit = new AutofillInit(autofillOverlayContentService, inlineMenuElements);
sendExtensionMessageSpy = jest
@@ -237,7 +237,10 @@ describe("AutofillInit", () => {
});
it("removes the overlay when filling the form", async () => {
const blurAndRemoveOverlaySpy = jest.spyOn(autofillInit as any, "blurAndRemoveOverlay");
const blurAndRemoveOverlaySpy = jest.spyOn(
autofillInit as any,
"blurAndRemoveInlineMenu",
);
sendMockExtensionMessage({
command: "fillForm",
fillScript,

View File

@@ -1,7 +1,7 @@
import { EVENTS } from "@bitwarden/common/autofill/constants";
import AutofillPageDetails from "../models/autofill-page-details";
import { AutofillOverlayInlineMenuElements } from "../overlay/inline-menu/abstractions/autofill-overlay-inline-menu-elements";
import { AutofillInlineMenuContentService } from "../overlay/inline-menu/abstractions/autofill-inline-menu-content.service";
import { AutofillOverlayContentService } from "../services/abstractions/autofill-overlay-content.service";
import CollectAutofillContentService from "../services/collect-autofill-content.service";
import DomElementVisibilityService from "../services/dom-element-visibility.service";
@@ -17,7 +17,7 @@ import {
class AutofillInit implements AutofillInitInterface {
private readonly sendExtensionMessage = sendExtensionMessage;
private readonly autofillOverlayContentService: AutofillOverlayContentService | undefined;
private readonly inlineMenuElements: AutofillOverlayInlineMenuElements | undefined;
private readonly autofillInlineMenuContentService: AutofillInlineMenuContentService | undefined;
private readonly domElementVisibilityService: DomElementVisibilityService;
private readonly collectAutofillContentService: CollectAutofillContentService;
private readonly insertAutofillContentService: InsertAutofillContentService;
@@ -37,7 +37,7 @@ class AutofillInit implements AutofillInitInterface {
*/
constructor(
autofillOverlayContentService?: AutofillOverlayContentService,
inlineMenuElements?: AutofillOverlayInlineMenuElements,
inlineMenuElements?: AutofillInlineMenuContentService,
) {
this.autofillOverlayContentService = autofillOverlayContentService;
if (this.autofillOverlayContentService) {
@@ -47,15 +47,17 @@ class AutofillInit implements AutofillInitInterface {
);
}
this.inlineMenuElements = inlineMenuElements;
if (this.inlineMenuElements) {
this.autofillInlineMenuContentService = inlineMenuElements;
if (this.autofillInlineMenuContentService) {
this.extensionMessageHandlers = Object.assign(
this.extensionMessageHandlers,
this.inlineMenuElements.extensionMessageHandlers,
this.autofillInlineMenuContentService.extensionMessageHandlers,
);
}
this.domElementVisibilityService = new DomElementVisibilityService(this.inlineMenuElements);
this.domElementVisibilityService = new DomElementVisibilityService(
this.autofillInlineMenuContentService,
);
this.collectAutofillContentService = new CollectAutofillContentService(
this.domElementVisibilityService,
this.autofillOverlayContentService,
@@ -135,7 +137,7 @@ class AutofillInit implements AutofillInitInterface {
return;
}
this.blurAndRemoveOverlay();
this.blurAndRemoveInlineMenu();
await this.sendExtensionMessage("updateIsFieldCurrentlyFilling", {
isFieldCurrentlyFilling: true,
});
@@ -155,8 +157,8 @@ class AutofillInit implements AutofillInitInterface {
* in cases where the background unlock or vault item reprompt popout
* is opened.
*/
private blurAndRemoveOverlay() {
this.autofillOverlayContentService?.blurMostRecentOverlayField(true);
private blurAndRemoveInlineMenu() {
this.autofillOverlayContentService?.blurMostRecentlyFocusedField(true);
}
/**
@@ -211,7 +213,7 @@ class AutofillInit implements AutofillInitInterface {
chrome.runtime.onMessage.removeListener(this.handleExtensionMessage);
this.collectAutofillContentService.destroy();
this.autofillOverlayContentService?.destroy();
this.inlineMenuElements?.destroy();
this.autofillInlineMenuContentService?.destroy();
}
}

View File

@@ -1,4 +1,4 @@
import { AutofillOverlayInlineMenuElements } from "../overlay/inline-menu/content/autofill-overlay-inline-menu-elements";
import { AutofillInlineMenuContentService } from "../overlay/inline-menu/content/autofill-inline-menu-content.service";
import AutofillOverlayContentService from "../services/autofill-overlay-content.service";
import { setupAutofillInitDisconnectAction } from "../utils";
@@ -7,9 +7,9 @@ import AutofillInit from "./autofill-init";
(function (windowContext) {
if (!windowContext.bitwardenAutofillInit) {
const autofillOverlayContentService = new AutofillOverlayContentService();
let inlineMenuElements: AutofillOverlayInlineMenuElements;
let inlineMenuElements: AutofillInlineMenuContentService;
if (globalThis.self === globalThis.top) {
inlineMenuElements = new AutofillOverlayInlineMenuElements();
inlineMenuElements = new AutofillInlineMenuContentService();
}
windowContext.bitwardenAutofillInit = new AutofillInit(
autofillOverlayContentService,

View File

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

View File

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

View File

@@ -9,7 +9,7 @@ export type InlineMenuExtensionMessageHandlers = {
checkIsAutofillInlineMenuListVisible: () => boolean;
};
export interface AutofillOverlayInlineMenuElements {
export interface AutofillInlineMenuContentService {
extensionMessageHandlers: InlineMenuExtensionMessageHandlers;
isElementInlineMenu(element: HTMLElement): boolean;
destroy(): void;

View File

@@ -23,7 +23,7 @@ type BackgroundPortMessageHandlers = {
updateAutofillInlineMenuColorScheme: () => void;
};
interface AutofillOverlayIframeService {
interface AutofillInlineMenuIframeService {
initMenuIframe(): void;
}
@@ -31,5 +31,5 @@ export {
AutofillOverlayIframeExtensionMessage,
AutofillOverlayIframeWindowMessageHandlers,
BackgroundPortMessageHandlers,
AutofillOverlayIframeService,
AutofillInlineMenuIframeService,
};

View File

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

View File

@@ -1,34 +0,0 @@
import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status";
type OverlayButtonMessage = { command: string; colorScheme?: string };
type UpdateAuthStatusMessage = OverlayButtonMessage & { authStatus: AuthenticationStatus };
type InitAutofillOverlayButtonMessage = UpdateAuthStatusMessage & {
styleSheetUrl: string;
translations: Record<string, string>;
portKey: string;
};
type OverlayButtonWindowMessageHandlers = {
[key: string]: CallableFunction;
initAutofillInlineMenuButton: ({
message,
}: {
message: InitAutofillOverlayButtonMessage;
}) => void;
checkAutofillInlineMenuButtonFocused: () => void;
updateAutofillOverlayButtonAuthStatus: ({
message,
}: {
message: UpdateAuthStatusMessage;
}) => void;
updateAutofillInlineMenuColorScheme: ({ message }: { message: OverlayButtonMessage }) => void;
};
export {
UpdateAuthStatusMessage,
OverlayButtonMessage,
InitAutofillOverlayButtonMessage,
OverlayButtonWindowMessageHandlers,
};

View File

@@ -1,15 +0,0 @@
import { OverlayButtonWindowMessageHandlers } from "./autofill-overlay-button";
import { OverlayListWindowMessageHandlers } from "./autofill-overlay-list";
type AutofillOverlayPageElementWindowMessageHandlers =
| OverlayButtonWindowMessageHandlers
| OverlayListWindowMessageHandlers;
type AutofillOverlayPageElementWindowMessage = {
[key: string]: any;
command: string;
overlayCipherId?: string;
height?: number;
};
export { AutofillOverlayPageElementWindowMessageHandlers, AutofillOverlayPageElementWindowMessage };

View File

@@ -7,12 +7,12 @@ import {
} from "../../../utils";
import {
InlineMenuExtensionMessageHandlers,
AutofillOverlayInlineMenuElements as InlineMenuElementsInterface,
} from "../abstractions/autofill-overlay-inline-menu-elements";
import AutofillOverlayButtonIframe from "../iframe-content/autofill-overlay-button-iframe";
import AutofillOverlayListIframe from "../iframe-content/autofill-overlay-list-iframe";
AutofillInlineMenuContentService as AutofillInlineMenuContentServiceInterface,
} from "../abstractions/autofill-inline-menu-content.service";
import { AutofillInlineMenuButtonIframe } from "../iframe-content/autofill-inline-menu-button-iframe";
import AutofillInlineMenuListIframe from "../iframe-content/autofill-inline-menu-list-iframe";
export class AutofillOverlayInlineMenuElements implements InlineMenuElementsInterface {
export class AutofillInlineMenuContentService implements AutofillInlineMenuContentServiceInterface {
private readonly sendExtensionMessage = sendExtensionMessage;
private readonly generateRandomCustomElementName = generateRandomCustomElementName;
private readonly setElementStyles = setElementStyles;
@@ -193,7 +193,7 @@ export class AutofillOverlayInlineMenuElements implements InlineMenuElementsInte
if (this.isFirefoxBrowser) {
this.buttonElement = globalThis.document.createElement("div");
new AutofillOverlayButtonIframe(this.buttonElement);
new AutofillInlineMenuButtonIframe(this.buttonElement);
return;
}
@@ -204,7 +204,7 @@ export class AutofillOverlayInlineMenuElements implements InlineMenuElementsInte
class extends HTMLElement {
constructor() {
super();
new AutofillOverlayButtonIframe(this);
new AutofillInlineMenuButtonIframe(this);
}
},
);
@@ -222,7 +222,7 @@ export class AutofillOverlayInlineMenuElements implements InlineMenuElementsInte
if (this.isFirefoxBrowser) {
this.listElement = globalThis.document.createElement("div");
new AutofillOverlayListIframe(this.listElement);
new AutofillInlineMenuListIframe(this.listElement);
return;
}
@@ -233,7 +233,7 @@ export class AutofillOverlayInlineMenuElements implements InlineMenuElementsInte
class extends HTMLElement {
constructor() {
super();
new AutofillOverlayListIframe(this);
new AutofillInlineMenuListIframe(this);
}
},
);
@@ -415,7 +415,7 @@ export class AutofillOverlayInlineMenuElements implements InlineMenuElementsInte
if (this.mutationObserverIterations > 100) {
clearTimeout(this.mutationObserverIterationsResetTimeout);
this.mutationObserverIterations = 0;
void this.sendExtensionMessage("blurMostRecentOverlayField");
void this.sendExtensionMessage("blurMostRecentlyFocusedField");
this.removeInlineMenu();
return true;

View File

@@ -0,0 +1,11 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`AutofillOverlayIframeService 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"
/>
`;

View File

@@ -0,0 +1,27 @@
import { AutofillInlineMenuButtonIframe } from "./autofill-inline-menu-button-iframe";
describe("AutofillInlineMenuButtonIframe", () => {
window.customElements.define(
"autofill-inline-menu-button-iframe",
class extends HTMLElement {
constructor() {
super();
new AutofillInlineMenuButtonIframe(this);
}
},
);
afterAll(() => {
jest.clearAllMocks();
});
it("creates a custom element that is an instance of the AutofillIframeElement parent class", () => {
document.body.innerHTML =
"<autofill-inline-menu-button-iframe></autofill-inline-menu-button-iframe>";
const iframe = document.querySelector("autofill-inline-menu-button-iframe");
expect(iframe).toBeInstanceOf(HTMLElement);
expect(iframe.shadowRoot).toBeDefined();
});
});

View File

@@ -1,8 +1,8 @@
import { AutofillOverlayPort } from "../../../enums/autofill-overlay.enum";
import AutofillOverlayIframeElement from "./autofill-overlay-iframe-element";
import { AutofillInlineMenuIframeElement } from "./autofill-inline-menu-iframe-element";
class AutofillOverlayButtonIframe extends AutofillOverlayIframeElement {
export class AutofillInlineMenuButtonIframe extends AutofillInlineMenuIframeElement {
constructor(element: HTMLElement) {
super(
element,
@@ -16,5 +16,3 @@ class AutofillOverlayButtonIframe extends AutofillOverlayIframeElement {
);
}
}
export default AutofillOverlayButtonIframe;

View File

@@ -1,17 +1,17 @@
import { AutofillOverlayPort } from "../../../enums/autofill-overlay.enum";
import AutofillOverlayIframeElement from "./autofill-overlay-iframe-element";
import AutofillOverlayIframeService from "./autofill-overlay-iframe.service";
import { AutofillInlineMenuIframeElement } from "./autofill-inline-menu-iframe-element";
import { AutofillInlineMenuIframeService } from "./autofill-inline-menu-iframe.service";
jest.mock("./autofill-overlay-iframe.service");
jest.mock("./autofill-inline-menu-iframe.service");
describe("AutofillOverlayIframeElement", () => {
describe("AutofillInlineMenuIframeElement", () => {
window.customElements.define(
"autofill-overlay-iframe",
"autofill-inline-menu-iframe",
class extends HTMLElement {
constructor() {
super();
new AutofillOverlayIframeElement(
new AutofillInlineMenuIframeElement(
this,
AutofillOverlayPort.Button,
{ background: "transparent", border: "none" },
@@ -26,22 +26,22 @@ describe("AutofillOverlayIframeElement", () => {
});
it("creates a custom element that is an instance of the HTMLElement parent class", () => {
document.body.innerHTML = "<autofill-overlay-iframe></autofill-overlay-iframe>";
document.body.innerHTML = "<autofill-inline-menu-iframe></autofill-inline-menu-iframe>";
const iframe = document.querySelector("autofill-overlay-iframe");
const iframe = document.querySelector("autofill-inline-menu-iframe");
expect(iframe).toBeInstanceOf(HTMLElement);
});
it("attaches a closed shadow DOM", () => {
document.body.innerHTML = "<autofill-overlay-iframe></autofill-overlay-iframe>";
document.body.innerHTML = "<autofill-inline-menu-iframe></autofill-inline-menu-iframe>";
const iframe = document.querySelector("autofill-overlay-iframe");
const iframe = document.querySelector("autofill-inline-menu-iframe");
expect(iframe.shadowRoot).toBeNull();
});
it("instantiates the autofill overlay iframe service for each attached custom element", () => {
expect(AutofillOverlayIframeService).toHaveBeenCalledTimes(2);
expect(AutofillInlineMenuIframeService).toHaveBeenCalledTimes(2);
});
});

View File

@@ -1,6 +1,6 @@
import AutofillOverlayIframeService from "./autofill-overlay-iframe.service";
import { AutofillInlineMenuIframeService } from "./autofill-inline-menu-iframe.service";
class AutofillOverlayIframeElement {
export class AutofillInlineMenuIframeElement {
constructor(
element: HTMLElement,
portName: string,
@@ -9,7 +9,7 @@ class AutofillOverlayIframeElement {
ariaAlert?: string,
) {
const shadow: ShadowRoot = element.attachShadow({ mode: "closed" });
const autofillOverlayIframeService = new AutofillOverlayIframeService(
const autofillOverlayIframeService = new AutofillInlineMenuIframeService(
shadow,
portName,
initStyles,
@@ -19,5 +19,3 @@ class AutofillOverlayIframeElement {
autofillOverlayIframeService.initMenuIframe();
}
}
export default AutofillOverlayIframeElement;

View File

@@ -11,10 +11,10 @@ import {
triggerPortOnDisconnectEvent,
} from "../../../spec/testing-utils";
import AutofillOverlayIframeService from "./autofill-overlay-iframe.service";
import { AutofillInlineMenuIframeService } from "./autofill-inline-menu-iframe.service";
describe("AutofillOverlayIframeService", () => {
let autofillOverlayIframeService: AutofillOverlayIframeService;
let autofillOverlayIframeService: AutofillInlineMenuIframeService;
let portSpy: chrome.runtime.Port;
let shadowAppendSpy: jest.SpyInstance;
let handlePortDisconnectSpy: jest.SpyInstance;
@@ -23,7 +23,7 @@ describe("AutofillOverlayIframeService", () => {
beforeEach(() => {
const shadow = document.createElement("div").attachShadow({ mode: "open" });
autofillOverlayIframeService = new AutofillOverlayIframeService(
autofillOverlayIframeService = new AutofillInlineMenuIframeService(
shadow,
AutofillOverlayPort.Button,
{ height: "0px" },

View File

@@ -4,11 +4,11 @@ import { ThemeType } from "@bitwarden/common/platform/enums";
import { sendExtensionMessage, setElementStyles } from "../../../utils";
import {
BackgroundPortMessageHandlers,
AutofillOverlayIframeService as AutofillOverlayIframeServiceInterface,
AutofillInlineMenuIframeService as AutofillOverlayIframeServiceInterface,
AutofillOverlayIframeExtensionMessage,
} from "../abstractions/autofill-overlay-iframe.service";
} from "../abstractions/autofill-inline-menu-iframe.service";
class AutofillOverlayIframeService implements AutofillOverlayIframeServiceInterface {
export class AutofillInlineMenuIframeService implements AutofillOverlayIframeServiceInterface {
private sendExtensionMessage = sendExtensionMessage;
private port: chrome.runtime.Port | null = null;
private portKey: string;
@@ -405,5 +405,3 @@ class AutofillOverlayIframeService implements AutofillOverlayIframeServiceInterf
return false;
}
}
export default AutofillOverlayIframeService;

View File

@@ -1,4 +1,4 @@
import AutofillOverlayListIframe from "./autofill-overlay-list-iframe";
import AutofillInlineMenuListIframe from "./autofill-inline-menu-list-iframe";
describe("AutofillOverlayListIframe", () => {
window.customElements.define(
@@ -6,7 +6,7 @@ describe("AutofillOverlayListIframe", () => {
class extends HTMLElement {
constructor() {
super();
new AutofillOverlayListIframe(this);
new AutofillInlineMenuListIframe(this);
}
},
);

View File

@@ -1,8 +1,8 @@
import { AutofillOverlayPort } from "../../../enums/autofill-overlay.enum";
import AutofillOverlayIframeElement from "./autofill-overlay-iframe-element";
import { AutofillInlineMenuIframeElement } from "./autofill-inline-menu-iframe-element";
class AutofillOverlayListIframe extends AutofillOverlayIframeElement {
class AutofillInlineMenuListIframe extends AutofillInlineMenuIframeElement {
constructor(element: HTMLElement) {
super(
element,
@@ -22,4 +22,4 @@ class AutofillOverlayListIframe extends AutofillOverlayIframeElement {
}
}
export default AutofillOverlayListIframe;
export default AutofillInlineMenuListIframe;

View File

@@ -1,26 +0,0 @@
import AutofillOverlayButtonIframe from "./autofill-overlay-button-iframe";
describe("AutofillOverlayButtonIframe", () => {
window.customElements.define(
"autofill-overlay-button-iframe",
class extends HTMLElement {
constructor() {
super();
new AutofillOverlayButtonIframe(this);
}
},
);
afterAll(() => {
jest.clearAllMocks();
});
it("creates a custom element that is an instance of the AutofillIframeElement parent class", () => {
document.body.innerHTML = "<autofill-overlay-button-iframe></autofill-overlay-button-iframe>";
const iframe = document.querySelector("autofill-overlay-button-iframe");
expect(iframe).toBeInstanceOf(HTMLElement);
expect(iframe.shadowRoot).toBeDefined();
});
});

View File

@@ -0,0 +1,165 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`AutofillInlineMenuButton initAutofillInlineMenuButton creates the button element with the locked icon when the user's auth status is not Unlocked 1`] = `
<button
aria-label="toggleBitwardenVaultOverlay"
class="inline-menu-button"
tabindex="-1"
type="button"
>
<svg
aria-hidden="true"
class="inline-menu-button-svg-icon logo-locked-icon"
fill="none"
height="16"
viewBox="0 0 16 16"
width="16"
xmlns="http://www.w3.org/2000/svg"
>
<g
clip-path="url(#a)"
>
<path
d="M12.66.175A.566.566 0 0 0 12.25 0H1.75a.559.559 0 0 0-.409.175.561.561 0 0 0-.175.41v7c.002.532.105 1.06.305 1.554.189.488.444.948.756 1.368.322.42.682.81 1.076 1.163.365.335.75.649 1.152.939.35.248.718.483 1.103.706.385.222.656.372.815.45.16.08.29.141.386.182A.53.53 0 0 0 7 14a.509.509 0 0 0 .238-.055c.098-.043.225-.104.387-.182.162-.079.438-.23.816-.45.378-.222.75-.459 1.102-.707.403-.29.788-.604 1.154-.939a8.435 8.435 0 0 0 1.076-1.163c.312-.42.567-.88.757-1.367a4.19 4.19 0 0 0 .304-1.555v-7a.55.55 0 0 0-.174-.407Z"
fill="#175DDC"
/>
<path
d="M7 12.365s4.306-2.18 4.306-4.717V1.5H7v10.865Z"
fill="#fff"
/>
<circle
cx="12.889"
cy="12.889"
fill="#F8F9FA"
r="4.889"
/>
<path
d="M11.26 11.717h2.37v-.848c0-.313-.116-.58-.348-.8a1.17 1.17 0 0 0-.838-.332c-.327 0-.606.11-.838.332a1.066 1.066 0 0 0-.347.8v.848Zm3.851.424v2.546a.4.4 0 0 1-.13.3.44.44 0 0 1-.314.124h-4.445a.44.44 0 0 1-.315-.124.4.4 0 0 1-.13-.3V12.14a.4.4 0 0 1 .13-.3.44.44 0 0 1 .315-.124h.148v-.848c0-.542.204-1.008.612-1.397a2.044 2.044 0 0 1 1.462-.583c.568 0 1.056.194 1.463.583.408.39.611.855.611 1.397v.848h.149a.44.44 0 0 1 .315.124.4.4 0 0 1 .13.3Z"
fill="#555"
/>
</g>
<defs>
<clippath
id="a"
>
<rect
fill="#fff"
height="16"
rx="2"
width="16"
/>
</clippath>
</defs>
</svg>
</button>
`;
exports[`AutofillInlineMenuButton initAutofillInlineMenuButton creates the button element with the normal icon when the user's auth status is Unlocked 1`] = `
<button
aria-label="toggleBitwardenVaultOverlay"
class="inline-menu-button"
tabindex="-1"
type="button"
>
<svg
aria-hidden="true"
class="inline-menu-button-svg-icon logo-icon"
fill="none"
height="14"
viewBox="0 0 14 14"
width="14"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M12.66.175A.566.566 0 0 0 12.25 0H1.75a.559.559 0 0 0-.409.175.561.561 0 0 0-.175.41v7c.002.532.105 1.06.305 1.554.189.488.444.948.756 1.368.322.42.682.81 1.076 1.163.365.335.75.649 1.152.939.35.248.718.483 1.103.706.385.222.656.372.815.45.16.08.29.141.386.182A.53.53 0 0 0 7 14a.509.509 0 0 0 .238-.055c.098-.043.225-.104.387-.182.162-.079.438-.23.816-.45.378-.222.75-.459 1.102-.707.403-.29.788-.604 1.154-.939a8.435 8.435 0 0 0 1.076-1.163c.312-.42.567-.88.757-1.367a4.19 4.19 0 0 0 .304-1.555v-7a.55.55 0 0 0-.174-.407Z"
fill="#175DDC"
/>
<path
d="M7 12.365s4.306-2.18 4.306-4.717V1.5H7v10.865Z"
fill="#fff"
/>
</svg>
</button>
`;
exports[`AutofillOverlayButton initAutofillInlineMenuButton creates the button element with the locked icon when the user's auth status is not Unlocked 1`] = `
<button
aria-label="toggleBitwardenVaultOverlay"
class="inline-menu-button"
tabindex="-1"
type="button"
>
<svg
aria-hidden="true"
class="inline-menu-button-svg-icon logo-locked-icon"
fill="none"
height="16"
viewBox="0 0 16 16"
width="16"
xmlns="http://www.w3.org/2000/svg"
>
<g
clip-path="url(#a)"
>
<path
d="M12.66.175A.566.566 0 0 0 12.25 0H1.75a.559.559 0 0 0-.409.175.561.561 0 0 0-.175.41v7c.002.532.105 1.06.305 1.554.189.488.444.948.756 1.368.322.42.682.81 1.076 1.163.365.335.75.649 1.152.939.35.248.718.483 1.103.706.385.222.656.372.815.45.16.08.29.141.386.182A.53.53 0 0 0 7 14a.509.509 0 0 0 .238-.055c.098-.043.225-.104.387-.182.162-.079.438-.23.816-.45.378-.222.75-.459 1.102-.707.403-.29.788-.604 1.154-.939a8.435 8.435 0 0 0 1.076-1.163c.312-.42.567-.88.757-1.367a4.19 4.19 0 0 0 .304-1.555v-7a.55.55 0 0 0-.174-.407Z"
fill="#175DDC"
/>
<path
d="M7 12.365s4.306-2.18 4.306-4.717V1.5H7v10.865Z"
fill="#fff"
/>
<circle
cx="12.889"
cy="12.889"
fill="#F8F9FA"
r="4.889"
/>
<path
d="M11.26 11.717h2.37v-.848c0-.313-.116-.58-.348-.8a1.17 1.17 0 0 0-.838-.332c-.327 0-.606.11-.838.332a1.066 1.066 0 0 0-.347.8v.848Zm3.851.424v2.546a.4.4 0 0 1-.13.3.44.44 0 0 1-.314.124h-4.445a.44.44 0 0 1-.315-.124.4.4 0 0 1-.13-.3V12.14a.4.4 0 0 1 .13-.3.44.44 0 0 1 .315-.124h.148v-.848c0-.542.204-1.008.612-1.397a2.044 2.044 0 0 1 1.462-.583c.568 0 1.056.194 1.463.583.408.39.611.855.611 1.397v.848h.149a.44.44 0 0 1 .315.124.4.4 0 0 1 .13.3Z"
fill="#555"
/>
</g>
<defs>
<clippath
id="a"
>
<rect
fill="#fff"
height="16"
rx="2"
width="16"
/>
</clippath>
</defs>
</svg>
</button>
`;
exports[`AutofillOverlayButton initAutofillInlineMenuButton creates the button element with the normal icon when the user's auth status is Unlocked 1`] = `
<button
aria-label="toggleBitwardenVaultOverlay"
class="inline-menu-button"
tabindex="-1"
type="button"
>
<svg
aria-hidden="true"
class="inline-menu-button-svg-icon logo-icon"
fill="none"
height="14"
viewBox="0 0 14 14"
width="14"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M12.66.175A.566.566 0 0 0 12.25 0H1.75a.559.559 0 0 0-.409.175.561.561 0 0 0-.175.41v7c.002.532.105 1.06.305 1.554.189.488.444.948.756 1.368.322.42.682.81 1.076 1.163.365.335.75.649 1.152.939.35.248.718.483 1.103.706.385.222.656.372.815.45.16.08.29.141.386.182A.53.53 0 0 0 7 14a.509.509 0 0 0 .238-.055c.098-.043.225-.104.387-.182.162-.079.438-.23.816-.45.378-.222.75-.459 1.102-.707.403-.29.788-.604 1.154-.939a8.435 8.435 0 0 0 1.076-1.163c.312-.42.567-.88.757-1.367a4.19 4.19 0 0 0 .304-1.555v-7a.55.55 0 0 0-.174-.407Z"
fill="#175DDC"
/>
<path
d="M7 12.365s4.306-2.18 4.306-4.717V1.5H7v10.865Z"
fill="#fff"
/>
</svg>
</button>
`;

View File

@@ -3,13 +3,13 @@
exports[`AutofillOverlayButton initAutofillOverlayButton creates the button element with the locked icon when the user's auth status is not Unlocked 1`] = `
<button
aria-label="toggleBitwardenVaultOverlay"
class="overlay-button"
class="inline-menu-button"
tabindex="-1"
type="button"
>
<svg
aria-hidden="true"
class="overlay-button-svg-icon logo-locked-icon"
class="inline-menu-button-svg-icon logo-locked-icon"
fill="none"
height="16"
viewBox="0 0 16 16"
@@ -57,13 +57,13 @@ exports[`AutofillOverlayButton initAutofillOverlayButton creates the button elem
exports[`AutofillOverlayButton initAutofillOverlayButton creates the button element with the normal icon when the user's auth status is Unlocked 1`] = `
<button
aria-label="toggleBitwardenVaultOverlay"
class="overlay-button"
class="inline-menu-button"
tabindex="-1"
type="button"
>
<svg
aria-hidden="true"
class="overlay-button-svg-icon logo-icon"
class="inline-menu-button-svg-icon logo-icon"
fill="none"
height="14"
viewBox="0 0 14 14"

View File

@@ -1,20 +1,20 @@
import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status";
import { createInitAutofillOverlayButtonMessageMock } from "../../../../spec/autofill-mocks";
import { createInitAutofillInlineMenuButtonMessageMock } from "../../../../spec/autofill-mocks";
import { flushPromises, postWindowMessage } from "../../../../spec/testing-utils";
import AutofillOverlayButton from "./autofill-overlay-button";
import AutofillInlineMenuButton from "./autofill-inline-menu-button";
describe("AutofillOverlayButton", () => {
globalThis.customElements.define("autofill-overlay-button", AutofillOverlayButton);
describe("AutofillInlineMenuButton", () => {
globalThis.customElements.define("autofill-inline-menu-button", AutofillInlineMenuButton);
let autofillOverlayButton: AutofillOverlayButton;
const portKey: string = "overlayButtonPortKey";
let autofillInlineMenuButton: AutofillInlineMenuButton;
const portKey: string = "inlineMenuButtonPortKey";
beforeEach(() => {
document.body.innerHTML = `<autofill-overlay-button></autofill-overlay-button>`;
autofillOverlayButton = document.querySelector("autofill-overlay-button");
autofillOverlayButton["messageOrigin"] = "https://localhost/";
document.body.innerHTML = `<autofill-inline-menu-button></autofill-inline-menu-button>`;
autofillInlineMenuButton = document.querySelector("autofill-inline-menu-button");
autofillInlineMenuButton["messageOrigin"] = "https://localhost/";
jest.spyOn(globalThis.document, "createElement");
jest.spyOn(globalThis.parent, "postMessage");
});
@@ -26,34 +26,34 @@ describe("AutofillOverlayButton", () => {
describe("initAutofillInlineMenuButton", () => {
it("creates the button element with the locked icon when the user's auth status is not Unlocked", async () => {
postWindowMessage(
createInitAutofillOverlayButtonMessageMock({
createInitAutofillInlineMenuButtonMessageMock({
authStatus: AuthenticationStatus.Locked,
portKey,
}),
);
await flushPromises();
expect(autofillOverlayButton["buttonElement"]).toMatchSnapshot();
expect(autofillOverlayButton["buttonElement"].querySelector("svg")).toBe(
autofillOverlayButton["logoLockedIconElement"],
expect(autofillInlineMenuButton["buttonElement"]).toMatchSnapshot();
expect(autofillInlineMenuButton["buttonElement"].querySelector("svg")).toBe(
autofillInlineMenuButton["logoLockedIconElement"],
);
});
it("creates the button element with the normal icon when the user's auth status is Unlocked ", async () => {
postWindowMessage(createInitAutofillOverlayButtonMessageMock({ portKey }));
postWindowMessage(createInitAutofillInlineMenuButtonMessageMock({ portKey }));
await flushPromises();
expect(autofillOverlayButton["buttonElement"]).toMatchSnapshot();
expect(autofillOverlayButton["buttonElement"].querySelector("svg")).toBe(
autofillOverlayButton["logoIconElement"],
expect(autofillInlineMenuButton["buttonElement"]).toMatchSnapshot();
expect(autofillInlineMenuButton["buttonElement"].querySelector("svg")).toBe(
autofillInlineMenuButton["logoIconElement"],
);
});
it("posts a message to the background indicating that the icon was clicked", async () => {
postWindowMessage(createInitAutofillOverlayButtonMessageMock({ portKey }));
postWindowMessage(createInitAutofillInlineMenuButtonMessageMock({ portKey }));
await flushPromises();
autofillOverlayButton["buttonElement"].click();
autofillInlineMenuButton["buttonElement"].click();
expect(globalThis.parent.postMessage).toHaveBeenCalledWith(
{ command: "autofillInlineMenuButtonClicked", portKey },
@@ -64,10 +64,10 @@ describe("AutofillOverlayButton", () => {
describe("global event listeners", () => {
beforeEach(() => {
postWindowMessage(createInitAutofillOverlayButtonMessageMock({ portKey }));
postWindowMessage(createInitAutofillInlineMenuButtonMessageMock({ portKey }));
});
it("does not post a message to close the autofill overlay if the element is focused during the focus check", async () => {
it("does not post a message to close the autofill inline menu if the element is focused during the focus check", async () => {
jest.spyOn(globalThis.document, "hasFocus").mockReturnValue(true);
postWindowMessage({ command: "checkAutofillInlineMenuButtonFocused" });
@@ -78,7 +78,7 @@ describe("AutofillOverlayButton", () => {
});
});
it("posts a message to close the autofill overlay if the element is not focused during the focus check", async () => {
it("posts a message to close the autofill inline menu if the element is not focused during the focus check", async () => {
jest.spyOn(globalThis.document, "hasFocus").mockReturnValue(false);
postWindowMessage({ command: "checkAutofillInlineMenuButtonFocused" });
@@ -91,15 +91,15 @@ describe("AutofillOverlayButton", () => {
});
it("updates the user's auth status", async () => {
autofillOverlayButton["authStatus"] = AuthenticationStatus.Locked;
autofillInlineMenuButton["authStatus"] = AuthenticationStatus.Locked;
postWindowMessage({
command: "updateAutofillOverlayButtonAuthStatus",
command: "updateAutofillInlineMenuButtonAuthStatus",
authStatus: AuthenticationStatus.Unlocked,
});
await flushPromises();
expect(autofillOverlayButton["authStatus"]).toBe(AuthenticationStatus.Unlocked);
expect(autofillInlineMenuButton["authStatus"]).toBe(AuthenticationStatus.Unlocked);
});
it("updates the page color scheme meta tag", async () => {

View File

@@ -6,37 +6,38 @@ import { EVENTS } from "@bitwarden/common/autofill/constants";
import { buildSvgDomElement } from "../../../../utils";
import { logoIcon, logoLockedIcon } from "../../../../utils/svg-icons";
import {
InitAutofillOverlayButtonMessage,
OverlayButtonMessage,
OverlayButtonWindowMessageHandlers,
} from "../../abstractions/autofill-overlay-button";
import AutofillOverlayPageElement from "../shared/autofill-overlay-page-element";
InitAutofillInlineMenuButtonMessage,
AutofillInlineMenuButtonMessage,
AutofillInlineMenuButtonWindowMessageHandlers,
} from "../../abstractions/autofill-inline-menu-button";
import { AutofillInlineMenuPageElement } from "../shared/autofill-inline-menu-page-element";
class AutofillOverlayButton extends AutofillOverlayPageElement {
class AutofillInlineMenuButton extends AutofillInlineMenuPageElement {
private authStatus: AuthenticationStatus = AuthenticationStatus.LoggedOut;
private readonly buttonElement: HTMLButtonElement;
private readonly logoIconElement: HTMLElement;
private readonly logoLockedIconElement: HTMLElement;
private readonly overlayButtonWindowMessageHandlers: OverlayButtonWindowMessageHandlers = {
initAutofillInlineMenuButton: ({ message }) => this.initAutofillInlineMenuButton(message),
checkAutofillInlineMenuButtonFocused: () => this.checkButtonFocused(),
updateAutofillOverlayButtonAuthStatus: ({ message }) =>
this.updateAuthStatus(message.authStatus),
updateAutofillInlineMenuColorScheme: ({ message }) => this.updatePageColorScheme(message),
};
private readonly inlineMenuButtonWindowMessageHandlers: AutofillInlineMenuButtonWindowMessageHandlers =
{
initAutofillInlineMenuButton: ({ message }) => this.initAutofillInlineMenuButton(message),
checkAutofillInlineMenuButtonFocused: () => this.checkButtonFocused(),
updateAutofillInlineMenuButtonAuthStatus: ({ message }) =>
this.updateAuthStatus(message.authStatus),
updateAutofillInlineMenuColorScheme: ({ message }) => this.updatePageColorScheme(message),
};
constructor() {
super();
this.buttonElement = globalThis.document.createElement("button");
this.setupGlobalListeners(this.overlayButtonWindowMessageHandlers);
this.setupGlobalListeners(this.inlineMenuButtonWindowMessageHandlers);
this.logoIconElement = buildSvgDomElement(logoIcon);
this.logoIconElement.classList.add("overlay-button-svg-icon", "logo-icon");
this.logoIconElement.classList.add("inline-menu-button-svg-icon", "logo-icon");
this.logoLockedIconElement = buildSvgDomElement(logoLockedIcon);
this.logoLockedIconElement.classList.add("overlay-button-svg-icon", "logo-locked-icon");
this.logoLockedIconElement.classList.add("inline-menu-button-svg-icon", "logo-locked-icon");
}
/**
@@ -53,11 +54,16 @@ class AutofillOverlayButton extends AutofillOverlayPageElement {
styleSheetUrl,
translations,
portKey,
}: InitAutofillOverlayButtonMessage) {
const linkElement = await this.initOverlayPage("button", styleSheetUrl, translations, portKey);
}: InitAutofillInlineMenuButtonMessage) {
const linkElement = await this.initAutofillInlineMenuPage(
"button",
styleSheetUrl,
translations,
portKey,
);
this.buttonElement.tabIndex = -1;
this.buttonElement.type = "button";
this.buttonElement.classList.add("overlay-button");
this.buttonElement.classList.add("inline-menu-button");
this.buttonElement.setAttribute(
"aria-label",
this.getTranslation("toggleBitwardenVaultOverlay"),
@@ -93,7 +99,7 @@ class AutofillOverlayButton extends AutofillOverlayPageElement {
*
* @param colorScheme - The color scheme of the iframe's parent page
*/
private updatePageColorScheme({ colorScheme }: OverlayButtonMessage) {
private updatePageColorScheme({ colorScheme }: AutofillInlineMenuButtonMessage) {
const colorSchemeMetaTag = globalThis.document.querySelector("meta[name='color-scheme']");
colorSchemeMetaTag?.setAttribute("content", colorScheme);
}
@@ -119,4 +125,4 @@ class AutofillOverlayButton extends AutofillOverlayPageElement {
}
}
export default AutofillOverlayButton;
export default AutofillInlineMenuButton;

View File

@@ -1,9 +1,9 @@
import { AutofillOverlayElement } from "../../../../enums/autofill-overlay.enum";
import AutofillOverlayButton from "./autofill-overlay-button";
import AutofillInlineMenuButton from "./autofill-inline-menu-button";
require("./button.scss");
(function () {
globalThis.customElements.define(AutofillOverlayElement.Button, AutofillOverlayButton);
globalThis.customElements.define(AutofillOverlayElement.Button, AutofillInlineMenuButton);
})();

View File

@@ -1,12 +1,12 @@
<!doctype html>
<html lang="en">
<head>
<title>Bitwarden overlay button</title>
<title>Bitwarden inline menu button</title>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width,initial-scale=1" />
<meta name="color-scheme" content="normal" />
</head>
<body>
<autofill-overlay-button></autofill-overlay-button>
<autofill-inline-menu-button></autofill-inline-menu-button>
</body>
</html>

View File

@@ -14,12 +14,12 @@ body {
background: transparent;
overflow: hidden;
}
autofill-overlay-button {
autofill-inline-menu-button {
width: 100%;
height: auto;
}
.overlay-button {
.inline-menu-button {
display: block;
width: 100%;
padding: 0;
@@ -28,7 +28,7 @@ autofill-overlay-button {
background: transparent;
cursor: pointer;
.overlay-button-svg-icon {
.inline-menu-button-svg-icon {
display: block;
width: 100%;
height: auto;

View File

@@ -1,5 +1,540 @@
// 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>
`;
exports[`AutofillOverlayList initAutofillOverlayList the list of ciphers for an authenticated user creates the view for a list of ciphers 1`] = `
<div
class="overlay-list-container theme_light"

View File

@@ -9,10 +9,10 @@ import { globeIcon, lockIcon, plusIcon, viewCipherIcon } from "../../../../utils
import {
InitAutofillOverlayListMessage,
OverlayListWindowMessageHandlers,
} from "../../abstractions/autofill-overlay-list";
import AutofillOverlayPageElement from "../shared/autofill-overlay-page-element";
} from "../../abstractions/autofill-inline-menu-list";
import { AutofillInlineMenuPageElement } from "../shared/autofill-inline-menu-page-element";
class AutofillOverlayList extends AutofillOverlayPageElement {
class AutofillOverlayList extends AutofillInlineMenuPageElement {
private overlayListContainer: HTMLDivElement;
private resizeObserver: ResizeObserver;
private eventHandlersMemo: { [key: string]: EventListener } = {};
@@ -54,7 +54,12 @@ class AutofillOverlayList extends AutofillOverlayPageElement {
ciphers,
portKey,
}: InitAutofillOverlayListMessage) {
const linkElement = await this.initOverlayPage("list", styleSheetUrl, translations, portKey);
const linkElement = await this.initAutofillInlineMenuPage(
"list",
styleSheetUrl,
translations,
portKey,
);
const themeClass = `theme_${theme}`;
globalThis.document.documentElement.classList.add(themeClass);

View File

@@ -4,7 +4,7 @@ import { setElementStyles } from "../../../../utils";
import {
InitOverlayElementMessage,
AutofillOverlayMenuContainerWindowMessageHandlers,
} from "../../abstractions/autofill-overlay-menu-container";
} from "../../abstractions/autofill-inline-menu-container";
export class AutofillOverlayMenuContainer {
private extensionOriginsSet: Set<string>;

View File

@@ -2,16 +2,16 @@ import { EVENTS } from "@bitwarden/common/autofill/constants";
import { RedirectFocusDirection } from "../../../../enums/autofill-overlay.enum";
import {
AutofillOverlayPageElementWindowMessage,
AutofillOverlayPageElementWindowMessageHandlers,
} from "../../abstractions/autofill-overlay-page-element";
AutofillInlineMenuPageElementWindowMessage,
AutofillInlineMenuPageElementWindowMessageHandlers,
} from "../../abstractions/autofill-inline-menu-page-element";
class AutofillOverlayPageElement extends HTMLElement {
export class AutofillInlineMenuPageElement extends HTMLElement {
protected shadowDom: ShadowRoot;
protected messageOrigin: string;
protected translations: Record<string, string>;
private portKey: string;
protected windowMessageHandlers: AutofillOverlayPageElementWindowMessageHandlers;
protected windowMessageHandlers: AutofillInlineMenuPageElementWindowMessageHandlers;
constructor() {
super();
@@ -28,7 +28,7 @@ class AutofillOverlayPageElement extends HTMLElement {
* @param translations - The translations to apply to the page
* @param portKey - Background generated key that allows the port to communicate with the background
*/
protected async initOverlayPage(
protected async initAutofillInlineMenuPage(
elementName: "button" | "list",
styleSheetUrl: string,
translations: Record<string, string>,
@@ -53,7 +53,7 @@ class AutofillOverlayPageElement extends HTMLElement {
*
* @param message - The message to post
*/
protected postMessageToParent(message: AutofillOverlayPageElementWindowMessage) {
protected postMessageToParent(message: AutofillInlineMenuPageElementWindowMessage) {
globalThis.parent.postMessage({ portKey: this.portKey, ...message }, "*");
}
@@ -73,7 +73,7 @@ class AutofillOverlayPageElement extends HTMLElement {
* @param windowMessageHandlers - The window message handlers to use
*/
protected setupGlobalListeners(
windowMessageHandlers: AutofillOverlayPageElementWindowMessageHandlers,
windowMessageHandlers: AutofillInlineMenuPageElementWindowMessageHandlers,
) {
this.windowMessageHandlers = windowMessageHandlers;
@@ -133,25 +133,23 @@ class AutofillOverlayPageElement extends HTMLElement {
event.stopPropagation();
if (event.code === "Tab") {
this.redirectOverlayFocusOutMessage(
this.sendRedirectFocusOutMessage(
event.shiftKey ? RedirectFocusDirection.Previous : RedirectFocusDirection.Next,
);
return;
}
this.redirectOverlayFocusOutMessage(RedirectFocusDirection.Current);
this.sendRedirectFocusOutMessage(RedirectFocusDirection.Current);
};
/**
* Redirects the overlay focus out to the previous element on KeyDown of the `Tab+Shift` keys.
* Redirects the overlay focus out to the next element on KeyDown of the `Tab` key.
* Redirects the overlay focus out to the current element on KeyDown of the `Escape` key.
* Redirects the inline menu focus out to the previous element on KeyDown of the `Tab+Shift` keys.
* Redirects the inline menu focus out to the next element on KeyDown of the `Tab` key.
* Redirects the inline menu focus out to the current element on KeyDown of the `Escape` key.
*
* @param direction - The direction to redirect the focus out
*/
private redirectOverlayFocusOutMessage(direction: string) {
private sendRedirectFocusOutMessage(direction: string) {
this.postMessageToParent({ command: "redirectAutofillInlineMenuFocusOut", direction });
}
}
export default AutofillOverlayPageElement;

View File

@@ -15,7 +15,7 @@ export type AutofillOverlayContentExtensionMessageHandlers = {
[key: string]: CallableFunction;
openAutofillInlineMenu: ({ message }: AutofillExtensionMessageParam) => void;
addNewVaultItemFromOverlay: () => void;
blurMostRecentOverlayField: () => void;
blurMostRecentlyFocusedField: () => void;
bgUnlockPopoutOpened: () => void;
bgVaultItemRepromptPopoutOpened: () => void;
redirectAutofillInlineMenuFocusOut: ({ message }: AutofillExtensionMessageParam) => void;
@@ -32,6 +32,6 @@ export interface AutofillOverlayContentService {
autofillFieldElement: ElementWithOpId<FormFieldElement>,
autofillFieldData: AutofillField,
): Promise<void>;
blurMostRecentOverlayField(isRemovingOverlay?: boolean): void;
blurMostRecentlyFocusedField(isRemovingInlineMenu?: boolean): void;
destroy(): void;
}

View File

@@ -884,7 +884,7 @@ describe("AutofillOverlayContentService", () => {
});
});
describe("blurMostRecentOverlayField", () => {
describe("blurMostRecentlyFocusedField", () => {
it("removes focus from the most recently focused overlay field", () => {
const mostRecentlyFocusedField = document.createElement(
"input",
@@ -892,7 +892,7 @@ describe("AutofillOverlayContentService", () => {
autofillOverlayContentService["mostRecentlyFocusedField"] = mostRecentlyFocusedField;
jest.spyOn(mostRecentlyFocusedField, "blur");
autofillOverlayContentService["blurMostRecentOverlayField"]();
autofillOverlayContentService["blurMostRecentlyFocusedField"]();
expect(mostRecentlyFocusedField.blur).toHaveBeenCalled();
});

View File

@@ -43,9 +43,9 @@ class AutofillOverlayContentService implements AutofillOverlayContentServiceInte
readonly extensionMessageHandlers: AutofillOverlayContentExtensionMessageHandlers = {
openAutofillInlineMenu: ({ message }) => this.openAutofillInlineMenu(message),
addNewVaultItemFromOverlay: () => this.addNewVaultItem(),
blurMostRecentOverlayField: () => this.blurMostRecentOverlayField(),
bgUnlockPopoutOpened: () => this.blurMostRecentOverlayField(true),
bgVaultItemRepromptPopoutOpened: () => this.blurMostRecentOverlayField(true),
blurMostRecentlyFocusedField: () => this.blurMostRecentlyFocusedField(),
bgUnlockPopoutOpened: () => this.blurMostRecentlyFocusedField(true),
bgVaultItemRepromptPopoutOpened: () => this.blurMostRecentlyFocusedField(true),
redirectAutofillInlineMenuFocusOut: ({ message }) =>
this.redirectInlineMenuFocusOut(message?.data?.direction),
updateAutofillInlineMenuVisibility: ({ message }) =>
@@ -154,10 +154,10 @@ class AutofillOverlayContentService implements AutofillOverlayContentServiceInte
/**
* Removes focus from the most recently focused field element.
*/
blurMostRecentOverlayField(isRemovingOverlay: boolean = false) {
blurMostRecentlyFocusedField(isRemovingInlineMenu: boolean = false) {
this.mostRecentlyFocusedField?.blur();
if (isRemovingOverlay) {
if (isRemovingInlineMenu) {
void sendExtensionMessage("closeAutofillInlineMenu");
}
}

View File

@@ -1,4 +1,4 @@
import { AutofillOverlayInlineMenuElements } from "../overlay/inline-menu/abstractions/autofill-overlay-inline-menu-elements";
import { AutofillInlineMenuContentService } from "../overlay/inline-menu/abstractions/autofill-inline-menu-content.service";
import { FillableFormFieldElement, FormFieldElement } from "../types";
import { DomElementVisibilityService as domElementVisibilityServiceInterface } from "./abstractions/dom-element-visibility.service";
@@ -6,7 +6,7 @@ import { DomElementVisibilityService as domElementVisibilityServiceInterface } f
class DomElementVisibilityService implements domElementVisibilityServiceInterface {
private cachedComputedStyle: CSSStyleDeclaration | null = null;
constructor(private inlineMenuElements?: AutofillOverlayInlineMenuElements) {}
constructor(private inlineMenuElements?: AutofillInlineMenuContentService) {}
/**
* Checks if a form field is viewable. This is done by checking if the element is within the

View File

@@ -12,8 +12,8 @@ import AutofillField from "../models/autofill-field";
import AutofillForm from "../models/autofill-form";
import AutofillPageDetails from "../models/autofill-page-details";
import AutofillScript, { FillScript } from "../models/autofill-script";
import { InitAutofillOverlayButtonMessage } from "../overlay/inline-menu/abstractions/autofill-overlay-button";
import { InitAutofillOverlayListMessage } from "../overlay/inline-menu/abstractions/autofill-overlay-list";
import { InitAutofillInlineMenuButtonMessage } from "../overlay/inline-menu/abstractions/autofill-inline-menu-button";
import { InitAutofillOverlayListMessage } from "../overlay/inline-menu/abstractions/autofill-inline-menu-list";
import { GenerateFillScriptOptions, PageDetail } from "../services/abstractions/autofill.service";
function createAutofillFormMock(customFields = {}): AutofillForm {
@@ -165,9 +165,9 @@ const overlayPagesTranslations = {
newItem: "newItem",
addNewVaultItem: "addNewVaultItem",
};
function createInitAutofillOverlayButtonMessageMock(
function createInitAutofillInlineMenuButtonMessageMock(
customFields = {},
): InitAutofillOverlayButtonMessage {
): InitAutofillInlineMenuButtonMessage {
return {
command: "initAutofillInlineMenuButton",
translations: overlayPagesTranslations,
@@ -297,7 +297,7 @@ export {
createChromeTabMock,
createGenerateFillScriptOptionsMock,
createAutofillScriptMock,
createInitAutofillOverlayButtonMessageMock,
createInitAutofillInlineMenuButtonMessageMock,
createInitAutofillOverlayListMessageMock,
createFocusedFieldDataMock,
createPortSpyMock,

View File

@@ -175,7 +175,7 @@ const mainConfig = {
"content/fido2/page-script": "./src/vault/fido2/content/page-script.ts",
"notification/bar": "./src/autofill/notification/bar.ts",
"overlay/button":
"./src/autofill/overlay/inline-menu/pages/button/bootstrap-autofill-overlay-button.ts",
"./src/autofill/overlay/inline-menu/pages/button/bootstrap-autofill-inline-menu-button.ts",
"overlay/list":
"./src/autofill/overlay/inline-menu/pages/list/bootstrap-autofill-overlay-list.ts",
"overlay/menu":