mirror of
https://github.com/bitwarden/browser
synced 2025-12-18 17:23:37 +00:00
[PM-5189] Implementing a really rough sketch of the message-connector rework based on requirements for Firefox
This commit is contained in:
@@ -962,13 +962,19 @@ class OverlayBackground implements OverlayBackgroundInterface {
|
|||||||
port.onDisconnect.addListener(this.handlePortOnDisconnect);
|
port.onDisconnect.addListener(this.handlePortOnDisconnect);
|
||||||
port.postMessage({
|
port.postMessage({
|
||||||
command: `initAutofillOverlay${isOverlayListPort ? "List" : "Button"}`,
|
command: `initAutofillOverlay${isOverlayListPort ? "List" : "Button"}`,
|
||||||
|
iframeUrl: chrome.runtime.getURL(`overlay/${isOverlayListPort ? "list" : "button"}.html`),
|
||||||
|
pageTitle: chrome.i18n.getMessage(
|
||||||
|
isOverlayListPort ? "bitwardenVault" : "bitwardenOverlayButton",
|
||||||
|
),
|
||||||
authStatus: await this.getAuthStatus(),
|
authStatus: await this.getAuthStatus(),
|
||||||
styleSheetUrl: chrome.runtime.getURL(`overlay/${isOverlayListPort ? "list" : "button"}.css`),
|
styleSheetUrl: chrome.runtime.getURL(`overlay/${isOverlayListPort ? "list" : "button"}.css`),
|
||||||
theme: await firstValueFrom(this.themeStateService.selectedTheme$),
|
theme: await firstValueFrom(this.themeStateService.selectedTheme$),
|
||||||
translations: this.getTranslations(),
|
translations: this.getTranslations(),
|
||||||
ciphers: isOverlayListPort ? await this.getOverlayCipherData() : null,
|
ciphers: isOverlayListPort ? await this.getOverlayCipherData() : null,
|
||||||
messageConnectorUrl: chrome.runtime.getURL("overlay/message-connector.html"),
|
|
||||||
portKey: this.portKeyForTab[port.sender.tab.id],
|
portKey: this.portKeyForTab[port.sender.tab.id],
|
||||||
|
portName: isOverlayListPort
|
||||||
|
? AutofillOverlayPort.ListMessageConnector
|
||||||
|
: AutofillOverlayPort.ButtonMessageConnector,
|
||||||
});
|
});
|
||||||
void this.updateOverlayPosition(
|
void this.updateOverlayPosition(
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ type UpdateAuthStatusMessage = OverlayButtonMessage & { authStatus: Authenticati
|
|||||||
type InitAutofillOverlayButtonMessage = UpdateAuthStatusMessage & {
|
type InitAutofillOverlayButtonMessage = UpdateAuthStatusMessage & {
|
||||||
styleSheetUrl: string;
|
styleSheetUrl: string;
|
||||||
translations: Record<string, string>;
|
translations: Record<string, string>;
|
||||||
messageConnectorUrl: string;
|
|
||||||
portKey: string;
|
portKey: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ type AutofillOverlayIframeExtensionMessage = {
|
|||||||
command: string;
|
command: string;
|
||||||
styles?: Partial<CSSStyleDeclaration>;
|
styles?: Partial<CSSStyleDeclaration>;
|
||||||
theme?: string;
|
theme?: string;
|
||||||
|
portKey?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
type AutofillOverlayIframeWindowMessageHandlers = {
|
type AutofillOverlayIframeWindowMessageHandlers = {
|
||||||
@@ -15,6 +16,7 @@ type AutofillOverlayIframeExtensionMessageParam = {
|
|||||||
|
|
||||||
type BackgroundPortMessageHandlers = {
|
type BackgroundPortMessageHandlers = {
|
||||||
[key: string]: CallableFunction;
|
[key: string]: CallableFunction;
|
||||||
|
initAutofillOverlayButton: ({ message }: AutofillOverlayIframeExtensionMessageParam) => void;
|
||||||
initAutofillOverlayList: ({ message }: AutofillOverlayIframeExtensionMessageParam) => void;
|
initAutofillOverlayList: ({ message }: AutofillOverlayIframeExtensionMessageParam) => void;
|
||||||
updateIframePosition: ({ message }: AutofillOverlayIframeExtensionMessageParam) => void;
|
updateIframePosition: ({ message }: AutofillOverlayIframeExtensionMessageParam) => void;
|
||||||
updateOverlayHidden: ({ message }: AutofillOverlayIframeExtensionMessageParam) => void;
|
updateOverlayHidden: ({ message }: AutofillOverlayIframeExtensionMessageParam) => void;
|
||||||
@@ -22,7 +24,7 @@ type BackgroundPortMessageHandlers = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
interface AutofillOverlayIframeService {
|
interface AutofillOverlayIframeService {
|
||||||
initOverlayIframe(initStyles: Partial<CSSStyleDeclaration>, ariaAlert?: string): void;
|
initMenuIframe(): void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export {
|
export {
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ class AutofillOverlayButtonIframe extends AutofillOverlayIframeElement {
|
|||||||
constructor(element: HTMLElement) {
|
constructor(element: HTMLElement) {
|
||||||
super(
|
super(
|
||||||
element,
|
element,
|
||||||
"overlay/button.html",
|
|
||||||
AutofillOverlayPort.Button,
|
AutofillOverlayPort.Button,
|
||||||
{
|
{
|
||||||
background: "transparent",
|
background: "transparent",
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ import AutofillOverlayIframeService from "./autofill-overlay-iframe.service";
|
|||||||
class AutofillOverlayIframeElement {
|
class AutofillOverlayIframeElement {
|
||||||
constructor(
|
constructor(
|
||||||
element: HTMLElement,
|
element: HTMLElement,
|
||||||
iframePath: string,
|
|
||||||
portName: string,
|
portName: string,
|
||||||
initStyles: Partial<CSSStyleDeclaration>,
|
initStyles: Partial<CSSStyleDeclaration>,
|
||||||
iframeTitle: string,
|
iframeTitle: string,
|
||||||
@@ -11,11 +10,13 @@ class AutofillOverlayIframeElement {
|
|||||||
) {
|
) {
|
||||||
const shadow: ShadowRoot = element.attachShadow({ mode: "closed" });
|
const shadow: ShadowRoot = element.attachShadow({ mode: "closed" });
|
||||||
const autofillOverlayIframeService = new AutofillOverlayIframeService(
|
const autofillOverlayIframeService = new AutofillOverlayIframeService(
|
||||||
iframePath,
|
|
||||||
portName,
|
|
||||||
shadow,
|
shadow,
|
||||||
|
portName,
|
||||||
|
initStyles,
|
||||||
|
iframeTitle,
|
||||||
|
ariaAlert,
|
||||||
);
|
);
|
||||||
autofillOverlayIframeService.initOverlayIframe(initStyles, iframeTitle, ariaAlert);
|
autofillOverlayIframeService.initMenuIframe();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import {
|
|||||||
class AutofillOverlayIframeService implements AutofillOverlayIframeServiceInterface {
|
class AutofillOverlayIframeService implements AutofillOverlayIframeServiceInterface {
|
||||||
private sendExtensionMessage = sendExtensionMessage;
|
private sendExtensionMessage = sendExtensionMessage;
|
||||||
private port: chrome.runtime.Port | null = null;
|
private port: chrome.runtime.Port | null = null;
|
||||||
|
private portKey: string;
|
||||||
private iframeMutationObserver: MutationObserver;
|
private iframeMutationObserver: MutationObserver;
|
||||||
private iframe: HTMLIFrameElement;
|
private iframe: HTMLIFrameElement;
|
||||||
private ariaAlertElement: HTMLDivElement;
|
private ariaAlertElement: HTMLDivElement;
|
||||||
@@ -34,7 +35,7 @@ class AutofillOverlayIframeService implements AutofillOverlayIframeServiceInterf
|
|||||||
private defaultIframeAttributes: Record<string, string> = {
|
private defaultIframeAttributes: Record<string, string> = {
|
||||||
src: "",
|
src: "",
|
||||||
title: "",
|
title: "",
|
||||||
sandbox: "allow-scripts",
|
// sandbox: "allow-scripts",
|
||||||
allowtransparency: "true",
|
allowtransparency: "true",
|
||||||
tabIndex: "-1",
|
tabIndex: "-1",
|
||||||
};
|
};
|
||||||
@@ -42,6 +43,7 @@ class AutofillOverlayIframeService implements AutofillOverlayIframeServiceInterf
|
|||||||
private mutationObserverIterations = 0;
|
private mutationObserverIterations = 0;
|
||||||
private mutationObserverIterationsResetTimeout: number | NodeJS.Timeout;
|
private mutationObserverIterationsResetTimeout: number | NodeJS.Timeout;
|
||||||
private readonly backgroundPortMessageHandlers: BackgroundPortMessageHandlers = {
|
private readonly backgroundPortMessageHandlers: BackgroundPortMessageHandlers = {
|
||||||
|
initAutofillOverlayButton: ({ message }) => this.initAutofillOverlay(message),
|
||||||
initAutofillOverlayList: ({ message }) => this.initAutofillOverlayList(message),
|
initAutofillOverlayList: ({ message }) => this.initAutofillOverlayList(message),
|
||||||
updateIframePosition: ({ message }) => this.updateIframePosition(message.styles),
|
updateIframePosition: ({ message }) => this.updateIframePosition(message.styles),
|
||||||
updateOverlayHidden: ({ message }) => this.updateElementStyles(this.iframe, message.styles),
|
updateOverlayHidden: ({ message }) => this.updateElementStyles(this.iframe, message.styles),
|
||||||
@@ -49,9 +51,11 @@ class AutofillOverlayIframeService implements AutofillOverlayIframeServiceInterf
|
|||||||
};
|
};
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private iframePath: string,
|
|
||||||
private portName: string,
|
|
||||||
private shadow: ShadowRoot,
|
private shadow: ShadowRoot,
|
||||||
|
private portName: string,
|
||||||
|
private initStyles: Partial<CSSStyleDeclaration>,
|
||||||
|
private iframeTitle: string,
|
||||||
|
private ariaAlert?: string,
|
||||||
) {
|
) {
|
||||||
this.iframeMutationObserver = new MutationObserver(this.handleMutations);
|
this.iframeMutationObserver = new MutationObserver(this.handleMutations);
|
||||||
}
|
}
|
||||||
@@ -63,29 +67,20 @@ class AutofillOverlayIframeService implements AutofillOverlayIframeServiceInterf
|
|||||||
* create an aria alert element to announce to screen readers when the iframe
|
* create an aria alert element to announce to screen readers when the iframe
|
||||||
* is loaded. The end result is append to the shadowDOM of the custom element
|
* is loaded. The end result is append to the shadowDOM of the custom element
|
||||||
* that is declared.
|
* that is declared.
|
||||||
*
|
|
||||||
*
|
|
||||||
* @param initStyles - Initial styles to apply to the iframe
|
|
||||||
* @param iframeTitle - Title to apply to the iframe
|
|
||||||
* @param ariaAlert - Text to announce to screen readers when the iframe is loaded
|
|
||||||
*/
|
*/
|
||||||
initOverlayIframe(
|
initMenuIframe() {
|
||||||
initStyles: Partial<CSSStyleDeclaration>,
|
this.defaultIframeAttributes.src = chrome.runtime.getURL("overlay/menu.html");
|
||||||
iframeTitle: string,
|
this.defaultIframeAttributes.title = this.iframeTitle;
|
||||||
ariaAlert?: string,
|
|
||||||
) {
|
|
||||||
this.defaultIframeAttributes.src = chrome.runtime.getURL(this.iframePath);
|
|
||||||
this.defaultIframeAttributes.title = iframeTitle;
|
|
||||||
|
|
||||||
this.iframe = globalThis.document.createElement("iframe");
|
this.iframe = globalThis.document.createElement("iframe");
|
||||||
this.updateElementStyles(this.iframe, { ...this.iframeStyles, ...initStyles });
|
this.updateElementStyles(this.iframe, { ...this.iframeStyles, ...this.initStyles });
|
||||||
for (const [attribute, value] of Object.entries(this.defaultIframeAttributes)) {
|
for (const [attribute, value] of Object.entries(this.defaultIframeAttributes)) {
|
||||||
this.iframe.setAttribute(attribute, value);
|
this.iframe.setAttribute(attribute, value);
|
||||||
}
|
}
|
||||||
this.iframe.addEventListener(EVENTS.LOAD, this.setupPortMessageListener);
|
this.iframe.addEventListener(EVENTS.LOAD, this.setupPortMessageListener);
|
||||||
|
|
||||||
if (ariaAlert) {
|
if (this.ariaAlert) {
|
||||||
this.createAriaAlertElement(ariaAlert);
|
this.createAriaAlertElement(this.ariaAlert);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.shadow.appendChild(this.iframe);
|
this.shadow.appendChild(this.iframe);
|
||||||
@@ -185,9 +180,14 @@ class AutofillOverlayIframeService implements AutofillOverlayIframeServiceInterf
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.iframe.contentWindow?.postMessage(message, "*");
|
this.postMessageToIFrame(message);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
private initAutofillOverlay(message: AutofillOverlayIframeExtensionMessage) {
|
||||||
|
this.portKey = message.portKey;
|
||||||
|
this.postMessageToIFrame(message);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handles messages sent from the iframe to the extension background script.
|
* Handles messages sent from the iframe to the extension background script.
|
||||||
* Will adjust the border element to fit the user's set theme.
|
* Will adjust the border element to fit the user's set theme.
|
||||||
@@ -196,6 +196,7 @@ class AutofillOverlayIframeService implements AutofillOverlayIframeServiceInterf
|
|||||||
*/
|
*/
|
||||||
private initAutofillOverlayList(message: AutofillOverlayIframeExtensionMessage) {
|
private initAutofillOverlayList(message: AutofillOverlayIframeExtensionMessage) {
|
||||||
const { theme } = message;
|
const { theme } = message;
|
||||||
|
this.portKey = message.portKey;
|
||||||
let borderColor: string;
|
let borderColor: string;
|
||||||
let verifiedTheme = theme;
|
let verifiedTheme = theme;
|
||||||
if (verifiedTheme === ThemeType.System) {
|
if (verifiedTheme === ThemeType.System) {
|
||||||
@@ -218,7 +219,11 @@ class AutofillOverlayIframeService implements AutofillOverlayIframeServiceInterf
|
|||||||
}
|
}
|
||||||
|
|
||||||
message.theme = verifiedTheme;
|
message.theme = verifiedTheme;
|
||||||
this.iframe.contentWindow?.postMessage(message, "*");
|
this.postMessageToIFrame(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
private postMessageToIFrame(message: any) {
|
||||||
|
this.iframe.contentWindow?.postMessage({ portKey: this.portKey, ...message }, "*");
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -247,10 +252,10 @@ class AutofillOverlayIframeService implements AutofillOverlayIframeServiceInterf
|
|||||||
.querySelector("meta[name='color-scheme']")
|
.querySelector("meta[name='color-scheme']")
|
||||||
?.getAttribute("content");
|
?.getAttribute("content");
|
||||||
|
|
||||||
this.iframe.contentWindow?.postMessage(
|
this.postMessageToIFrame({
|
||||||
{ command: "updateOverlayPageColorScheme", colorScheme: colorSchemeValue || "normal" },
|
command: "updateOverlayPageColorScheme",
|
||||||
"*",
|
colorScheme: colorSchemeValue || "normal",
|
||||||
);
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ class AutofillOverlayListIframe extends AutofillOverlayIframeElement {
|
|||||||
constructor(element: HTMLElement) {
|
constructor(element: HTMLElement) {
|
||||||
super(
|
super(
|
||||||
element,
|
element,
|
||||||
"overlay/list.html",
|
|
||||||
AutofillOverlayPort.List,
|
AutofillOverlayPort.List,
|
||||||
{
|
{
|
||||||
height: "0px",
|
height: "0px",
|
||||||
|
|||||||
@@ -53,16 +53,9 @@ class AutofillOverlayButton extends AutofillOverlayPageElement {
|
|||||||
authStatus,
|
authStatus,
|
||||||
styleSheetUrl,
|
styleSheetUrl,
|
||||||
translations,
|
translations,
|
||||||
messageConnectorUrl,
|
|
||||||
portKey,
|
portKey,
|
||||||
}: InitAutofillOverlayButtonMessage) {
|
}: InitAutofillOverlayButtonMessage) {
|
||||||
const linkElement = await this.initOverlayPage(
|
const linkElement = await this.initOverlayPage("button", styleSheetUrl, translations, portKey);
|
||||||
"button",
|
|
||||||
styleSheetUrl,
|
|
||||||
translations,
|
|
||||||
messageConnectorUrl,
|
|
||||||
portKey,
|
|
||||||
);
|
|
||||||
this.buttonElement.tabIndex = -1;
|
this.buttonElement.tabIndex = -1;
|
||||||
this.buttonElement.type = "button";
|
this.buttonElement.type = "button";
|
||||||
this.buttonElement.classList.add("overlay-button");
|
this.buttonElement.classList.add("overlay-button");
|
||||||
|
|||||||
@@ -44,7 +44,6 @@ class AutofillOverlayList extends AutofillOverlayPageElement {
|
|||||||
* @param theme - The theme to use for the overlay list.
|
* @param theme - The theme to use for the overlay list.
|
||||||
* @param authStatus - The current authentication status.
|
* @param authStatus - The current authentication status.
|
||||||
* @param ciphers - The ciphers to display in the overlay list.
|
* @param ciphers - The ciphers to display in the overlay list.
|
||||||
* @param messageConnectorUrl - The URL of the message connector to use.
|
|
||||||
* @param portKey - Background generated key that allows the port to communicate with the background.
|
* @param portKey - Background generated key that allows the port to communicate with the background.
|
||||||
*/
|
*/
|
||||||
private async initAutofillOverlayList({
|
private async initAutofillOverlayList({
|
||||||
@@ -53,16 +52,9 @@ class AutofillOverlayList extends AutofillOverlayPageElement {
|
|||||||
theme,
|
theme,
|
||||||
authStatus,
|
authStatus,
|
||||||
ciphers,
|
ciphers,
|
||||||
messageConnectorUrl,
|
|
||||||
portKey,
|
portKey,
|
||||||
}: InitAutofillOverlayListMessage) {
|
}: InitAutofillOverlayListMessage) {
|
||||||
const linkElement = await this.initOverlayPage(
|
const linkElement = await this.initOverlayPage("list", styleSheetUrl, translations, portKey);
|
||||||
"list",
|
|
||||||
styleSheetUrl,
|
|
||||||
translations,
|
|
||||||
messageConnectorUrl,
|
|
||||||
portKey,
|
|
||||||
);
|
|
||||||
|
|
||||||
const themeClass = `theme_${theme}`;
|
const themeClass = `theme_${theme}`;
|
||||||
globalThis.document.documentElement.classList.add(themeClass);
|
globalThis.document.documentElement.classList.add(themeClass);
|
||||||
|
|||||||
@@ -0,0 +1,132 @@
|
|||||||
|
import { EVENTS } from "@bitwarden/common/autofill/constants";
|
||||||
|
|
||||||
|
import { setElementStyles } from "../../../utils";
|
||||||
|
|
||||||
|
export class AutofillOverlayMenuContainer {
|
||||||
|
private initMessage: any;
|
||||||
|
|
||||||
|
private extensionOriginsSet: Set<string>;
|
||||||
|
private port: chrome.runtime.Port | null = null;
|
||||||
|
private portName: string;
|
||||||
|
private iframe: HTMLIFrameElement;
|
||||||
|
private iframeStyles: Partial<CSSStyleDeclaration> = {
|
||||||
|
all: "initial",
|
||||||
|
position: "fixed",
|
||||||
|
top: "0",
|
||||||
|
left: "0",
|
||||||
|
width: "100%",
|
||||||
|
height: "100%",
|
||||||
|
display: "block",
|
||||||
|
zIndex: "2147483647",
|
||||||
|
lineHeight: "0",
|
||||||
|
overflow: "hidden",
|
||||||
|
visibility: "visible",
|
||||||
|
clipPath: "none",
|
||||||
|
pointerEvents: "auto",
|
||||||
|
margin: "0",
|
||||||
|
padding: "0",
|
||||||
|
colorScheme: "normal",
|
||||||
|
};
|
||||||
|
private defaultIframeAttributes: Record<string, string> = {
|
||||||
|
src: "",
|
||||||
|
title: "",
|
||||||
|
// sandbox: "allow-scripts",
|
||||||
|
allowtransparency: "true",
|
||||||
|
tabIndex: "-1",
|
||||||
|
};
|
||||||
|
private windowMessageHandlers: Record<string, (message: any) => void> = {
|
||||||
|
initAutofillOverlayList: (message: any) => this.handleInitOverlayIframe(message),
|
||||||
|
initAutofillOverlayButton: (message: any) => this.handleInitOverlayIframe(message),
|
||||||
|
};
|
||||||
|
private backgroundPortMessageHandlers: Record<string, (message: any) => void> = {};
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
this.extensionOriginsSet = new Set([
|
||||||
|
chrome.runtime.getURL("").slice(0, -1).toLowerCase(), // Remove the trailing slash and normalize the extension url to lowercase
|
||||||
|
"null",
|
||||||
|
]);
|
||||||
|
|
||||||
|
globalThis.addEventListener("message", this.handleWindowMessage);
|
||||||
|
}
|
||||||
|
|
||||||
|
private handleInitOverlayIframe(message: any) {
|
||||||
|
this.initMessage = message;
|
||||||
|
this.defaultIframeAttributes.src = message.iframeUrl;
|
||||||
|
this.defaultIframeAttributes.title = message.pageTitle;
|
||||||
|
this.portName = message.portName;
|
||||||
|
|
||||||
|
this.iframe = globalThis.document.createElement("iframe");
|
||||||
|
setElementStyles(this.iframe, this.iframeStyles, true);
|
||||||
|
for (const [attribute, value] of Object.entries(this.defaultIframeAttributes)) {
|
||||||
|
this.iframe.setAttribute(attribute, value);
|
||||||
|
}
|
||||||
|
this.iframe.addEventListener(EVENTS.LOAD, this.setupPortMessageListener);
|
||||||
|
|
||||||
|
globalThis.document.body.appendChild(this.iframe);
|
||||||
|
}
|
||||||
|
|
||||||
|
private setupPortMessageListener = () => {
|
||||||
|
this.port = chrome.runtime.connect({ name: this.portName });
|
||||||
|
this.port.onMessage.addListener(this.handlePortMessage);
|
||||||
|
|
||||||
|
this.iframe.contentWindow?.postMessage(this.initMessage, "*");
|
||||||
|
};
|
||||||
|
|
||||||
|
private handlePortMessage = (message: any, port: chrome.runtime.Port) => {
|
||||||
|
if (port.name !== this.portName) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.backgroundPortMessageHandlers[message.command]) {
|
||||||
|
this.backgroundPortMessageHandlers[message.command]({ message, port });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.iframe.contentWindow?.postMessage(message, "*");
|
||||||
|
};
|
||||||
|
|
||||||
|
private handleWindowMessage = (event: MessageEvent) => {
|
||||||
|
const message = event.data;
|
||||||
|
if (this.isForeignWindowMessage(event)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.windowMessageHandlers[message.command]) {
|
||||||
|
this.windowMessageHandlers[message.command](message);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (event.source === globalThis.parent) {
|
||||||
|
this.iframe?.contentWindow?.postMessage(message, "*");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.port?.postMessage(message);
|
||||||
|
};
|
||||||
|
|
||||||
|
private isForeignWindowMessage(event: MessageEvent) {
|
||||||
|
if (!event.data.portKey) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (globalThis.parent === event.source) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
this.iframe?.contentWindow !== event.source ||
|
||||||
|
!this.isFromExtensionOrigin(event.origin.toLowerCase())
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Chrome returns null for any sandboxed iframe sources.
|
||||||
|
* Firefox references the extension URI as its origin.
|
||||||
|
* Any other origin value is a security risk.
|
||||||
|
*
|
||||||
|
* @param messageOrigin - The origin of the window message
|
||||||
|
*/
|
||||||
|
private isFromExtensionOrigin(messageOrigin: string): boolean {
|
||||||
|
return this.extensionOriginsSet.has(messageOrigin);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
import { AutofillOverlayMenuContainer } from "./autofill-overlay-menu-container";
|
||||||
|
|
||||||
|
(() => new AutofillOverlayMenuContainer())();
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
<!doctype html>
|
<!doctype html>
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<title>Autofill overlay message connector</title>
|
<title>Bitwarden inline menu</title>
|
||||||
<meta charset="utf-8" />
|
<meta charset="utf-8" />
|
||||||
<meta name="viewport" content="width=device-width,initial-scale=1" />
|
<meta name="viewport" content="width=device-width,initial-scale=1" />
|
||||||
<meta name="color-scheme" content="normal" />
|
<meta name="color-scheme" content="normal" />
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
import { AutofillOverlayMessageConnector } from "./message-connector";
|
|
||||||
|
|
||||||
(() => new AutofillOverlayMessageConnector())();
|
|
||||||
@@ -1,44 +0,0 @@
|
|||||||
export class AutofillOverlayMessageConnector {
|
|
||||||
private extensionOriginsSet: Set<string>;
|
|
||||||
private port: chrome.runtime.Port | null = null;
|
|
||||||
|
|
||||||
constructor() {
|
|
||||||
globalThis.addEventListener("message", this.handleWindowMessage);
|
|
||||||
|
|
||||||
this.extensionOriginsSet = new Set([
|
|
||||||
chrome.runtime.getURL("").slice(0, -1).toLowerCase(), // Remove the trailing slash and normalize the extension url to lowercase
|
|
||||||
"null",
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
private handleWindowMessage = (event: MessageEvent) => {
|
|
||||||
const message = event.data;
|
|
||||||
if (
|
|
||||||
event.source !== globalThis.parent ||
|
|
||||||
!this.isFromExtensionOrigin(event.origin.toLowerCase()) ||
|
|
||||||
!message.portKey
|
|
||||||
) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.port) {
|
|
||||||
this.port.postMessage(message);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (message.command === "initAutofillOverlayPort") {
|
|
||||||
this.port = chrome.runtime.connect({ name: message.portName });
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Chrome returns null for any sandboxed iframe sources.
|
|
||||||
* Firefox references the extension URI as its origin.
|
|
||||||
* Any other origin value is a security risk.
|
|
||||||
*
|
|
||||||
* @param messageOrigin - The origin of the window message
|
|
||||||
*/
|
|
||||||
private isFromExtensionOrigin(messageOrigin: string): boolean {
|
|
||||||
return this.extensionOriginsSet.has(messageOrigin);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
import { EVENTS } from "@bitwarden/common/autofill/constants";
|
import { EVENTS } from "@bitwarden/common/autofill/constants";
|
||||||
|
|
||||||
import { AutofillOverlayPort, RedirectFocusDirection } from "../../../utils/autofill-overlay.enum";
|
import { RedirectFocusDirection } from "../../../utils/autofill-overlay.enum";
|
||||||
import {
|
import {
|
||||||
AutofillOverlayPageElementWindowMessage,
|
AutofillOverlayPageElementWindowMessage,
|
||||||
WindowMessageHandlers,
|
WindowMessageHandlers,
|
||||||
@@ -27,14 +27,12 @@ class AutofillOverlayPageElement extends HTMLElement {
|
|||||||
* @param elementName - The name of the element, e.g. "button" or "list"
|
* @param elementName - The name of the element, e.g. "button" or "list"
|
||||||
* @param styleSheetUrl - The URL of the stylesheet to apply to the page
|
* @param styleSheetUrl - The URL of the stylesheet to apply to the page
|
||||||
* @param translations - The translations to apply to the page
|
* @param translations - The translations to apply to the page
|
||||||
* @param messageConnectorUrl - The URL of the message connector to use
|
|
||||||
* @param portKey - Background generated key that allows the port to communicate with the background
|
* @param portKey - Background generated key that allows the port to communicate with the background
|
||||||
*/
|
*/
|
||||||
protected async initOverlayPage(
|
protected async initOverlayPage(
|
||||||
elementName: "button" | "list",
|
elementName: "button" | "list",
|
||||||
styleSheetUrl: string,
|
styleSheetUrl: string,
|
||||||
translations: Record<string, string>,
|
translations: Record<string, string>,
|
||||||
messageConnectorUrl: string,
|
|
||||||
portKey: string,
|
portKey: string,
|
||||||
): Promise<HTMLLinkElement> {
|
): Promise<HTMLLinkElement> {
|
||||||
this.portKey = portKey;
|
this.portKey = portKey;
|
||||||
@@ -43,8 +41,6 @@ class AutofillOverlayPageElement extends HTMLElement {
|
|||||||
globalThis.document.documentElement.setAttribute("lang", this.getTranslation("locale"));
|
globalThis.document.documentElement.setAttribute("lang", this.getTranslation("locale"));
|
||||||
globalThis.document.head.title = this.getTranslation(`${elementName}PageTitle`);
|
globalThis.document.head.title = this.getTranslation(`${elementName}PageTitle`);
|
||||||
|
|
||||||
await this.initMessageConnector(messageConnectorUrl, elementName);
|
|
||||||
|
|
||||||
this.shadowDom.innerHTML = "";
|
this.shadowDom.innerHTML = "";
|
||||||
const linkElement = globalThis.document.createElement("link");
|
const linkElement = globalThis.document.createElement("link");
|
||||||
linkElement.setAttribute("rel", "stylesheet");
|
linkElement.setAttribute("rel", "stylesheet");
|
||||||
@@ -62,30 +58,30 @@ class AutofillOverlayPageElement extends HTMLElement {
|
|||||||
* @param messageConnectorUrl - The URL of the message connector to use
|
* @param messageConnectorUrl - The URL of the message connector to use
|
||||||
* @param elementName - The name of the element, e.g. "button" or "list"
|
* @param elementName - The name of the element, e.g. "button" or "list"
|
||||||
*/
|
*/
|
||||||
private initMessageConnector(messageConnectorUrl: string, elementName: "button" | "list") {
|
// private initMessageConnector(messageConnectorUrl: string, elementName: "button" | "list") {
|
||||||
this.messageConnectorIframe = globalThis.document.createElement("iframe");
|
// this.messageConnectorIframe = globalThis.document.createElement("iframe");
|
||||||
this.messageConnectorIframe.src = messageConnectorUrl;
|
// this.messageConnectorIframe.src = messageConnectorUrl;
|
||||||
this.messageConnectorIframe.style.opacity = "0";
|
// this.messageConnectorIframe.style.opacity = "0";
|
||||||
this.messageConnectorIframe.style.position = "absolute";
|
// this.messageConnectorIframe.style.position = "absolute";
|
||||||
this.messageConnectorIframe.style.width = "0";
|
// this.messageConnectorIframe.style.width = "0";
|
||||||
this.messageConnectorIframe.style.height = "0";
|
// this.messageConnectorIframe.style.height = "0";
|
||||||
this.messageConnectorIframe.style.border = "none";
|
// this.messageConnectorIframe.style.border = "none";
|
||||||
this.messageConnectorIframe.style.pointerEvents = "none";
|
// this.messageConnectorIframe.style.pointerEvents = "none";
|
||||||
globalThis.document.body.appendChild(this.messageConnectorIframe);
|
// globalThis.document.body.appendChild(this.messageConnectorIframe);
|
||||||
|
//
|
||||||
return new Promise<void>((resolve) => {
|
// return new Promise<void>((resolve) => {
|
||||||
this.messageConnectorIframe.addEventListener(EVENTS.LOAD, () => {
|
// this.messageConnectorIframe.addEventListener(EVENTS.LOAD, () => {
|
||||||
this.postMessageToConnector({
|
// this.postMessageToConnector({
|
||||||
command: `initAutofillOverlayPort`,
|
// command: `initAutofillOverlayPort`,
|
||||||
portName:
|
// portName:
|
||||||
elementName === "list"
|
// elementName === "list"
|
||||||
? AutofillOverlayPort.ListMessageConnector
|
// ? AutofillOverlayPort.ListMessageConnector
|
||||||
: AutofillOverlayPort.ButtonMessageConnector,
|
// : AutofillOverlayPort.ButtonMessageConnector,
|
||||||
});
|
// });
|
||||||
resolve();
|
// resolve();
|
||||||
});
|
// });
|
||||||
});
|
// });
|
||||||
}
|
// }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Posts a window message to the parent window.
|
* Posts a window message to the parent window.
|
||||||
@@ -93,10 +89,12 @@ class AutofillOverlayPageElement extends HTMLElement {
|
|||||||
* @param message - The message to post
|
* @param message - The message to post
|
||||||
*/
|
*/
|
||||||
protected postMessageToConnector(message: AutofillOverlayPageElementWindowMessage) {
|
protected postMessageToConnector(message: AutofillOverlayPageElementWindowMessage) {
|
||||||
this.messageConnectorIframe.contentWindow.postMessage(
|
globalThis.parent.postMessage({ portKey: this.portKey, ...message }, "*");
|
||||||
{ portKey: this.portKey, ...message },
|
|
||||||
"*",
|
// this.messageConnectorIframe.contentWindow.postMessage(
|
||||||
);
|
// { portKey: this.portKey, ...message },
|
||||||
|
// "*",
|
||||||
|
// );
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -115,7 +115,7 @@
|
|||||||
"images/icon38_locked.png",
|
"images/icon38_locked.png",
|
||||||
"overlay/button.html",
|
"overlay/button.html",
|
||||||
"overlay/list.html",
|
"overlay/list.html",
|
||||||
"overlay/message-connector.html",
|
"overlay/menu.html",
|
||||||
"popup/fonts/*"
|
"popup/fonts/*"
|
||||||
],
|
],
|
||||||
"applications": {
|
"applications": {
|
||||||
|
|||||||
@@ -119,7 +119,7 @@
|
|||||||
"images/icon38_locked.png",
|
"images/icon38_locked.png",
|
||||||
"overlay/button.html",
|
"overlay/button.html",
|
||||||
"overlay/list.html",
|
"overlay/list.html",
|
||||||
"overlay/message-connector.html",
|
"overlay/menu.html",
|
||||||
"popup/fonts/*"
|
"popup/fonts/*"
|
||||||
],
|
],
|
||||||
"matches": ["<all_urls>"]
|
"matches": ["<all_urls>"]
|
||||||
|
|||||||
@@ -117,9 +117,9 @@ const plugins = [
|
|||||||
chunks: ["overlay/list"],
|
chunks: ["overlay/list"],
|
||||||
}),
|
}),
|
||||||
new HtmlWebpackPlugin({
|
new HtmlWebpackPlugin({
|
||||||
template: "./src/autofill/overlay/pages/message-connector/message-connector.html",
|
template: "./src/autofill/overlay/pages/menu/menu.html",
|
||||||
filename: "overlay/message-connector.html",
|
filename: "overlay/menu.html",
|
||||||
chunks: ["overlay/message-connector"],
|
chunks: ["overlay/menu"],
|
||||||
}),
|
}),
|
||||||
new CopyWebpackPlugin({
|
new CopyWebpackPlugin({
|
||||||
patterns: [
|
patterns: [
|
||||||
@@ -178,8 +178,8 @@ const mainConfig = {
|
|||||||
"notification/bar": "./src/autofill/notification/bar.ts",
|
"notification/bar": "./src/autofill/notification/bar.ts",
|
||||||
"overlay/button": "./src/autofill/overlay/pages/button/bootstrap-autofill-overlay-button.ts",
|
"overlay/button": "./src/autofill/overlay/pages/button/bootstrap-autofill-overlay-button.ts",
|
||||||
"overlay/list": "./src/autofill/overlay/pages/list/bootstrap-autofill-overlay-list.ts",
|
"overlay/list": "./src/autofill/overlay/pages/list/bootstrap-autofill-overlay-list.ts",
|
||||||
"overlay/message-connector":
|
"overlay/menu":
|
||||||
"./src/autofill/overlay/pages/message-connector/bootstrap-autofill-overlay-message-connector.ts",
|
"./src/autofill/overlay/pages/menu/bootstrap-autofill-overlay-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",
|
||||||
|
|||||||
Reference in New Issue
Block a user