1
0
mirror of https://github.com/bitwarden/browser synced 2025-12-19 17:53:39 +00:00

[PM-6546] Implementing a methodology where Firefox browsers render the overlay UI within a div element rather than custom web component

This commit is contained in:
Cesar Gonzalez
2024-03-19 13:46:55 -05:00
parent 57464c41f3
commit 0cdadf284b
8 changed files with 122 additions and 23 deletions

View File

@@ -1,8 +1,15 @@
import AutofillOverlayButtonIframe from "./autofill-overlay-button-iframe"; import AutofillOverlayButtonIframe from "./autofill-overlay-button-iframe";
import AutofillOverlayIframeElement from "./autofill-overlay-iframe-element";
describe("AutofillOverlayButtonIframe", () => { describe("AutofillOverlayButtonIframe", () => {
window.customElements.define("autofill-overlay-button-iframe", AutofillOverlayButtonIframe); window.customElements.define(
"autofill-overlay-button-iframe",
class extends HTMLElement {
constructor() {
super();
new AutofillOverlayButtonIframe(this);
}
},
);
afterAll(() => { afterAll(() => {
jest.clearAllMocks(); jest.clearAllMocks();
@@ -13,7 +20,7 @@ describe("AutofillOverlayButtonIframe", () => {
const iframe = document.querySelector("autofill-overlay-button-iframe"); const iframe = document.querySelector("autofill-overlay-button-iframe");
expect(iframe).toBeInstanceOf(AutofillOverlayButtonIframe); expect(iframe).toBeInstanceOf(HTMLElement);
expect(iframe).toBeInstanceOf(AutofillOverlayIframeElement); expect(iframe.shadowRoot).toBeDefined();
}); });
}); });

View File

@@ -3,8 +3,9 @@ import { AutofillOverlayPort } from "../../utils/autofill-overlay.enum";
import AutofillOverlayIframeElement from "./autofill-overlay-iframe-element"; import AutofillOverlayIframeElement from "./autofill-overlay-iframe-element";
class AutofillOverlayButtonIframe extends AutofillOverlayIframeElement { class AutofillOverlayButtonIframe extends AutofillOverlayIframeElement {
constructor() { constructor(element: HTMLElement) {
super( super(
element,
"overlay/button.html", "overlay/button.html",
AutofillOverlayPort.Button, AutofillOverlayPort.Button,
{ {

View File

@@ -4,7 +4,21 @@ import AutofillOverlayIframeService from "./autofill-overlay-iframe.service";
jest.mock("./autofill-overlay-iframe.service"); jest.mock("./autofill-overlay-iframe.service");
describe("AutofillOverlayIframeElement", () => { describe("AutofillOverlayIframeElement", () => {
window.customElements.define("autofill-overlay-iframe", AutofillOverlayIframeElement); window.customElements.define(
"autofill-overlay-iframe",
class extends HTMLElement {
constructor() {
super();
new AutofillOverlayIframeElement(
this,
"overlay/button.html",
"overlay/button",
{ background: "transparent", border: "none" },
"bitwardenOverlayButton",
);
}
},
);
afterAll(() => { afterAll(() => {
jest.clearAllMocks(); jest.clearAllMocks();

View File

@@ -1,16 +1,15 @@
import AutofillOverlayIframeService from "./autofill-overlay-iframe.service"; import AutofillOverlayIframeService from "./autofill-overlay-iframe.service";
class AutofillOverlayIframeElement extends HTMLElement { class AutofillOverlayIframeElement {
constructor( constructor(
element: HTMLElement,
iframePath: string, iframePath: string,
portName: string, portName: string,
initStyles: Partial<CSSStyleDeclaration>, initStyles: Partial<CSSStyleDeclaration>,
iframeTitle: string, iframeTitle: string,
ariaAlert?: string, ariaAlert?: string,
) { ) {
super(); const shadow: ShadowRoot = element.attachShadow({ mode: "closed" });
const shadow: ShadowRoot = this.attachShadow({ mode: "closed" });
const autofillOverlayIframeService = new AutofillOverlayIframeService( const autofillOverlayIframeService = new AutofillOverlayIframeService(
iframePath, iframePath,
portName, portName,

View File

@@ -1,8 +1,15 @@
import AutofillOverlayIframeElement from "./autofill-overlay-iframe-element";
import AutofillOverlayListIframe from "./autofill-overlay-list-iframe"; import AutofillOverlayListIframe from "./autofill-overlay-list-iframe";
describe("AutofillOverlayListIframe", () => { describe("AutofillOverlayListIframe", () => {
window.customElements.define("autofill-overlay-list-iframe", AutofillOverlayListIframe); window.customElements.define(
"autofill-overlay-list-iframe",
class extends HTMLElement {
constructor() {
super();
new AutofillOverlayListIframe(this);
}
},
);
afterAll(() => { afterAll(() => {
jest.clearAllMocks(); jest.clearAllMocks();
@@ -13,7 +20,7 @@ describe("AutofillOverlayListIframe", () => {
const iframe = document.querySelector("autofill-overlay-list-iframe"); const iframe = document.querySelector("autofill-overlay-list-iframe");
expect(iframe).toBeInstanceOf(AutofillOverlayListIframe); expect(iframe).toBeInstanceOf(HTMLElement);
expect(iframe).toBeInstanceOf(AutofillOverlayIframeElement); expect(iframe.shadowRoot).toBeDefined();
}); });
}); });

View File

@@ -3,8 +3,9 @@ import { AutofillOverlayPort } from "../../utils/autofill-overlay.enum";
import AutofillOverlayIframeElement from "./autofill-overlay-iframe-element"; import AutofillOverlayIframeElement from "./autofill-overlay-iframe-element";
class AutofillOverlayListIframe extends AutofillOverlayIframeElement { class AutofillOverlayListIframe extends AutofillOverlayIframeElement {
constructor() { constructor(element: HTMLElement) {
super( super(
element,
"overlay/list.html", "overlay/list.html",
AutofillOverlayPort.List, AutofillOverlayPort.List,
{ {

View File

@@ -877,6 +877,44 @@ describe("AutofillOverlayContentService", () => {
sender: "autofillOverlayContentService", sender: "autofillOverlayContentService",
}); });
}); });
it("builds the overlay elements as custom web components if the user's browser is not Firefox", () => {
let namesIndex = 0;
const customNames = ["op-autofill-overlay-button", "op-autofill-overlay-list"];
jest
.spyOn(autofillOverlayContentService as any, "generateRandomCustomElementName")
.mockImplementation(() => {
if (namesIndex > 1) {
return "";
}
const customName = customNames[namesIndex];
namesIndex++;
return customName;
});
autofillOverlayContentService["isFirefoxBrowser"] = false;
autofillOverlayContentService.openAutofillOverlay();
expect(autofillOverlayContentService["overlayButtonElement"]).toBeInstanceOf(HTMLElement);
expect(autofillOverlayContentService["overlayButtonElement"].tagName).toEqual(
customNames[0].toUpperCase(),
);
expect(autofillOverlayContentService["overlayListElement"]).toBeInstanceOf(HTMLElement);
expect(autofillOverlayContentService["overlayListElement"].tagName).toEqual(
customNames[1].toUpperCase(),
);
});
it("builds the overlay elements as `div` elements if the user's browser is Firefox", () => {
autofillOverlayContentService["isFirefoxBrowser"] = true;
autofillOverlayContentService.openAutofillOverlay();
expect(autofillOverlayContentService["overlayButtonElement"]).toBeInstanceOf(HTMLDivElement);
expect(autofillOverlayContentService["overlayListElement"]).toBeInstanceOf(HTMLDivElement);
});
}); });
describe("focusMostRecentOverlayField", () => { describe("focusMostRecentOverlayField", () => {

View File

@@ -30,6 +30,10 @@ class AutofillOverlayContentService implements AutofillOverlayContentServiceInte
isOverlayCiphersPopulated = false; isOverlayCiphersPopulated = false;
pageDetailsUpdateRequired = false; pageDetailsUpdateRequired = false;
autofillOverlayVisibility: number; autofillOverlayVisibility: number;
private isFirefoxBrowser =
globalThis.navigator.userAgent.indexOf(" Firefox/") !== -1 ||
globalThis.navigator.userAgent.indexOf(" Gecko/") !== -1;
private readonly generateRandomCustomElementName = generateRandomCustomElementName;
private readonly findTabs = tabbable; private readonly findTabs = tabbable;
private readonly sendExtensionMessage = sendExtensionMessage; private readonly sendExtensionMessage = sendExtensionMessage;
private formFieldElements: Set<ElementWithOpId<FormFieldElement>> = new Set([]); private formFieldElements: Set<ElementWithOpId<FormFieldElement>> = new Set([]);
@@ -593,6 +597,7 @@ class AutofillOverlayContentService implements AutofillOverlayContentServiceInte
private updateOverlayButtonPosition() { private updateOverlayButtonPosition() {
if (!this.overlayButtonElement) { if (!this.overlayButtonElement) {
this.createAutofillOverlayButton(); this.createAutofillOverlayButton();
this.updateCustomElementDefaultStyles(this.overlayButtonElement);
} }
if (!this.isOverlayButtonVisible) { if (!this.isOverlayButtonVisible) {
@@ -613,6 +618,7 @@ class AutofillOverlayContentService implements AutofillOverlayContentServiceInte
private updateOverlayListPosition() { private updateOverlayListPosition() {
if (!this.overlayListElement) { if (!this.overlayListElement) {
this.createAutofillOverlayList(); this.createAutofillOverlayList();
this.updateCustomElementDefaultStyles(this.overlayListElement);
} }
if (!this.isOverlayListVisible) { if (!this.isOverlayListVisible) {
@@ -765,11 +771,24 @@ class AutofillOverlayContentService implements AutofillOverlayContentServiceInte
return; return;
} }
const customElementName = generateRandomCustomElementName(); if (this.isFirefoxBrowser) {
globalThis.customElements?.define(customElementName, AutofillOverlayButtonIframe); this.overlayButtonElement = globalThis.document.createElement("div");
this.overlayButtonElement = globalThis.document.createElement(customElementName); new AutofillOverlayButtonIframe(this.overlayButtonElement);
this.updateCustomElementDefaultStyles(this.overlayButtonElement); return;
}
const customElementName = this.generateRandomCustomElementName();
globalThis.customElements?.define(
customElementName,
class extends HTMLElement {
constructor() {
super();
new AutofillOverlayButtonIframe(this);
}
},
);
this.overlayButtonElement = globalThis.document.createElement(customElementName);
} }
/** /**
@@ -781,11 +800,24 @@ class AutofillOverlayContentService implements AutofillOverlayContentServiceInte
return; return;
} }
const customElementName = generateRandomCustomElementName(); if (this.isFirefoxBrowser) {
globalThis.customElements?.define(customElementName, AutofillOverlayListIframe); this.overlayListElement = globalThis.document.createElement("div");
this.overlayListElement = globalThis.document.createElement(customElementName); new AutofillOverlayListIframe(this.overlayListElement);
this.updateCustomElementDefaultStyles(this.overlayListElement); return;
}
const customElementName = this.generateRandomCustomElementName();
globalThis.customElements?.define(
customElementName,
class extends HTMLElement {
constructor() {
super();
new AutofillOverlayListIframe(this);
}
},
);
this.overlayListElement = globalThis.document.createElement(customElementName);
} }
/** /**