diff --git a/apps/browser/src/autofill/overlay/iframe-content/autofill-overlay-button-iframe.spec.ts b/apps/browser/src/autofill/overlay/iframe-content/autofill-overlay-button-iframe.spec.ts index 8106f2698dc..5cba4e0c0e2 100644 --- a/apps/browser/src/autofill/overlay/iframe-content/autofill-overlay-button-iframe.spec.ts +++ b/apps/browser/src/autofill/overlay/iframe-content/autofill-overlay-button-iframe.spec.ts @@ -1,8 +1,15 @@ import AutofillOverlayButtonIframe from "./autofill-overlay-button-iframe"; -import AutofillOverlayIframeElement from "./autofill-overlay-iframe-element"; 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(() => { jest.clearAllMocks(); @@ -13,7 +20,7 @@ describe("AutofillOverlayButtonIframe", () => { const iframe = document.querySelector("autofill-overlay-button-iframe"); - expect(iframe).toBeInstanceOf(AutofillOverlayButtonIframe); - expect(iframe).toBeInstanceOf(AutofillOverlayIframeElement); + expect(iframe).toBeInstanceOf(HTMLElement); + expect(iframe.shadowRoot).toBeDefined(); }); }); diff --git a/apps/browser/src/autofill/overlay/iframe-content/autofill-overlay-button-iframe.ts b/apps/browser/src/autofill/overlay/iframe-content/autofill-overlay-button-iframe.ts index 813d8054704..4f5d64b3cb8 100644 --- a/apps/browser/src/autofill/overlay/iframe-content/autofill-overlay-button-iframe.ts +++ b/apps/browser/src/autofill/overlay/iframe-content/autofill-overlay-button-iframe.ts @@ -3,8 +3,9 @@ import { AutofillOverlayPort } from "../../utils/autofill-overlay.enum"; import AutofillOverlayIframeElement from "./autofill-overlay-iframe-element"; class AutofillOverlayButtonIframe extends AutofillOverlayIframeElement { - constructor() { + constructor(element: HTMLElement) { super( + element, "overlay/button.html", AutofillOverlayPort.Button, { diff --git a/apps/browser/src/autofill/overlay/iframe-content/autofill-overlay-iframe-element.spec.ts b/apps/browser/src/autofill/overlay/iframe-content/autofill-overlay-iframe-element.spec.ts index 7af5d973a92..71f7a290de1 100644 --- a/apps/browser/src/autofill/overlay/iframe-content/autofill-overlay-iframe-element.spec.ts +++ b/apps/browser/src/autofill/overlay/iframe-content/autofill-overlay-iframe-element.spec.ts @@ -4,7 +4,21 @@ import AutofillOverlayIframeService from "./autofill-overlay-iframe.service"; jest.mock("./autofill-overlay-iframe.service"); 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(() => { jest.clearAllMocks(); diff --git a/apps/browser/src/autofill/overlay/iframe-content/autofill-overlay-iframe-element.ts b/apps/browser/src/autofill/overlay/iframe-content/autofill-overlay-iframe-element.ts index 209834410f9..ed61c1eb8f1 100644 --- a/apps/browser/src/autofill/overlay/iframe-content/autofill-overlay-iframe-element.ts +++ b/apps/browser/src/autofill/overlay/iframe-content/autofill-overlay-iframe-element.ts @@ -1,16 +1,15 @@ import AutofillOverlayIframeService from "./autofill-overlay-iframe.service"; -class AutofillOverlayIframeElement extends HTMLElement { +class AutofillOverlayIframeElement { constructor( + element: HTMLElement, iframePath: string, portName: string, initStyles: Partial, iframeTitle: string, ariaAlert?: string, ) { - super(); - - const shadow: ShadowRoot = this.attachShadow({ mode: "closed" }); + const shadow: ShadowRoot = element.attachShadow({ mode: "closed" }); const autofillOverlayIframeService = new AutofillOverlayIframeService( iframePath, portName, diff --git a/apps/browser/src/autofill/overlay/iframe-content/autofill-overlay-list-iframe.spec.ts b/apps/browser/src/autofill/overlay/iframe-content/autofill-overlay-list-iframe.spec.ts index 5ba39eb0023..ec89697e75e 100644 --- a/apps/browser/src/autofill/overlay/iframe-content/autofill-overlay-list-iframe.spec.ts +++ b/apps/browser/src/autofill/overlay/iframe-content/autofill-overlay-list-iframe.spec.ts @@ -1,8 +1,15 @@ -import AutofillOverlayIframeElement from "./autofill-overlay-iframe-element"; import AutofillOverlayListIframe from "./autofill-overlay-list-iframe"; 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(() => { jest.clearAllMocks(); @@ -13,7 +20,7 @@ describe("AutofillOverlayListIframe", () => { const iframe = document.querySelector("autofill-overlay-list-iframe"); - expect(iframe).toBeInstanceOf(AutofillOverlayListIframe); - expect(iframe).toBeInstanceOf(AutofillOverlayIframeElement); + expect(iframe).toBeInstanceOf(HTMLElement); + expect(iframe.shadowRoot).toBeDefined(); }); }); diff --git a/apps/browser/src/autofill/overlay/iframe-content/autofill-overlay-list-iframe.ts b/apps/browser/src/autofill/overlay/iframe-content/autofill-overlay-list-iframe.ts index b60b618e4e7..23df6581546 100644 --- a/apps/browser/src/autofill/overlay/iframe-content/autofill-overlay-list-iframe.ts +++ b/apps/browser/src/autofill/overlay/iframe-content/autofill-overlay-list-iframe.ts @@ -3,8 +3,9 @@ import { AutofillOverlayPort } from "../../utils/autofill-overlay.enum"; import AutofillOverlayIframeElement from "./autofill-overlay-iframe-element"; class AutofillOverlayListIframe extends AutofillOverlayIframeElement { - constructor() { + constructor(element: HTMLElement) { super( + element, "overlay/list.html", AutofillOverlayPort.List, { diff --git a/apps/browser/src/autofill/services/autofill-overlay-content.service.spec.ts b/apps/browser/src/autofill/services/autofill-overlay-content.service.spec.ts index 8926f5b298e..9f3ffea142a 100644 --- a/apps/browser/src/autofill/services/autofill-overlay-content.service.spec.ts +++ b/apps/browser/src/autofill/services/autofill-overlay-content.service.spec.ts @@ -877,6 +877,44 @@ describe("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", () => { diff --git a/apps/browser/src/autofill/services/autofill-overlay-content.service.ts b/apps/browser/src/autofill/services/autofill-overlay-content.service.ts index 2cf063a5ba8..79abdc39381 100644 --- a/apps/browser/src/autofill/services/autofill-overlay-content.service.ts +++ b/apps/browser/src/autofill/services/autofill-overlay-content.service.ts @@ -30,6 +30,10 @@ class AutofillOverlayContentService implements AutofillOverlayContentServiceInte isOverlayCiphersPopulated = false; pageDetailsUpdateRequired = false; 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 sendExtensionMessage = sendExtensionMessage; private formFieldElements: Set> = new Set([]); @@ -593,6 +597,7 @@ class AutofillOverlayContentService implements AutofillOverlayContentServiceInte private updateOverlayButtonPosition() { if (!this.overlayButtonElement) { this.createAutofillOverlayButton(); + this.updateCustomElementDefaultStyles(this.overlayButtonElement); } if (!this.isOverlayButtonVisible) { @@ -613,6 +618,7 @@ class AutofillOverlayContentService implements AutofillOverlayContentServiceInte private updateOverlayListPosition() { if (!this.overlayListElement) { this.createAutofillOverlayList(); + this.updateCustomElementDefaultStyles(this.overlayListElement); } if (!this.isOverlayListVisible) { @@ -765,11 +771,24 @@ class AutofillOverlayContentService implements AutofillOverlayContentServiceInte return; } - const customElementName = generateRandomCustomElementName(); - globalThis.customElements?.define(customElementName, AutofillOverlayButtonIframe); - this.overlayButtonElement = globalThis.document.createElement(customElementName); + if (this.isFirefoxBrowser) { + this.overlayButtonElement = globalThis.document.createElement("div"); + 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; } - const customElementName = generateRandomCustomElementName(); - globalThis.customElements?.define(customElementName, AutofillOverlayListIframe); - this.overlayListElement = globalThis.document.createElement(customElementName); + if (this.isFirefoxBrowser) { + this.overlayListElement = globalThis.document.createElement("div"); + 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); } /**