diff --git a/apps/browser/src/autofill/fido2/background/fido2.background.spec.ts b/apps/browser/src/autofill/fido2/background/fido2.background.spec.ts index 43d07bdea1e..2a22a226e38 100644 --- a/apps/browser/src/autofill/fido2/background/fido2.background.spec.ts +++ b/apps/browser/src/autofill/fido2/background/fido2.background.spec.ts @@ -1,6 +1,7 @@ import { mock, MockProxy } from "jest-mock-extended"; import { BehaviorSubject } from "rxjs"; +import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { AssertCredentialParams, CreateCredentialParams, @@ -54,6 +55,7 @@ describe("Fido2Background", () => { let fido2ClientService!: MockProxy; let vaultSettingsService!: MockProxy; let scriptInjectorServiceMock!: MockProxy; + let configServiceMock!: MockProxy; let enablePasskeysMock$!: BehaviorSubject; let fido2Background!: Fido2Background; @@ -71,6 +73,7 @@ describe("Fido2Background", () => { abortController = mock(); registeredContentScripsMock = mock(); scriptInjectorServiceMock = mock(); + configServiceMock = mock(); enablePasskeysMock$ = new BehaviorSubject(true); vaultSettingsService.enablePasskeys$ = enablePasskeysMock$; @@ -80,6 +83,7 @@ describe("Fido2Background", () => { fido2ClientService, vaultSettingsService, scriptInjectorServiceMock, + configServiceMock, ); fido2Background["abortManager"] = abortManagerMock; abortManagerMock.runWithAbortController.mockImplementation((_requestId, runner) => @@ -110,6 +114,7 @@ describe("Fido2Background", () => { tabsQuerySpy.mockResolvedValueOnce([tabMock, secondTabMock, insecureTab, noUrlTab]); await fido2Background.injectFido2ContentScriptsInAllTabs(); + await flushPromises(); expect(scriptInjectorServiceMock.inject).toHaveBeenCalledWith({ tabId: tabMock.id, @@ -133,6 +138,7 @@ describe("Fido2Background", () => { tabsQuerySpy.mockResolvedValueOnce([tabMock]); await fido2Background.injectFido2ContentScriptsInAllTabs(); + await flushPromises(); expect(scriptInjectorServiceMock.inject).toHaveBeenCalledWith({ tabId: tabMock.id, @@ -206,6 +212,22 @@ describe("Fido2Background", () => { }); }); + it("registers the page-script-delay-append-mv2.js content script when the DelayFido2PageScriptInitWithinMv2 feature flag is enabled", async () => { + configServiceMock.getFeatureFlag.mockResolvedValue(true); + isManifestVersionSpy.mockImplementation((manifestVersion) => manifestVersion === 2); + + enablePasskeysMock$.next(true); + await flushPromises(); + + expect(BrowserApi.registerContentScriptsMv2).toHaveBeenCalledWith({ + js: [ + { file: Fido2ContentScript.PageScriptDelayAppend }, + { file: Fido2ContentScript.ContentScript }, + ], + ...sharedRegistrationOptions, + }); + }); + it("unregisters any existing registered content scripts when the enablePasskeys setting is set to `false`", async () => { isManifestVersionSpy.mockImplementation((manifestVersion) => manifestVersion === 2); fido2Background["registeredContentScripts"] = registeredContentScripsMock; diff --git a/apps/browser/src/autofill/fido2/background/fido2.background.ts b/apps/browser/src/autofill/fido2/background/fido2.background.ts index 1694602225b..800c7d4e0de 100644 --- a/apps/browser/src/autofill/fido2/background/fido2.background.ts +++ b/apps/browser/src/autofill/fido2/background/fido2.background.ts @@ -1,6 +1,8 @@ import { firstValueFrom, startWith } from "rxjs"; import { pairwise } from "rxjs/operators"; +import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; +import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { AssertCredentialParams, AssertCredentialResult, @@ -50,6 +52,7 @@ export class Fido2Background implements Fido2BackgroundInterface { private fido2ClientService: Fido2ClientService, private vaultSettingsService: VaultSettingsService, private scriptInjectorService: ScriptInjectorService, + private configService: ConfigService, ) {} /** @@ -132,7 +135,7 @@ export class Fido2Background implements Fido2BackgroundInterface { this.registeredContentScripts = await BrowserApi.registerContentScriptsMv2({ js: [ - { file: Fido2ContentScript.PageScriptAppend }, + { file: await this.getFido2PageScriptAppendFileName() }, { file: Fido2ContentScript.ContentScript }, ], ...this.sharedRegistrationOptions, @@ -176,7 +179,7 @@ export class Fido2Background implements Fido2BackgroundInterface { void this.scriptInjectorService.inject({ tabId: tab.id, injectDetails: { frame: "all_frames", ...this.sharedInjectionDetails }, - mv2Details: { file: Fido2ContentScript.PageScriptAppend }, + mv2Details: { file: await this.getFido2PageScriptAppendFileName() }, mv3Details: { file: Fido2ContentScript.PageScript, world: "MAIN" }, }); @@ -353,4 +356,20 @@ export class Fido2Background implements Fido2BackgroundInterface { this.fido2ContentScriptPortsSet.delete(port); }; + + /** + * Gets the file name of the page-script used within mv2. Will return the + * delayed append script if the associated feature flag is enabled. + */ + private async getFido2PageScriptAppendFileName() { + const shouldDelayInit = await this.configService.getFeatureFlag( + FeatureFlag.DelayFido2PageScriptInitWithinMv2, + ); + + if (shouldDelayInit) { + return Fido2ContentScript.PageScriptDelayAppend; + } + + return Fido2ContentScript.PageScriptAppend; + } } diff --git a/apps/browser/src/autofill/fido2/content/fido2-page-script-delay-append.mv2.ts b/apps/browser/src/autofill/fido2/content/fido2-page-script-delay-append.mv2.ts new file mode 100644 index 00000000000..4afeb76a0d3 --- /dev/null +++ b/apps/browser/src/autofill/fido2/content/fido2-page-script-delay-append.mv2.ts @@ -0,0 +1,27 @@ +/** + * This script handles injection of the FIDO2 override page script into the document. + * This is required for manifest v2, but will be removed when we migrate fully to manifest v3. + */ +import { Fido2ContentScript } from "../enums/fido2-content-script.enum"; + +(function (globalContext) { + if (globalContext.document.contentType !== "text/html") { + return; + } + + if (globalContext.document.readyState === "complete") { + loadScript(); + } else { + globalContext.addEventListener("DOMContentLoaded", loadScript); + } + + function loadScript() { + const script = globalContext.document.createElement("script"); + script.src = chrome.runtime.getURL(Fido2ContentScript.PageScript); + script.addEventListener("load", () => script.remove()); + + const scriptInsertionPoint = + globalContext.document.head || globalContext.document.documentElement; + scriptInsertionPoint.insertBefore(script, scriptInsertionPoint.firstChild); + } +})(globalThis); diff --git a/apps/browser/src/autofill/fido2/enums/fido2-content-script.enum.ts b/apps/browser/src/autofill/fido2/enums/fido2-content-script.enum.ts index 14e629b412e..9d9189c1623 100644 --- a/apps/browser/src/autofill/fido2/enums/fido2-content-script.enum.ts +++ b/apps/browser/src/autofill/fido2/enums/fido2-content-script.enum.ts @@ -1,6 +1,7 @@ export const Fido2ContentScript = { PageScript: "content/fido2-page-script.js", PageScriptAppend: "content/fido2-page-script-append-mv2.js", + PageScriptDelayAppend: "content/fido2-page-script-delay-append-mv2.js", ContentScript: "content/fido2-content-script.js", } as const; diff --git a/apps/browser/src/background/main.background.ts b/apps/browser/src/background/main.background.ts index d514c417efd..db3055b4c68 100644 --- a/apps/browser/src/background/main.background.ts +++ b/apps/browser/src/background/main.background.ts @@ -1025,6 +1025,7 @@ export default class MainBackground { this.fido2ClientService, this.vaultSettingsService, this.scriptInjectorService, + this.configService, ); this.runtimeBackground = new RuntimeBackground( this, diff --git a/apps/browser/webpack.config.js b/apps/browser/webpack.config.js index e6ef80bcd9e..36007f26f0c 100644 --- a/apps/browser/webpack.config.js +++ b/apps/browser/webpack.config.js @@ -301,6 +301,8 @@ if (manifestVersion == 2) { "./src/tools/content/lp-suppress-import-download-script-append.mv2.ts"; mainConfig.entry["content/fido2-page-script-append-mv2"] = "./src/autofill/fido2/content/fido2-page-script-append.mv2.ts"; + mainConfig.entry["content/fido2-page-script-delay-append-mv2"] = + "./src/autofill/fido2/content/fido2-page-script-delay-append.mv2.ts"; configs.push(mainConfig); } else { diff --git a/libs/common/src/enums/feature-flag.enum.ts b/libs/common/src/enums/feature-flag.enum.ts index 4b19251d979..3b2849e0f54 100644 --- a/libs/common/src/enums/feature-flag.enum.ts +++ b/libs/common/src/enums/feature-flag.enum.ts @@ -30,6 +30,7 @@ export enum FeatureFlag { UnauthenticatedExtensionUIRefresh = "unauth-ui-refresh", EnableUpgradePasswordManagerSub = "AC-2708-upgrade-password-manager-sub", GenerateIdentityFillScriptRefactor = "generate-identity-fill-script-refactor", + DelayFido2PageScriptInitWithinMv2 = "delay-fido2-page-script-init-within-mv2", } export type AllowedFeatureFlagTypes = boolean | number | string; @@ -70,6 +71,7 @@ export const DefaultFeatureFlagValue = { [FeatureFlag.UnauthenticatedExtensionUIRefresh]: FALSE, [FeatureFlag.EnableUpgradePasswordManagerSub]: FALSE, [FeatureFlag.GenerateIdentityFillScriptRefactor]: FALSE, + [FeatureFlag.DelayFido2PageScriptInitWithinMv2]: FALSE, } satisfies Record; export type DefaultFeatureFlagValueType = typeof DefaultFeatureFlagValue;