From 1efdcacd16d5d1fd9e0c853976c73491d40e839d Mon Sep 17 00:00:00 2001 From: Jonathan Prusik Date: Wed, 16 Apr 2025 13:15:43 -0400 Subject: [PATCH] [PM-16641] Remove "inline-menu-positioning-improvements" feature flag (#14225) * remove inline-menu-positioning-improvements flag * remove unused LegacyOverlayBackground * remove unused deprecated files * appease ts error TS2564 * remove deleted resources from the manifest files --- .../autofill/background/tabs.background.ts | 14 +- .../overlay.background.deprecated.ts | 124 -- .../overlay.background.deprecated.spec.ts | 1464 -------------- .../overlay.background.deprecated.ts | 811 -------- .../abstractions/autofill-init.deprecated.ts | 41 - .../content/autofill-init.deprecated.spec.ts | 604 ------ .../content/autofill-init.deprecated.ts | 315 --- .../bootstrap-legacy-autofill-overlay.ts | 14 - .../autofill-overlay-button.deprecated.ts | 29 - ...ofill-overlay-iframe.service.deprecated.ts | 33 - .../autofill-overlay-list.deprecated.ts | 31 - ...utofill-overlay-page-element.deprecated.ts | 13 - ...lay-iframe.service.deprecated.spec.ts.snap | 23 - ...l-overlay-button-iframe.deprecated.spec.ts | 26 - ...tofill-overlay-button-iframe.deprecated.ts | 21 - ...-overlay-iframe-element.deprecated.spec.ts | 46 - ...ofill-overlay-iframe-element.deprecated.ts | 22 - ...-overlay-iframe.service.deprecated.spec.ts | 521 ----- ...ofill-overlay-iframe.service.deprecated.ts | 429 ---- ...ill-overlay-list-iframe.deprecated.spec.ts | 26 - ...autofill-overlay-list-iframe.deprecated.ts | 26 - ...ill-overlay-button.deprecated.spec.ts.snap | 83 - ...autofill-overlay-button.deprecated.spec.ts | 135 -- .../autofill-overlay-button.deprecated.ts | 124 -- ...trap-autofill-overlay-button.deprecated.ts | 11 - .../overlay/pages/button/legacy-button.html | 12 - .../overlay/pages/button/legacy-button.scss | 36 - ...ofill-overlay-list.deprecated.spec.ts.snap | 537 ----- .../autofill-overlay-list.deprecated.spec.ts | 467 ----- .../list/autofill-overlay-list.deprecated.ts | 621 ------ ...tstrap-autofill-overlay-list.deprecated.ts | 11 - .../overlay/pages/list/legacy-list.html | 12 - .../overlay/pages/list/legacy-list.scss | 292 --- ...ll-overlay-page-element.deprecated.spec.ts | 222 --- ...utofill-overlay-page-element.deprecated.ts | 157 -- .../autofill-overlay-content.service.ts | 37 - ...overlay-content.service.deprecated.spec.ts | 1743 ----------------- ...fill-overlay-content.service.deprecated.ts | 1139 ----------- .../popup/settings/autofill.component.html | 10 +- .../popup/settings/autofill.component.ts | 17 +- .../src/autofill/services/autofill.service.ts | 9 - .../browser/src/background/main.background.ts | 56 +- apps/browser/src/manifest.json | 9 +- apps/browser/src/manifest.v3.json | 9 +- apps/browser/webpack.config.js | 16 - libs/common/src/enums/feature-flag.enum.ts | 2 - 46 files changed, 29 insertions(+), 10371 deletions(-) delete mode 100644 apps/browser/src/autofill/deprecated/background/abstractions/overlay.background.deprecated.ts delete mode 100644 apps/browser/src/autofill/deprecated/background/overlay.background.deprecated.spec.ts delete mode 100644 apps/browser/src/autofill/deprecated/background/overlay.background.deprecated.ts delete mode 100644 apps/browser/src/autofill/deprecated/content/abstractions/autofill-init.deprecated.ts delete mode 100644 apps/browser/src/autofill/deprecated/content/autofill-init.deprecated.spec.ts delete mode 100644 apps/browser/src/autofill/deprecated/content/autofill-init.deprecated.ts delete mode 100644 apps/browser/src/autofill/deprecated/content/bootstrap-legacy-autofill-overlay.ts delete mode 100644 apps/browser/src/autofill/deprecated/overlay/abstractions/autofill-overlay-button.deprecated.ts delete mode 100644 apps/browser/src/autofill/deprecated/overlay/abstractions/autofill-overlay-iframe.service.deprecated.ts delete mode 100644 apps/browser/src/autofill/deprecated/overlay/abstractions/autofill-overlay-list.deprecated.ts delete mode 100644 apps/browser/src/autofill/deprecated/overlay/abstractions/autofill-overlay-page-element.deprecated.ts delete mode 100644 apps/browser/src/autofill/deprecated/overlay/iframe-content/__snapshots__/autofill-overlay-iframe.service.deprecated.spec.ts.snap delete mode 100644 apps/browser/src/autofill/deprecated/overlay/iframe-content/autofill-overlay-button-iframe.deprecated.spec.ts delete mode 100644 apps/browser/src/autofill/deprecated/overlay/iframe-content/autofill-overlay-button-iframe.deprecated.ts delete mode 100644 apps/browser/src/autofill/deprecated/overlay/iframe-content/autofill-overlay-iframe-element.deprecated.spec.ts delete mode 100644 apps/browser/src/autofill/deprecated/overlay/iframe-content/autofill-overlay-iframe-element.deprecated.ts delete mode 100644 apps/browser/src/autofill/deprecated/overlay/iframe-content/autofill-overlay-iframe.service.deprecated.spec.ts delete mode 100644 apps/browser/src/autofill/deprecated/overlay/iframe-content/autofill-overlay-iframe.service.deprecated.ts delete mode 100644 apps/browser/src/autofill/deprecated/overlay/iframe-content/autofill-overlay-list-iframe.deprecated.spec.ts delete mode 100644 apps/browser/src/autofill/deprecated/overlay/iframe-content/autofill-overlay-list-iframe.deprecated.ts delete mode 100644 apps/browser/src/autofill/deprecated/overlay/pages/button/__snapshots__/autofill-overlay-button.deprecated.spec.ts.snap delete mode 100644 apps/browser/src/autofill/deprecated/overlay/pages/button/autofill-overlay-button.deprecated.spec.ts delete mode 100644 apps/browser/src/autofill/deprecated/overlay/pages/button/autofill-overlay-button.deprecated.ts delete mode 100644 apps/browser/src/autofill/deprecated/overlay/pages/button/bootstrap-autofill-overlay-button.deprecated.ts delete mode 100644 apps/browser/src/autofill/deprecated/overlay/pages/button/legacy-button.html delete mode 100644 apps/browser/src/autofill/deprecated/overlay/pages/button/legacy-button.scss delete mode 100644 apps/browser/src/autofill/deprecated/overlay/pages/list/__snapshots__/autofill-overlay-list.deprecated.spec.ts.snap delete mode 100644 apps/browser/src/autofill/deprecated/overlay/pages/list/autofill-overlay-list.deprecated.spec.ts delete mode 100644 apps/browser/src/autofill/deprecated/overlay/pages/list/autofill-overlay-list.deprecated.ts delete mode 100644 apps/browser/src/autofill/deprecated/overlay/pages/list/bootstrap-autofill-overlay-list.deprecated.ts delete mode 100644 apps/browser/src/autofill/deprecated/overlay/pages/list/legacy-list.html delete mode 100644 apps/browser/src/autofill/deprecated/overlay/pages/list/legacy-list.scss delete mode 100644 apps/browser/src/autofill/deprecated/overlay/pages/shared/autofill-overlay-page-element.deprecated.spec.ts delete mode 100644 apps/browser/src/autofill/deprecated/overlay/pages/shared/autofill-overlay-page-element.deprecated.ts delete mode 100644 apps/browser/src/autofill/deprecated/services/abstractions/autofill-overlay-content.service.ts delete mode 100644 apps/browser/src/autofill/deprecated/services/autofill-overlay-content.service.deprecated.spec.ts delete mode 100644 apps/browser/src/autofill/deprecated/services/autofill-overlay-content.service.deprecated.ts diff --git a/apps/browser/src/autofill/background/tabs.background.ts b/apps/browser/src/autofill/background/tabs.background.ts index b07e06234d3..c093f1a3b00 100644 --- a/apps/browser/src/autofill/background/tabs.background.ts +++ b/apps/browser/src/autofill/background/tabs.background.ts @@ -1,7 +1,3 @@ -// FIXME: Update this file to be type safe and remove this and next line -// @ts-strict-ignore -import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; - import MainBackground from "../../background/main.background"; import { OverlayBackground } from "./abstractions/overlay.background"; @@ -14,7 +10,7 @@ export default class TabsBackground { private overlayBackground: OverlayBackground, ) {} - private focusedWindowId: number; + private focusedWindowId: number = -1; /** * Initializes the window and tab listeners. @@ -90,14 +86,6 @@ export default class TabsBackground { changeInfo: chrome.tabs.TabChangeInfo, tab: chrome.tabs.Tab, ) => { - const overlayImprovementsFlag = await this.main.configService.getFeatureFlag( - FeatureFlag.InlineMenuPositioningImprovements, - ); - const removePageDetailsStatus = new Set(["loading", "unloaded"]); - if (!overlayImprovementsFlag && removePageDetailsStatus.has(changeInfo.status)) { - this.overlayBackground.removePageDetails(tabId); - } - if (this.focusedWindowId > 0 && tab.windowId !== this.focusedWindowId) { return; } diff --git a/apps/browser/src/autofill/deprecated/background/abstractions/overlay.background.deprecated.ts b/apps/browser/src/autofill/deprecated/background/abstractions/overlay.background.deprecated.ts deleted file mode 100644 index 88b78dc2495..00000000000 --- a/apps/browser/src/autofill/deprecated/background/abstractions/overlay.background.deprecated.ts +++ /dev/null @@ -1,124 +0,0 @@ -import { CipherType } from "@bitwarden/common/vault/enums"; -import { CipherRepromptType } from "@bitwarden/common/vault/enums/cipher-reprompt-type"; - -import { LockedVaultPendingNotificationsData } from "../../../background/abstractions/notification.background"; -import AutofillPageDetails from "../../../models/autofill-page-details"; - -type WebsiteIconData = { - imageEnabled: boolean; - image: string; - fallbackImage: string; - icon: string; -}; - -type OverlayAddNewItemMessage = { - login?: { - uri?: string; - hostname: string; - username: string; - password: string; - }; -}; - -type OverlayBackgroundExtensionMessage = { - [key: string]: any; - command: string; - tab?: chrome.tabs.Tab; - sender?: string; - details?: AutofillPageDetails; - overlayElement?: string; - display?: string; - data?: LockedVaultPendingNotificationsData; -} & OverlayAddNewItemMessage; - -type OverlayPortMessage = { - [key: string]: any; - command: string; - direction?: string; - overlayCipherId?: string; -}; - -type FocusedFieldData = { - focusedFieldStyles: Partial; - focusedFieldRects: Partial; - tabId?: number; -}; - -type OverlayCipherData = { - id: string; - name: string; - type: CipherType; - reprompt: CipherRepromptType; - favorite: boolean; - icon: { imageEnabled: boolean; image: string; fallbackImage: string; icon: string }; - login?: { username: string }; - card?: string; -}; - -type BackgroundMessageParam = { - message: OverlayBackgroundExtensionMessage; -}; -type BackgroundSenderParam = { - sender: chrome.runtime.MessageSender; -}; -type BackgroundOnMessageHandlerParams = BackgroundMessageParam & BackgroundSenderParam; - -type OverlayBackgroundExtensionMessageHandlers = { - [key: string]: CallableFunction; - openAutofillOverlay: () => void; - autofillOverlayElementClosed: ({ message, sender }: BackgroundOnMessageHandlerParams) => void; - autofillOverlayAddNewVaultItem: ({ message, sender }: BackgroundOnMessageHandlerParams) => void; - getAutofillOverlayVisibility: () => void; - checkAutofillOverlayFocused: () => void; - focusAutofillOverlayList: () => void; - updateAutofillOverlayPosition: ({ message, sender }: BackgroundOnMessageHandlerParams) => void; - updateAutofillOverlayHidden: ({ message }: BackgroundMessageParam) => void; - updateFocusedFieldData: ({ message, sender }: BackgroundOnMessageHandlerParams) => void; - collectPageDetailsResponse: ({ message, sender }: BackgroundOnMessageHandlerParams) => void; - unlockCompleted: ({ message }: BackgroundMessageParam) => void; - addedCipher: () => void; - addEditCipherSubmitted: () => void; - editedCipher: () => void; - deletedCipher: () => void; -}; - -type PortMessageParam = { - message: OverlayPortMessage; -}; -type PortConnectionParam = { - port: chrome.runtime.Port; -}; -type PortOnMessageHandlerParams = PortMessageParam & PortConnectionParam; - -type OverlayButtonPortMessageHandlers = { - [key: string]: CallableFunction; - overlayButtonClicked: ({ port }: PortConnectionParam) => void; - closeAutofillOverlay: ({ port }: PortConnectionParam) => void; - forceCloseAutofillOverlay: ({ port }: PortConnectionParam) => void; - overlayPageBlurred: () => void; - redirectOverlayFocusOut: ({ message, port }: PortOnMessageHandlerParams) => void; -}; - -type OverlayListPortMessageHandlers = { - [key: string]: CallableFunction; - checkAutofillOverlayButtonFocused: () => void; - forceCloseAutofillOverlay: ({ port }: PortConnectionParam) => void; - overlayPageBlurred: () => void; - unlockVault: ({ port }: PortConnectionParam) => void; - fillSelectedListItem: ({ message, port }: PortOnMessageHandlerParams) => void; - addNewVaultItem: ({ port }: PortConnectionParam) => void; - viewSelectedCipher: ({ message, port }: PortOnMessageHandlerParams) => void; - redirectOverlayFocusOut: ({ message, port }: PortOnMessageHandlerParams) => void; -}; - -export { - WebsiteIconData, - OverlayBackgroundExtensionMessage, - OverlayPortMessage, - FocusedFieldData, - OverlayCipherData, - OverlayAddNewItemMessage, - OverlayBackgroundExtensionMessageHandlers, - OverlayButtonPortMessageHandlers, - OverlayListPortMessageHandlers, -}; diff --git a/apps/browser/src/autofill/deprecated/background/overlay.background.deprecated.spec.ts b/apps/browser/src/autofill/deprecated/background/overlay.background.deprecated.spec.ts deleted file mode 100644 index 68f8032350e..00000000000 --- a/apps/browser/src/autofill/deprecated/background/overlay.background.deprecated.spec.ts +++ /dev/null @@ -1,1464 +0,0 @@ -import { mock, MockProxy, mockReset } from "jest-mock-extended"; -import { BehaviorSubject, of } from "rxjs"; - -import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status"; -import { AuthService } from "@bitwarden/common/auth/services/auth.service"; -import { - SHOW_AUTOFILL_BUTTON, - AutofillOverlayVisibility, -} from "@bitwarden/common/autofill/constants"; -import { AutofillSettingsService } from "@bitwarden/common/autofill/services/autofill-settings.service"; -import { - DefaultDomainSettingsService, - DomainSettingsService, -} from "@bitwarden/common/autofill/services/domain-settings.service"; -import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; -import { - EnvironmentService, - Region, -} from "@bitwarden/common/platform/abstractions/environment.service"; -import { ThemeType } from "@bitwarden/common/platform/enums"; -import { Utils } from "@bitwarden/common/platform/misc/utils"; -import { CloudEnvironment } from "@bitwarden/common/platform/services/default-environment.service"; -import { I18nService } from "@bitwarden/common/platform/services/i18n.service"; -import { ThemeStateService } from "@bitwarden/common/platform/theming/theme-state.service"; -import { - FakeStateProvider, - FakeAccountService, - mockAccountServiceWith, -} from "@bitwarden/common/spec"; -import { UserId } from "@bitwarden/common/types/guid"; -import { CipherType } from "@bitwarden/common/vault/enums"; -import { CipherRepromptType } from "@bitwarden/common/vault/enums/cipher-reprompt-type"; -import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; -import { CipherService } from "@bitwarden/common/vault/services/cipher.service"; - -import { BrowserApi } from "../../../platform/browser/browser-api"; -import { BrowserPlatformUtilsService } from "../../../platform/services/platform-utils/browser-platform-utils.service"; -import { - AutofillOverlayElement, - AutofillOverlayPort, - RedirectFocusDirection, -} from "../../enums/autofill-overlay.enum"; -import { AutofillService } from "../../services/abstractions/autofill.service"; -import { - createAutofillPageDetailsMock, - createChromeTabMock, - createFocusedFieldDataMock, - createPageDetailMock, - createPortSpyMock, -} from "../../spec/autofill-mocks"; -import { flushPromises, sendMockExtensionMessage, sendPortMessage } from "../../spec/testing-utils"; - -import LegacyOverlayBackground from "./overlay.background.deprecated"; - -describe("OverlayBackground", () => { - const mockUserId = Utils.newGuid() as UserId; - const accountService: FakeAccountService = mockAccountServiceWith(mockUserId); - const fakeStateProvider: FakeStateProvider = new FakeStateProvider(accountService); - let domainSettingsService: DomainSettingsService; - let buttonPortSpy: chrome.runtime.Port; - let listPortSpy: chrome.runtime.Port; - let overlayBackground: LegacyOverlayBackground; - const cipherService = mock(); - const autofillService = mock(); - let configService: MockProxy; - let activeAccountStatusMock$: BehaviorSubject; - let authService: MockProxy; - - const environmentService = mock(); - environmentService.environment$ = new BehaviorSubject( - new CloudEnvironment({ - key: Region.US, - domain: "bitwarden.com", - urls: { icons: "https://icons.bitwarden.com/" }, - }), - ); - const autofillSettingsService = mock(); - const i18nService = mock(); - const platformUtilsService = mock(); - const themeStateService = mock(); - const initOverlayElementPorts = async (options = { initList: true, initButton: true }) => { - const { initList, initButton } = options; - if (initButton) { - await overlayBackground["handlePortOnConnect"](createPortSpyMock(AutofillOverlayPort.Button)); - buttonPortSpy = overlayBackground["overlayButtonPort"]; - } - - if (initList) { - await overlayBackground["handlePortOnConnect"](createPortSpyMock(AutofillOverlayPort.List)); - listPortSpy = overlayBackground["overlayListPort"]; - } - - return { buttonPortSpy, listPortSpy }; - }; - - beforeEach(() => { - configService = mock(); - configService.getFeatureFlag$.mockImplementation(() => of(true)); - domainSettingsService = new DefaultDomainSettingsService(fakeStateProvider, configService); - activeAccountStatusMock$ = new BehaviorSubject(AuthenticationStatus.Unlocked); - authService = mock(); - authService.activeAccountStatus$ = activeAccountStatusMock$; - overlayBackground = new LegacyOverlayBackground( - cipherService, - autofillService, - authService, - environmentService, - domainSettingsService, - autofillSettingsService, - i18nService, - platformUtilsService, - themeStateService, - accountService, - ); - - jest - .spyOn(overlayBackground as any, "getOverlayVisibility") - .mockResolvedValue(AutofillOverlayVisibility.OnFieldFocus); - - themeStateService.selectedTheme$ = of(ThemeType.Light); - domainSettingsService.showFavicons$ = of(true); - - void overlayBackground.init(); - }); - - afterEach(() => { - jest.clearAllMocks(); - mockReset(cipherService); - }); - - describe("removePageDetails", () => { - it("removes the page details for a specific tab from the pageDetailsForTab object", () => { - const tabId = 1; - const frameId = 2; - overlayBackground["pageDetailsForTab"][tabId] = new Map([[frameId, createPageDetailMock()]]); - overlayBackground.removePageDetails(tabId); - - expect(overlayBackground["pageDetailsForTab"][tabId]).toBeUndefined(); - }); - }); - - describe("init", () => { - it("sets up the extension message listeners, get the overlay's visibility settings, and get the user's auth status", async () => { - overlayBackground["setupExtensionMessageListeners"] = jest.fn(); - overlayBackground["getOverlayVisibility"] = jest.fn(); - overlayBackground["getAuthStatus"] = jest.fn(); - - await overlayBackground.init(); - - expect(overlayBackground["setupExtensionMessageListeners"]).toHaveBeenCalled(); - expect(overlayBackground["getOverlayVisibility"]).toHaveBeenCalled(); - expect(overlayBackground["getAuthStatus"]).toHaveBeenCalled(); - }); - }); - - describe("updateOverlayCiphers", () => { - const url = "https://jest-testing-website.com"; - const tab = createChromeTabMock({ url }); - const cipher1 = mock({ - id: "id-1", - localData: { lastUsedDate: 222 }, - name: "name-1", - type: CipherType.Login, - login: { username: "username-1", uri: url }, - }); - const cipher2 = mock({ - id: "id-2", - localData: { lastUsedDate: 111 }, - name: "name-2", - type: CipherType.Login, - login: { username: "username-2", uri: url }, - }); - - beforeEach(() => { - activeAccountStatusMock$.next(AuthenticationStatus.Unlocked); - }); - - it("ignores updating the overlay ciphers if the user's auth status is not unlocked", async () => { - activeAccountStatusMock$.next(AuthenticationStatus.Locked); - jest.spyOn(BrowserApi, "getTabFromCurrentWindowId"); - jest.spyOn(cipherService, "getAllDecryptedForUrl"); - - await overlayBackground.updateOverlayCiphers(); - - expect(BrowserApi.getTabFromCurrentWindowId).not.toHaveBeenCalled(); - expect(cipherService.getAllDecryptedForUrl).not.toHaveBeenCalled(); - }); - - it("ignores updating the overlay ciphers if the tab is undefined", async () => { - jest.spyOn(BrowserApi, "getTabFromCurrentWindowId").mockResolvedValueOnce(undefined); - jest.spyOn(cipherService, "getAllDecryptedForUrl"); - - await overlayBackground.updateOverlayCiphers(); - - expect(BrowserApi.getTabFromCurrentWindowId).toHaveBeenCalled(); - expect(cipherService.getAllDecryptedForUrl).not.toHaveBeenCalled(); - }); - - it("queries all ciphers for the given url, sort them by last used, and format them for usage in the overlay", async () => { - jest.spyOn(BrowserApi, "getTabFromCurrentWindowId").mockResolvedValueOnce(tab); - cipherService.getAllDecryptedForUrl.mockResolvedValue([cipher1, cipher2]); - cipherService.sortCiphersByLastUsedThenName.mockReturnValue(-1); - jest.spyOn(BrowserApi, "tabSendMessageData").mockImplementation(); - jest.spyOn(overlayBackground as any, "getOverlayCipherData"); - - await overlayBackground.updateOverlayCiphers(); - - expect(BrowserApi.getTabFromCurrentWindowId).toHaveBeenCalled(); - expect(cipherService.getAllDecryptedForUrl).toHaveBeenCalledWith(url, mockUserId); - expect(overlayBackground["cipherService"].sortCiphersByLastUsedThenName).toHaveBeenCalled(); - expect(overlayBackground["overlayLoginCiphers"]).toStrictEqual( - new Map([ - ["overlay-cipher-0", cipher2], - ["overlay-cipher-1", cipher1], - ]), - ); - expect(overlayBackground["getOverlayCipherData"]).toHaveBeenCalled(); - }); - - it("posts an `updateOverlayListCiphers` message to the overlay list port, and send a `updateIsOverlayCiphersPopulated` message to the tab indicating that the list of ciphers is populated", async () => { - overlayBackground["overlayListPort"] = mock(); - cipherService.getAllDecryptedForUrl.mockResolvedValue([cipher1, cipher2]); - cipherService.sortCiphersByLastUsedThenName.mockReturnValue(-1); - jest.spyOn(BrowserApi, "getTabFromCurrentWindowId").mockResolvedValueOnce(tab); - jest.spyOn(BrowserApi, "tabSendMessageData").mockImplementation(); - - await overlayBackground.updateOverlayCiphers(); - - expect(overlayBackground["overlayListPort"].postMessage).toHaveBeenCalledWith({ - command: "updateOverlayListCiphers", - ciphers: [ - { - card: null, - favorite: cipher2.favorite, - icon: { - fallbackImage: "images/bwi-globe.png", - icon: "bwi-globe", - image: "https://icons.bitwarden.com//jest-testing-website.com/icon.png", - imageEnabled: true, - }, - id: "overlay-cipher-0", - login: { - username: "username-2", - }, - name: "name-2", - reprompt: cipher2.reprompt, - type: 1, - }, - { - card: null, - favorite: cipher1.favorite, - icon: { - fallbackImage: "images/bwi-globe.png", - icon: "bwi-globe", - image: "https://icons.bitwarden.com//jest-testing-website.com/icon.png", - imageEnabled: true, - }, - id: "overlay-cipher-1", - login: { - username: "username-1", - }, - name: "name-1", - reprompt: cipher1.reprompt, - type: 1, - }, - ], - }); - expect(BrowserApi.tabSendMessageData).toHaveBeenCalledWith( - tab, - "updateIsOverlayCiphersPopulated", - { isOverlayCiphersPopulated: true }, - ); - }); - }); - - describe("getOverlayCipherData", () => { - const url = "https://jest-testing-website.com"; - const cipher1 = mock({ - id: "id-1", - localData: { lastUsedDate: 222 }, - name: "name-1", - type: CipherType.Login, - login: { username: "username-1", uri: url }, - }); - const cipher2 = mock({ - id: "id-2", - localData: { lastUsedDate: 111 }, - name: "name-2", - type: CipherType.Login, - login: { username: "username-2", uri: url }, - }); - const cipher3 = mock({ - id: "id-3", - localData: { lastUsedDate: 333 }, - name: "name-3", - type: CipherType.Card, - card: { subTitle: "Visa, *6789" }, - }); - const cipher4 = mock({ - id: "id-4", - localData: { lastUsedDate: 444 }, - name: "name-4", - type: CipherType.Card, - card: { subTitle: "Mastercard, *1234" }, - }); - - it("formats and returns the cipher data", async () => { - overlayBackground["overlayLoginCiphers"] = new Map([ - ["overlay-cipher-0", cipher2], - ["overlay-cipher-1", cipher1], - ["overlay-cipher-2", cipher3], - ["overlay-cipher-3", cipher4], - ]); - - const overlayCipherData = await overlayBackground["getOverlayCipherData"](); - - expect(overlayCipherData).toStrictEqual([ - { - card: null, - favorite: cipher2.favorite, - icon: { - fallbackImage: "images/bwi-globe.png", - icon: "bwi-globe", - image: "https://icons.bitwarden.com//jest-testing-website.com/icon.png", - imageEnabled: true, - }, - id: "overlay-cipher-0", - login: { - username: "username-2", - }, - name: "name-2", - reprompt: cipher2.reprompt, - type: 1, - }, - { - card: null, - favorite: cipher1.favorite, - icon: { - fallbackImage: "images/bwi-globe.png", - icon: "bwi-globe", - image: "https://icons.bitwarden.com//jest-testing-website.com/icon.png", - imageEnabled: true, - }, - id: "overlay-cipher-1", - login: { - username: "username-1", - }, - name: "name-1", - reprompt: cipher1.reprompt, - type: 1, - }, - { - card: "Visa, *6789", - favorite: cipher3.favorite, - icon: { - fallbackImage: "", - icon: "bwi-credit-card", - image: null, - imageEnabled: true, - }, - id: "overlay-cipher-2", - login: null, - name: "name-3", - reprompt: cipher3.reprompt, - type: 3, - }, - { - card: "Mastercard, *1234", - favorite: cipher4.favorite, - icon: { - fallbackImage: "", - icon: "bwi-credit-card", - image: null, - imageEnabled: true, - }, - id: "overlay-cipher-3", - login: null, - name: "name-4", - reprompt: cipher4.reprompt, - type: 3, - }, - ]); - }); - }); - - describe("getAuthStatus", () => { - it("will update the user's auth status but will not update the overlay ciphers", async () => { - const authStatus = AuthenticationStatus.Unlocked; - overlayBackground["userAuthStatus"] = AuthenticationStatus.Unlocked; - jest.spyOn(overlayBackground["authService"], "getAuthStatus").mockResolvedValue(authStatus); - jest.spyOn(overlayBackground as any, "updateOverlayButtonAuthStatus").mockImplementation(); - jest.spyOn(overlayBackground as any, "updateOverlayCiphers").mockImplementation(); - - const status = await overlayBackground["getAuthStatus"](); - - expect(overlayBackground["authService"].getAuthStatus).toHaveBeenCalled(); - expect(overlayBackground["updateOverlayButtonAuthStatus"]).not.toHaveBeenCalled(); - expect(overlayBackground["updateOverlayCiphers"]).not.toHaveBeenCalled(); - expect(overlayBackground["userAuthStatus"]).toBe(authStatus); - expect(status).toBe(authStatus); - }); - - it("will update the user's auth status and update the overlay ciphers if the status has been modified", async () => { - const authStatus = AuthenticationStatus.Unlocked; - overlayBackground["userAuthStatus"] = AuthenticationStatus.LoggedOut; - jest.spyOn(overlayBackground["authService"], "getAuthStatus").mockResolvedValue(authStatus); - jest.spyOn(overlayBackground as any, "updateOverlayButtonAuthStatus").mockImplementation(); - jest.spyOn(overlayBackground as any, "updateOverlayCiphers").mockImplementation(); - - await overlayBackground["getAuthStatus"](); - - expect(overlayBackground["authService"].getAuthStatus).toHaveBeenCalled(); - expect(overlayBackground["updateOverlayButtonAuthStatus"]).toHaveBeenCalled(); - expect(overlayBackground["updateOverlayCiphers"]).toHaveBeenCalled(); - expect(overlayBackground["userAuthStatus"]).toBe(authStatus); - }); - }); - - describe("updateOverlayButtonAuthStatus", () => { - it("will send a message to the button port with the user's auth status", () => { - overlayBackground["overlayButtonPort"] = mock(); - jest.spyOn(overlayBackground["overlayButtonPort"], "postMessage"); - - overlayBackground["updateOverlayButtonAuthStatus"](); - - expect(overlayBackground["overlayButtonPort"].postMessage).toHaveBeenCalledWith({ - command: "updateOverlayButtonAuthStatus", - authStatus: overlayBackground["userAuthStatus"], - }); - }); - }); - - describe("getTranslations", () => { - it("will query the overlay page translations if they have not been queried", () => { - overlayBackground["overlayPageTranslations"] = undefined; - jest.spyOn(overlayBackground as any, "getTranslations"); - jest.spyOn(overlayBackground["i18nService"], "translate").mockImplementation((key) => key); - jest.spyOn(BrowserApi, "getUILanguage").mockReturnValue("en"); - - const translations = overlayBackground["getTranslations"](); - - expect(overlayBackground["getTranslations"]).toHaveBeenCalled(); - const translationKeys = [ - "opensInANewWindow", - "bitwardenOverlayButton", - "toggleBitwardenVaultOverlay", - "bitwardenVault", - "unlockYourAccountToViewMatchingLogins", - "unlockAccount", - "fillCredentialsFor", - "partialUsername", - "view", - "noItemsToShow", - "newItem", - "addNewVaultItem", - ]; - translationKeys.forEach((key) => { - expect(overlayBackground["i18nService"].translate).toHaveBeenCalledWith(key); - }); - expect(translations).toStrictEqual({ - locale: "en", - opensInANewWindow: "opensInANewWindow", - buttonPageTitle: "bitwardenOverlayButton", - toggleBitwardenVaultOverlay: "toggleBitwardenVaultOverlay", - listPageTitle: "bitwardenVault", - unlockYourAccount: "unlockYourAccountToViewMatchingLogins", - unlockAccount: "unlockAccount", - fillCredentialsFor: "fillCredentialsFor", - partialUsername: "partialUsername", - view: "view", - noItemsToShow: "noItemsToShow", - newItem: "newItem", - addNewVaultItem: "addNewVaultItem", - }); - }); - }); - - describe("setupExtensionMessageListeners", () => { - it("will set up onMessage and onConnect listeners", () => { - overlayBackground["setupExtensionMessageListeners"](); - - expect(chrome.runtime.onMessage.addListener).toHaveBeenCalled(); - expect(chrome.runtime.onConnect.addListener).toHaveBeenCalled(); - }); - }); - - describe("handleExtensionMessage", () => { - it("will return early if the message command is not present within the extensionMessageHandlers", () => { - const message = { - command: "not-a-command", - }; - const sender = mock({ tab: { id: 1 } }); - const sendResponse = jest.fn(); - - const returnValue = overlayBackground["handleExtensionMessage"]( - message, - sender, - sendResponse, - ); - - expect(returnValue).toBe(null); - expect(sendResponse).not.toHaveBeenCalled(); - }); - - it("will trigger the message handler and return undefined if the message does not have a response", () => { - const message = { - command: "autofillOverlayElementClosed", - }; - const sender = mock({ tab: { id: 1 } }); - const sendResponse = jest.fn(); - jest.spyOn(overlayBackground as any, "overlayElementClosed"); - - const returnValue = overlayBackground["handleExtensionMessage"]( - message, - sender, - sendResponse, - ); - - expect(returnValue).toBe(null); - expect(sendResponse).not.toHaveBeenCalled(); - expect(overlayBackground["overlayElementClosed"]).toHaveBeenCalledWith(message, sender); - }); - - it("will return a response if the message handler returns a response", async () => { - const message = { - command: "openAutofillOverlay", - }; - const sender = mock({ tab: { id: 1 } }); - const sendResponse = jest.fn(); - jest.spyOn(overlayBackground as any, "getTranslations").mockReturnValue("translations"); - - const returnValue = overlayBackground["handleExtensionMessage"]( - message, - sender, - sendResponse, - ); - - expect(returnValue).toBe(true); - }); - - describe("extension message handlers", () => { - beforeEach(() => { - jest - .spyOn(overlayBackground as any, "getAuthStatus") - .mockResolvedValue(AuthenticationStatus.Unlocked); - }); - - describe("openAutofillOverlay message handler", () => { - it("opens the autofill overlay by sending a message to the current tab", async () => { - const sender = mock({ tab: { id: 1 } }); - jest.spyOn(BrowserApi, "getTabFromCurrentWindowId").mockResolvedValueOnce(sender.tab); - jest.spyOn(BrowserApi, "tabSendMessageData").mockImplementation(); - - sendMockExtensionMessage({ command: "openAutofillOverlay" }); - await flushPromises(); - - expect(BrowserApi.tabSendMessageData).toHaveBeenCalledWith( - sender.tab, - "openAutofillOverlay", - { - isFocusingFieldElement: false, - isOpeningFullOverlay: false, - authStatus: AuthenticationStatus.Unlocked, - }, - ); - }); - }); - - describe("autofillOverlayElementClosed message handler", () => { - beforeEach(async () => { - await initOverlayElementPorts(); - }); - - it("disconnects any expired ports if the sender is not from the same page as the most recently focused field", () => { - const port1 = mock(); - const port2 = mock(); - overlayBackground["expiredPorts"] = [port1, port2]; - const sender = mock({ tab: { id: 1 } }); - const focusedFieldData = createFocusedFieldDataMock({ tabId: 2 }); - sendMockExtensionMessage({ command: "updateFocusedFieldData", focusedFieldData }); - - sendMockExtensionMessage( - { - command: "autofillOverlayElementClosed", - overlayElement: AutofillOverlayElement.Button, - }, - sender, - ); - - expect(port1.disconnect).toHaveBeenCalled(); - expect(port2.disconnect).toHaveBeenCalled(); - }); - - it("disconnects the button element port", () => { - sendMockExtensionMessage({ - command: "autofillOverlayElementClosed", - overlayElement: AutofillOverlayElement.Button, - }); - - expect(buttonPortSpy.disconnect).toHaveBeenCalled(); - expect(overlayBackground["overlayButtonPort"]).toBeNull(); - }); - - it("disconnects the list element port", () => { - sendMockExtensionMessage({ - command: "autofillOverlayElementClosed", - overlayElement: AutofillOverlayElement.List, - }); - - expect(listPortSpy.disconnect).toHaveBeenCalled(); - expect(overlayBackground["overlayListPort"]).toBeNull(); - }); - }); - - describe("autofillOverlayAddNewVaultItem message handler", () => { - let sender: chrome.runtime.MessageSender; - beforeEach(() => { - sender = mock({ tab: { id: 1 } }); - jest - .spyOn(overlayBackground["cipherService"], "setAddEditCipherInfo") - .mockImplementation(); - jest.spyOn(overlayBackground as any, "openAddEditVaultItemPopout").mockImplementation(); - }); - - it("will not open the add edit popout window if the message does not have a login cipher provided", () => { - sendMockExtensionMessage({ command: "autofillOverlayAddNewVaultItem" }, sender); - - expect(overlayBackground["cipherService"].setAddEditCipherInfo).not.toHaveBeenCalled(); - expect(overlayBackground["openAddEditVaultItemPopout"]).not.toHaveBeenCalled(); - }); - - it("will open the add edit popout window after creating a new cipher", async () => { - jest.spyOn(BrowserApi, "sendMessage"); - - sendMockExtensionMessage( - { - command: "autofillOverlayAddNewVaultItem", - login: { - uri: "https://tacos.com", - hostname: "", - username: "username", - password: "password", - }, - }, - sender, - ); - await flushPromises(); - - expect(overlayBackground["cipherService"].setAddEditCipherInfo).toHaveBeenCalled(); - expect(overlayBackground["openAddEditVaultItemPopout"]).toHaveBeenCalled(); - }); - }); - - describe("getAutofillOverlayVisibility message handler", () => { - beforeEach(() => { - jest - .spyOn(overlayBackground as any, "getOverlayVisibility") - .mockResolvedValue(AutofillOverlayVisibility.OnFieldFocus); - }); - - it("will set the overlayVisibility property", async () => { - sendMockExtensionMessage({ command: "getAutofillOverlayVisibility" }); - await flushPromises(); - - expect(await overlayBackground["getOverlayVisibility"]()).toBe( - AutofillOverlayVisibility.OnFieldFocus, - ); - }); - - it("returns the overlayVisibility property", async () => { - const sendMessageSpy = jest.fn(); - - sendMockExtensionMessage( - { command: "getAutofillOverlayVisibility" }, - undefined, - sendMessageSpy, - ); - await flushPromises(); - - expect(sendMessageSpy).toHaveBeenCalledWith(AutofillOverlayVisibility.OnFieldFocus); - }); - }); - - describe("checkAutofillOverlayFocused message handler", () => { - beforeEach(async () => { - await initOverlayElementPorts(); - }); - - it("will check if the overlay list is focused if the list port is open", () => { - sendMockExtensionMessage({ command: "checkAutofillOverlayFocused" }); - - expect(listPortSpy.postMessage).toHaveBeenCalledWith({ - command: "checkAutofillOverlayListFocused", - }); - expect(buttonPortSpy.postMessage).not.toHaveBeenCalledWith({ - command: "checkAutofillOverlayButtonFocused", - }); - }); - - it("will check if the overlay button is focused if the list port is not open", () => { - overlayBackground["overlayListPort"] = undefined; - - sendMockExtensionMessage({ command: "checkAutofillOverlayFocused" }); - - expect(buttonPortSpy.postMessage).toHaveBeenCalledWith({ - command: "checkAutofillOverlayButtonFocused", - }); - expect(listPortSpy.postMessage).not.toHaveBeenCalledWith({ - command: "checkAutofillOverlayListFocused", - }); - }); - }); - - describe("focusAutofillOverlayList message handler", () => { - it("will send a `focusOverlayList` message to the overlay list port", async () => { - await initOverlayElementPorts({ initList: true, initButton: false }); - - sendMockExtensionMessage({ command: "focusAutofillOverlayList" }); - - expect(listPortSpy.postMessage).toHaveBeenCalledWith({ command: "focusOverlayList" }); - }); - }); - - describe("updateAutofillOverlayPosition message handler", () => { - beforeEach(async () => { - await overlayBackground["handlePortOnConnect"]( - createPortSpyMock(AutofillOverlayPort.List), - ); - listPortSpy = overlayBackground["overlayListPort"]; - - await overlayBackground["handlePortOnConnect"]( - createPortSpyMock(AutofillOverlayPort.Button), - ); - buttonPortSpy = overlayBackground["overlayButtonPort"]; - }); - - it("ignores updating the position if the overlay element type is not provided", () => { - sendMockExtensionMessage({ command: "updateAutofillOverlayPosition" }); - - expect(listPortSpy.postMessage).not.toHaveBeenCalledWith({ - command: "updateIframePosition", - styles: expect.anything(), - }); - expect(buttonPortSpy.postMessage).not.toHaveBeenCalledWith({ - command: "updateIframePosition", - styles: expect.anything(), - }); - }); - - it("skips updating the position if the most recently focused field is different than the message sender", () => { - const sender = mock({ tab: { id: 1 } }); - const focusedFieldData = createFocusedFieldDataMock({ tabId: 2 }); - sendMockExtensionMessage({ command: "updateFocusedFieldData", focusedFieldData }); - - sendMockExtensionMessage({ command: "updateAutofillOverlayPosition" }, sender); - - expect(listPortSpy.postMessage).not.toHaveBeenCalledWith({ - command: "updateIframePosition", - styles: expect.anything(), - }); - expect(buttonPortSpy.postMessage).not.toHaveBeenCalledWith({ - command: "updateIframePosition", - styles: expect.anything(), - }); - }); - - it("updates the overlay button's position", () => { - const focusedFieldData = createFocusedFieldDataMock(); - sendMockExtensionMessage({ command: "updateFocusedFieldData", focusedFieldData }); - - sendMockExtensionMessage({ - command: "updateAutofillOverlayPosition", - overlayElement: AutofillOverlayElement.Button, - }); - - expect(buttonPortSpy.postMessage).toHaveBeenCalledWith({ - command: "updateIframePosition", - styles: { height: "2px", left: "4px", top: "2px", width: "2px" }, - }); - }); - - it("modifies the overlay button's height for medium sized input elements", () => { - const focusedFieldData = createFocusedFieldDataMock({ - focusedFieldRects: { top: 1, left: 2, height: 35, width: 4 }, - }); - sendMockExtensionMessage({ command: "updateFocusedFieldData", focusedFieldData }); - - sendMockExtensionMessage({ - command: "updateAutofillOverlayPosition", - overlayElement: AutofillOverlayElement.Button, - }); - - expect(buttonPortSpy.postMessage).toHaveBeenCalledWith({ - command: "updateIframePosition", - styles: { height: "20px", left: "-22px", top: "8px", width: "20px" }, - }); - }); - - it("modifies the overlay button's height for large sized input elements", () => { - const focusedFieldData = createFocusedFieldDataMock({ - focusedFieldRects: { top: 1, left: 2, height: 50, width: 4 }, - }); - sendMockExtensionMessage({ command: "updateFocusedFieldData", focusedFieldData }); - - sendMockExtensionMessage({ - command: "updateAutofillOverlayPosition", - overlayElement: AutofillOverlayElement.Button, - }); - - expect(buttonPortSpy.postMessage).toHaveBeenCalledWith({ - command: "updateIframePosition", - styles: { height: "27px", left: "-32px", top: "13px", width: "27px" }, - }); - }); - - it("takes into account the right padding of the focused field in positioning the button if the right padding of the field is larger than the left padding", () => { - const focusedFieldData = createFocusedFieldDataMock({ - focusedFieldStyles: { paddingRight: "20px", paddingLeft: "6px" }, - }); - sendMockExtensionMessage({ command: "updateFocusedFieldData", focusedFieldData }); - - sendMockExtensionMessage({ - command: "updateAutofillOverlayPosition", - overlayElement: AutofillOverlayElement.Button, - }); - - expect(buttonPortSpy.postMessage).toHaveBeenCalledWith({ - command: "updateIframePosition", - styles: { height: "2px", left: "-18px", top: "2px", width: "2px" }, - }); - }); - - it("will post a message to the overlay list facilitating an update of the list's position", () => { - const sender = mock({ tab: { id: 1 } }); - const focusedFieldData = createFocusedFieldDataMock(); - sendMockExtensionMessage({ command: "updateFocusedFieldData", focusedFieldData }); - - overlayBackground["updateOverlayPosition"]( - { overlayElement: AutofillOverlayElement.List }, - sender, - ); - sendMockExtensionMessage({ - command: "updateAutofillOverlayPosition", - overlayElement: AutofillOverlayElement.List, - }); - - expect(listPortSpy.postMessage).toHaveBeenCalledWith({ - command: "updateIframePosition", - styles: { left: "2px", top: "4px", width: "4px" }, - }); - }); - }); - - describe("updateOverlayHidden", () => { - beforeEach(async () => { - await initOverlayElementPorts(); - }); - - it("returns early if the display value is not provided", () => { - const message = { - command: "updateAutofillOverlayHidden", - }; - - sendMockExtensionMessage(message); - - expect(buttonPortSpy.postMessage).not.toHaveBeenCalledWith(message); - expect(listPortSpy.postMessage).not.toHaveBeenCalledWith(message); - }); - - it("posts a message to the overlay button and list with the display value", () => { - const message = { command: "updateAutofillOverlayHidden", display: "none" }; - - sendMockExtensionMessage(message); - - expect(overlayBackground["overlayButtonPort"].postMessage).toHaveBeenCalledWith({ - command: "updateOverlayHidden", - styles: { - display: message.display, - }, - }); - expect(overlayBackground["overlayListPort"].postMessage).toHaveBeenCalledWith({ - command: "updateOverlayHidden", - styles: { - display: message.display, - }, - }); - }); - }); - - describe("collectPageDetailsResponse message handler", () => { - let sender: chrome.runtime.MessageSender; - const pageDetails1 = createAutofillPageDetailsMock({ - login: { username: "username1", password: "password1" }, - }); - const pageDetails2 = createAutofillPageDetailsMock({ - login: { username: "username2", password: "password2" }, - }); - - beforeEach(() => { - sender = mock({ tab: { id: 1 } }); - }); - - it("stores the page details provided by the message by the tab id of the sender", () => { - sendMockExtensionMessage( - { command: "collectPageDetailsResponse", details: pageDetails1 }, - sender, - ); - - expect(overlayBackground["pageDetailsForTab"][sender.tab.id]).toStrictEqual( - new Map([ - [sender.frameId, { frameId: sender.frameId, tab: sender.tab, details: pageDetails1 }], - ]), - ); - }); - - it("updates the page details for a tab that already has a set of page details stored ", () => { - const secondFrameSender = mock({ - tab: { id: 1 }, - frameId: 3, - }); - overlayBackground["pageDetailsForTab"][sender.tab.id] = new Map([ - [sender.frameId, { frameId: sender.frameId, tab: sender.tab, details: pageDetails1 }], - ]); - - sendMockExtensionMessage( - { command: "collectPageDetailsResponse", details: pageDetails2 }, - secondFrameSender, - ); - - expect(overlayBackground["pageDetailsForTab"][sender.tab.id]).toStrictEqual( - new Map([ - [sender.frameId, { frameId: sender.frameId, tab: sender.tab, details: pageDetails1 }], - [ - secondFrameSender.frameId, - { - frameId: secondFrameSender.frameId, - tab: secondFrameSender.tab, - details: pageDetails2, - }, - ], - ]), - ); - }); - }); - - describe("unlockCompleted message handler", () => { - let getAuthStatusSpy: jest.SpyInstance; - - beforeEach(() => { - overlayBackground["userAuthStatus"] = AuthenticationStatus.LoggedOut; - jest.spyOn(BrowserApi, "tabSendMessageData"); - getAuthStatusSpy = jest - .spyOn(overlayBackground as any, "getAuthStatus") - .mockImplementation(() => { - overlayBackground["userAuthStatus"] = AuthenticationStatus.Unlocked; - return Promise.resolve(AuthenticationStatus.Unlocked); - }); - }); - - it("updates the user's auth status but does not open the overlay", async () => { - const message = { - command: "unlockCompleted", - data: { - commandToRetry: { message: { command: "" } }, - }, - }; - - sendMockExtensionMessage(message); - await flushPromises(); - - expect(getAuthStatusSpy).toHaveBeenCalled(); - expect(BrowserApi.tabSendMessageData).not.toHaveBeenCalled(); - }); - - it("updates user's auth status and opens the overlay if a follow up command is provided", async () => { - const sender = mock({ tab: { id: 1 } }); - const message = { - command: "unlockCompleted", - data: { - commandToRetry: { message: { command: "openAutofillOverlay" } }, - }, - }; - jest.spyOn(BrowserApi, "getTabFromCurrentWindowId").mockResolvedValueOnce(sender.tab); - - sendMockExtensionMessage(message); - await flushPromises(); - - expect(getAuthStatusSpy).toHaveBeenCalled(); - expect(BrowserApi.tabSendMessageData).toHaveBeenCalledWith( - sender.tab, - "openAutofillOverlay", - { - isFocusingFieldElement: true, - isOpeningFullOverlay: false, - authStatus: AuthenticationStatus.Unlocked, - }, - ); - }); - }); - - describe("extension messages that trigger an update of the inline menu ciphers", () => { - const extensionMessages = [ - "addedCipher", - "addEditCipherSubmitted", - "editedCipher", - "deletedCipher", - ]; - - beforeEach(() => { - jest.spyOn(overlayBackground, "updateOverlayCiphers").mockImplementation(); - }); - - extensionMessages.forEach((message) => { - it(`triggers an update of the overlay ciphers when the ${message} message is received`, () => { - sendMockExtensionMessage({ command: message }); - expect(overlayBackground.updateOverlayCiphers).toHaveBeenCalled(); - }); - }); - }); - }); - }); - - describe("handlePortOnConnect", () => { - beforeEach(() => { - jest.spyOn(overlayBackground as any, "updateOverlayPosition").mockImplementation(); - jest.spyOn(overlayBackground as any, "getAuthStatus").mockImplementation(); - jest.spyOn(overlayBackground as any, "getTranslations").mockImplementation(); - jest.spyOn(overlayBackground as any, "getOverlayCipherData").mockImplementation(); - }); - - it("skips setting up the overlay port if the port connection is not for an overlay element", async () => { - const port = createPortSpyMock("not-an-overlay-element"); - - await overlayBackground["handlePortOnConnect"](port); - - expect(port.onMessage.addListener).not.toHaveBeenCalled(); - expect(port.postMessage).not.toHaveBeenCalled(); - }); - - it("sets up the overlay list port if the port connection is for the overlay list", async () => { - await initOverlayElementPorts({ initList: true, initButton: false }); - await flushPromises(); - - expect(overlayBackground["overlayButtonPort"]).toBeUndefined(); - expect(listPortSpy.onMessage.addListener).toHaveBeenCalled(); - expect(listPortSpy.postMessage).toHaveBeenCalled(); - expect(overlayBackground["getAuthStatus"]).toHaveBeenCalled(); - expect(chrome.runtime.getURL).toHaveBeenCalledWith("overlay/list.css"); - expect(overlayBackground["getTranslations"]).toHaveBeenCalled(); - expect(overlayBackground["getOverlayCipherData"]).toHaveBeenCalled(); - expect(overlayBackground["updateOverlayPosition"]).toHaveBeenCalledWith( - { overlayElement: AutofillOverlayElement.List }, - listPortSpy.sender, - ); - }); - - it("sets up the overlay button port if the port connection is for the overlay button", async () => { - await initOverlayElementPorts({ initList: false, initButton: true }); - await flushPromises(); - - expect(overlayBackground["overlayListPort"]).toBeUndefined(); - expect(buttonPortSpy.onMessage.addListener).toHaveBeenCalled(); - expect(buttonPortSpy.postMessage).toHaveBeenCalled(); - expect(overlayBackground["getAuthStatus"]).toHaveBeenCalled(); - expect(chrome.runtime.getURL).toHaveBeenCalledWith("overlay/button.css"); - expect(overlayBackground["getTranslations"]).toHaveBeenCalled(); - expect(overlayBackground["updateOverlayPosition"]).toHaveBeenCalledWith( - { overlayElement: AutofillOverlayElement.Button }, - buttonPortSpy.sender, - ); - }); - - it("stores an existing overlay port so that it can be disconnected at a later time", async () => { - overlayBackground["overlayButtonPort"] = mock(); - - await initOverlayElementPorts({ initList: false, initButton: true }); - await flushPromises(); - - expect(overlayBackground["expiredPorts"].length).toBe(1); - }); - - it("gets the system theme", async () => { - themeStateService.selectedTheme$ = of(ThemeType.System); - - await initOverlayElementPorts({ initList: true, initButton: false }); - await flushPromises(); - - expect(listPortSpy.postMessage).toHaveBeenCalledWith( - expect.objectContaining({ theme: ThemeType.System }), - ); - }); - }); - - describe("handleOverlayElementPortMessage", () => { - beforeEach(async () => { - await initOverlayElementPorts(); - overlayBackground["userAuthStatus"] = AuthenticationStatus.Unlocked; - }); - - it("ignores port messages that do not contain a handler", () => { - jest.spyOn(overlayBackground as any, "checkOverlayButtonFocused").mockImplementation(); - - sendPortMessage(buttonPortSpy, { command: "checkAutofillOverlayButtonFocused" }); - - expect(overlayBackground["checkOverlayButtonFocused"]).not.toHaveBeenCalled(); - }); - - describe("overlay button message handlers", () => { - it("unlocks the vault if the user auth status is not unlocked", () => { - overlayBackground["userAuthStatus"] = AuthenticationStatus.LoggedOut; - jest.spyOn(overlayBackground as any, "unlockVault").mockImplementation(); - - sendPortMessage(buttonPortSpy, { command: "overlayButtonClicked" }); - - expect(overlayBackground["unlockVault"]).toHaveBeenCalled(); - }); - - it("opens the autofill overlay if the auth status is unlocked", () => { - jest.spyOn(overlayBackground as any, "openOverlay").mockImplementation(); - - sendPortMessage(buttonPortSpy, { command: "overlayButtonClicked" }); - - expect(overlayBackground["openOverlay"]).toHaveBeenCalled(); - }); - - describe("closeAutofillOverlay", () => { - it("sends a `closeOverlay` message to the sender tab", () => { - jest.spyOn(BrowserApi, "tabSendMessageData"); - - sendPortMessage(buttonPortSpy, { command: "closeAutofillOverlay" }); - - expect(BrowserApi.tabSendMessageData).toHaveBeenCalledWith( - buttonPortSpy.sender.tab, - "closeAutofillOverlay", - { forceCloseOverlay: false }, - ); - }); - }); - - describe("forceCloseAutofillOverlay", () => { - it("sends a `closeOverlay` message to the sender tab with a `forceCloseOverlay` flag of `true` set", () => { - jest.spyOn(BrowserApi, "tabSendMessageData"); - - sendPortMessage(buttonPortSpy, { command: "forceCloseAutofillOverlay" }); - - expect(BrowserApi.tabSendMessageData).toHaveBeenCalledWith( - buttonPortSpy.sender.tab, - "closeAutofillOverlay", - { forceCloseOverlay: true }, - ); - }); - }); - - describe("overlayPageBlurred", () => { - it("checks if the overlay list is focused", () => { - jest.spyOn(overlayBackground as any, "checkOverlayListFocused"); - - sendPortMessage(buttonPortSpy, { command: "overlayPageBlurred" }); - - expect(overlayBackground["checkOverlayListFocused"]).toHaveBeenCalled(); - }); - }); - - describe("redirectOverlayFocusOut", () => { - beforeEach(() => { - jest.spyOn(BrowserApi, "tabSendMessageData"); - }); - - it("ignores the redirect message if the direction is not provided", () => { - sendPortMessage(buttonPortSpy, { command: "redirectOverlayFocusOut" }); - - expect(BrowserApi.tabSendMessageData).not.toHaveBeenCalled(); - }); - - it("sends the redirect message if the direction is provided", () => { - sendPortMessage(buttonPortSpy, { - command: "redirectOverlayFocusOut", - direction: RedirectFocusDirection.Next, - }); - - expect(BrowserApi.tabSendMessageData).toHaveBeenCalledWith( - buttonPortSpy.sender.tab, - "redirectOverlayFocusOut", - { direction: RedirectFocusDirection.Next }, - ); - }); - }); - }); - - describe("overlay list message handlers", () => { - describe("checkAutofillOverlayButtonFocused", () => { - it("checks on the focus state of the overlay button", () => { - jest.spyOn(overlayBackground as any, "checkOverlayButtonFocused").mockImplementation(); - - sendPortMessage(listPortSpy, { command: "checkAutofillOverlayButtonFocused" }); - - expect(overlayBackground["checkOverlayButtonFocused"]).toHaveBeenCalled(); - }); - }); - - describe("forceCloseAutofillOverlay", () => { - it("sends a `closeOverlay` message to the sender tab with a `forceCloseOverlay` flag of `true` set", () => { - jest.spyOn(BrowserApi, "tabSendMessageData"); - - sendPortMessage(listPortSpy, { command: "forceCloseAutofillOverlay" }); - - expect(BrowserApi.tabSendMessageData).toHaveBeenCalledWith( - listPortSpy.sender.tab, - "closeAutofillOverlay", - { forceCloseOverlay: true }, - ); - }); - }); - - describe("overlayPageBlurred", () => { - it("checks on the focus state of the overlay button", () => { - jest.spyOn(overlayBackground as any, "checkOverlayButtonFocused").mockImplementation(); - - sendPortMessage(listPortSpy, { command: "overlayPageBlurred" }); - - expect(overlayBackground["checkOverlayButtonFocused"]).toHaveBeenCalled(); - }); - }); - - describe("unlockVault", () => { - it("closes the autofill overlay and opens the unlock popout", async () => { - jest.spyOn(overlayBackground as any, "closeOverlay").mockImplementation(); - jest.spyOn(overlayBackground as any, "openUnlockPopout").mockImplementation(); - jest.spyOn(BrowserApi, "tabSendMessageData").mockImplementation(); - - sendPortMessage(listPortSpy, { command: "unlockVault" }); - await flushPromises(); - - expect(overlayBackground["closeOverlay"]).toHaveBeenCalledWith(listPortSpy); - expect(BrowserApi.tabSendMessageData).toHaveBeenCalledWith( - listPortSpy.sender.tab, - "addToLockedVaultPendingNotifications", - { - commandToRetry: { - message: { command: "openAutofillOverlay" }, - sender: listPortSpy.sender, - }, - target: "overlay.background", - }, - ); - expect(overlayBackground["openUnlockPopout"]).toHaveBeenCalledWith( - listPortSpy.sender.tab, - true, - ); - }); - }); - - describe("fillSelectedListItem", () => { - let getLoginCiphersSpy: jest.SpyInstance; - let isPasswordRepromptRequiredSpy: jest.SpyInstance; - let doAutoFillSpy: jest.SpyInstance; - let sender: chrome.runtime.MessageSender; - const pageDetails = createAutofillPageDetailsMock({ - login: { username: "username1", password: "password1" }, - }); - - beforeEach(() => { - getLoginCiphersSpy = jest.spyOn(overlayBackground["overlayLoginCiphers"], "get"); - isPasswordRepromptRequiredSpy = jest.spyOn( - overlayBackground["autofillService"], - "isPasswordRepromptRequired", - ); - doAutoFillSpy = jest.spyOn(overlayBackground["autofillService"], "doAutoFill"); - sender = mock({ tab: { id: 1 } }); - }); - - it("ignores the fill request if the overlay cipher id is not provided", async () => { - sendPortMessage(listPortSpy, { command: "fillSelectedListItem" }); - await flushPromises(); - - expect(getLoginCiphersSpy).not.toHaveBeenCalled(); - expect(isPasswordRepromptRequiredSpy).not.toHaveBeenCalled(); - expect(doAutoFillSpy).not.toHaveBeenCalled(); - }); - - it("ignores the fill request if the tab does not contain any identified page details", async () => { - sendPortMessage(listPortSpy, { - command: "fillSelectedListItem", - overlayCipherId: "overlay-cipher-1", - }); - await flushPromises(); - - expect(getLoginCiphersSpy).not.toHaveBeenCalled(); - expect(isPasswordRepromptRequiredSpy).not.toHaveBeenCalled(); - expect(doAutoFillSpy).not.toHaveBeenCalled(); - }); - - it("ignores the fill request if a master password reprompt is required", async () => { - const cipher = mock({ - reprompt: CipherRepromptType.Password, - type: CipherType.Login, - }); - overlayBackground["overlayLoginCiphers"] = new Map([["overlay-cipher-1", cipher]]); - overlayBackground["pageDetailsForTab"][sender.tab.id] = new Map([ - [sender.frameId, { frameId: sender.frameId, tab: sender.tab, details: pageDetails }], - ]); - getLoginCiphersSpy = jest.spyOn(overlayBackground["overlayLoginCiphers"], "get"); - isPasswordRepromptRequiredSpy.mockResolvedValue(true); - - sendPortMessage(listPortSpy, { - command: "fillSelectedListItem", - overlayCipherId: "overlay-cipher-1", - }); - await flushPromises(); - - expect(getLoginCiphersSpy).toHaveBeenCalled(); - expect(isPasswordRepromptRequiredSpy).toHaveBeenCalledWith( - cipher, - listPortSpy.sender.tab, - ); - expect(doAutoFillSpy).not.toHaveBeenCalled(); - }); - - it("autofills the selected cipher and move it to the top of the front of the ciphers map", async () => { - const cipher1 = mock({ id: "overlay-cipher-1" }); - const cipher2 = mock({ id: "overlay-cipher-2" }); - const cipher3 = mock({ id: "overlay-cipher-3" }); - overlayBackground["overlayLoginCiphers"] = new Map([ - ["overlay-cipher-1", cipher1], - ["overlay-cipher-2", cipher2], - ["overlay-cipher-3", cipher3], - ]); - const pageDetailsForTab = { - frameId: sender.frameId, - tab: sender.tab, - details: pageDetails, - }; - overlayBackground["pageDetailsForTab"][sender.tab.id] = new Map([ - [sender.frameId, pageDetailsForTab], - ]); - isPasswordRepromptRequiredSpy.mockResolvedValue(false); - - sendPortMessage(listPortSpy, { - command: "fillSelectedListItem", - overlayCipherId: "overlay-cipher-2", - }); - await flushPromises(); - - expect(isPasswordRepromptRequiredSpy).toHaveBeenCalledWith( - cipher2, - listPortSpy.sender.tab, - ); - expect(doAutoFillSpy).toHaveBeenCalledWith({ - tab: listPortSpy.sender.tab, - cipher: cipher2, - pageDetails: [pageDetailsForTab], - fillNewPassword: true, - allowTotpAutofill: true, - }); - expect(overlayBackground["overlayLoginCiphers"].entries()).toStrictEqual( - new Map([ - ["overlay-cipher-2", cipher2], - ["overlay-cipher-1", cipher1], - ["overlay-cipher-3", cipher3], - ]).entries(), - ); - }); - - it("copies the cipher's totp code to the clipboard after filling", async () => { - const cipher1 = mock({ id: "overlay-cipher-1" }); - overlayBackground["overlayLoginCiphers"] = new Map([["overlay-cipher-1", cipher1]]); - overlayBackground["pageDetailsForTab"][sender.tab.id] = new Map([ - [sender.frameId, { frameId: sender.frameId, tab: sender.tab, details: pageDetails }], - ]); - isPasswordRepromptRequiredSpy.mockResolvedValue(false); - const copyToClipboardSpy = jest - .spyOn(overlayBackground["platformUtilsService"], "copyToClipboard") - .mockImplementation(); - doAutoFillSpy.mockReturnValueOnce("totp-code"); - - sendPortMessage(listPortSpy, { - command: "fillSelectedListItem", - overlayCipherId: "overlay-cipher-2", - }); - await flushPromises(); - - expect(copyToClipboardSpy).toHaveBeenCalledWith("totp-code"); - }); - }); - - describe("getNewVaultItemDetails", () => { - it("will send an addNewVaultItemFromOverlay message", async () => { - jest.spyOn(BrowserApi, "tabSendMessage"); - - sendPortMessage(listPortSpy, { command: "addNewVaultItem" }); - await flushPromises(); - - expect(BrowserApi.tabSendMessage).toHaveBeenCalledWith(listPortSpy.sender.tab, { - command: "addNewVaultItemFromOverlay", - }); - }); - }); - - describe("viewSelectedCipher", () => { - let openViewVaultItemPopoutSpy: jest.SpyInstance; - - beforeEach(() => { - openViewVaultItemPopoutSpy = jest - .spyOn(overlayBackground as any, "openViewVaultItemPopout") - .mockImplementation(); - }); - - it("returns early if the passed cipher ID does not match one of the overlay login ciphers", async () => { - overlayBackground["overlayLoginCiphers"] = new Map([ - ["overlay-cipher-0", mock({ id: "overlay-cipher-0" })], - ]); - - sendPortMessage(listPortSpy, { - command: "viewSelectedCipher", - overlayCipherId: "overlay-cipher-1", - }); - await flushPromises(); - - expect(openViewVaultItemPopoutSpy).not.toHaveBeenCalled(); - }); - - it("will open the view vault item popout with the selected cipher", async () => { - const cipher = mock({ id: "overlay-cipher-1" }); - overlayBackground["overlayLoginCiphers"] = new Map([ - ["overlay-cipher-0", mock({ id: "overlay-cipher-0" })], - ["overlay-cipher-1", cipher], - ]); - - sendPortMessage(listPortSpy, { - command: "viewSelectedCipher", - overlayCipherId: "overlay-cipher-1", - }); - await flushPromises(); - - expect(overlayBackground["openViewVaultItemPopout"]).toHaveBeenCalledWith( - listPortSpy.sender.tab, - { - cipherId: cipher.id, - action: SHOW_AUTOFILL_BUTTON, - }, - ); - }); - }); - - describe("redirectOverlayFocusOut", () => { - it("redirects focus out of the overlay list", async () => { - const message = { - command: "redirectOverlayFocusOut", - direction: RedirectFocusDirection.Next, - }; - const redirectOverlayFocusOutSpy = jest.spyOn( - overlayBackground as any, - "redirectOverlayFocusOut", - ); - - sendPortMessage(listPortSpy, message); - await flushPromises(); - - expect(redirectOverlayFocusOutSpy).toHaveBeenCalledWith(message, listPortSpy); - }); - }); - }); - }); -}); diff --git a/apps/browser/src/autofill/deprecated/background/overlay.background.deprecated.ts b/apps/browser/src/autofill/deprecated/background/overlay.background.deprecated.ts deleted file mode 100644 index c9eb442d75d..00000000000 --- a/apps/browser/src/autofill/deprecated/background/overlay.background.deprecated.ts +++ /dev/null @@ -1,811 +0,0 @@ -// FIXME: Update this file to be type safe and remove this and next line -// @ts-strict-ignore -import { firstValueFrom, map } from "rxjs"; - -import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; -import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service"; -import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status"; -import { SHOW_AUTOFILL_BUTTON } from "@bitwarden/common/autofill/constants"; -import { AutofillSettingsServiceAbstraction } from "@bitwarden/common/autofill/services/autofill-settings.service"; -import { DomainSettingsService } from "@bitwarden/common/autofill/services/domain-settings.service"; -import { InlineMenuVisibilitySetting } from "@bitwarden/common/autofill/types"; -import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service"; -import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; -import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; -import { Utils } from "@bitwarden/common/platform/misc/utils"; -import { ThemeStateService } from "@bitwarden/common/platform/theming/theme-state.service"; -import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; -import { CipherType } from "@bitwarden/common/vault/enums"; -import { buildCipherIcon } from "@bitwarden/common/vault/icon/build-cipher-icon"; -import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; -import { LoginUriView } from "@bitwarden/common/vault/models/view/login-uri.view"; -import { LoginView } from "@bitwarden/common/vault/models/view/login.view"; - -import { openUnlockPopout } from "../../../auth/popup/utils/auth-popout-window"; -import { BrowserApi } from "../../../platform/browser/browser-api"; -import { - openViewVaultItemPopout, - openAddEditVaultItemPopout, -} from "../../../vault/popup/utils/vault-popout-window"; -import { LockedVaultPendingNotificationsData } from "../../background/abstractions/notification.background"; -import { OverlayBackground as OverlayBackgroundInterface } from "../../background/abstractions/overlay.background"; -import { AutofillOverlayElement, AutofillOverlayPort } from "../../enums/autofill-overlay.enum"; -import { AutofillService, PageDetail } from "../../services/abstractions/autofill.service"; - -import { - FocusedFieldData, - OverlayBackgroundExtensionMessageHandlers, - OverlayButtonPortMessageHandlers, - OverlayCipherData, - OverlayListPortMessageHandlers, - OverlayBackgroundExtensionMessage, - OverlayAddNewItemMessage, - OverlayPortMessage, - WebsiteIconData, -} from "./abstractions/overlay.background.deprecated"; - -class LegacyOverlayBackground implements OverlayBackgroundInterface { - private readonly openUnlockPopout = openUnlockPopout; - private readonly openViewVaultItemPopout = openViewVaultItemPopout; - private readonly openAddEditVaultItemPopout = openAddEditVaultItemPopout; - private overlayLoginCiphers: Map = new Map(); - private pageDetailsForTab: Record< - chrome.runtime.MessageSender["tab"]["id"], - Map - > = {}; - private userAuthStatus: AuthenticationStatus = AuthenticationStatus.LoggedOut; - private overlayButtonPort: chrome.runtime.Port; - private overlayListPort: chrome.runtime.Port; - private expiredPorts: chrome.runtime.Port[] = []; - private focusedFieldData: FocusedFieldData; - private overlayPageTranslations: Record; - private iconsServerUrl: string; - private readonly extensionMessageHandlers: OverlayBackgroundExtensionMessageHandlers = { - openAutofillOverlay: () => this.openOverlay(false), - autofillOverlayElementClosed: ({ message, sender }) => - this.overlayElementClosed(message, sender), - autofillOverlayAddNewVaultItem: ({ message, sender }) => this.addNewVaultItem(message, sender), - getAutofillOverlayVisibility: () => this.getOverlayVisibility(), - checkAutofillOverlayFocused: () => this.checkOverlayFocused(), - focusAutofillOverlayList: () => this.focusOverlayList(), - updateAutofillOverlayPosition: ({ message, sender }) => - this.updateOverlayPosition(message, sender), - updateAutofillOverlayHidden: ({ message }) => this.updateOverlayHidden(message), - updateFocusedFieldData: ({ message, sender }) => this.setFocusedFieldData(message, sender), - collectPageDetailsResponse: ({ message, sender }) => this.storePageDetails(message, sender), - unlockCompleted: ({ message }) => this.unlockCompleted(message), - addedCipher: () => this.updateOverlayCiphers(), - addEditCipherSubmitted: () => this.updateOverlayCiphers(), - editedCipher: () => this.updateOverlayCiphers(), - deletedCipher: () => this.updateOverlayCiphers(), - }; - private readonly overlayButtonPortMessageHandlers: OverlayButtonPortMessageHandlers = { - overlayButtonClicked: ({ port }) => this.handleOverlayButtonClicked(port), - closeAutofillOverlay: ({ port }) => this.closeOverlay(port), - forceCloseAutofillOverlay: ({ port }) => this.closeOverlay(port, true), - overlayPageBlurred: () => this.checkOverlayListFocused(), - redirectOverlayFocusOut: ({ message, port }) => this.redirectOverlayFocusOut(message, port), - }; - private readonly overlayListPortMessageHandlers: OverlayListPortMessageHandlers = { - checkAutofillOverlayButtonFocused: () => this.checkOverlayButtonFocused(), - forceCloseAutofillOverlay: ({ port }) => this.closeOverlay(port, true), - overlayPageBlurred: () => this.checkOverlayButtonFocused(), - unlockVault: ({ port }) => this.unlockVault(port), - fillSelectedListItem: ({ message, port }) => this.fillSelectedOverlayListItem(message, port), - addNewVaultItem: ({ port }) => this.getNewVaultItemDetails(port), - viewSelectedCipher: ({ message, port }) => this.viewSelectedCipher(message, port), - redirectOverlayFocusOut: ({ message, port }) => this.redirectOverlayFocusOut(message, port), - }; - - constructor( - private cipherService: CipherService, - private autofillService: AutofillService, - private authService: AuthService, - private environmentService: EnvironmentService, - private domainSettingsService: DomainSettingsService, - private autofillSettingsService: AutofillSettingsServiceAbstraction, - private i18nService: I18nService, - private platformUtilsService: PlatformUtilsService, - private themeStateService: ThemeStateService, - private accountService: AccountService, - ) {} - - /** - * Removes cached page details for a tab - * based on the passed tabId. - * - * @param tabId - Used to reference the page details of a specific tab - */ - removePageDetails(tabId: number) { - if (!this.pageDetailsForTab[tabId]) { - return; - } - - this.pageDetailsForTab[tabId].clear(); - delete this.pageDetailsForTab[tabId]; - } - - /** - * Sets up the extension message listeners and gets the settings for the - * overlay's visibility and the user's authentication status. - */ - async init() { - this.setupExtensionMessageListeners(); - const env = await firstValueFrom(this.environmentService.environment$); - this.iconsServerUrl = env.getIconsUrl(); - await this.getOverlayVisibility(); - await this.getAuthStatus(); - } - - /** - * Updates the overlay list's ciphers and sends the updated list to the overlay list iframe. - * Queries all ciphers for the given url, and sorts them by last used. Will not update the - * list of ciphers if the extension is not unlocked. - */ - async updateOverlayCiphers() { - const authStatus = await firstValueFrom(this.authService.activeAccountStatus$); - if (authStatus !== AuthenticationStatus.Unlocked) { - return; - } - - const currentTab = await BrowserApi.getTabFromCurrentWindowId(); - if (!currentTab?.url) { - return; - } - - this.overlayLoginCiphers = new Map(); - - const activeUserId = await firstValueFrom( - this.accountService.activeAccount$.pipe(map((a) => a?.id)), - ); - const ciphersViews = ( - await this.cipherService.getAllDecryptedForUrl(currentTab.url, activeUserId) - ).sort((a, b) => this.cipherService.sortCiphersByLastUsedThenName(a, b)); - for (let cipherIndex = 0; cipherIndex < ciphersViews.length; cipherIndex++) { - this.overlayLoginCiphers.set(`overlay-cipher-${cipherIndex}`, ciphersViews[cipherIndex]); - } - - const ciphers = await this.getOverlayCipherData(); - this.overlayListPort?.postMessage({ command: "updateOverlayListCiphers", ciphers }); - await BrowserApi.tabSendMessageData(currentTab, "updateIsOverlayCiphersPopulated", { - isOverlayCiphersPopulated: Boolean(ciphers.length), - }); - } - - /** - * Strips out unnecessary data from the ciphers and returns an array of - * objects that contain the cipher data needed for the overlay list. - */ - private async getOverlayCipherData(): Promise { - const showFavicons = await firstValueFrom(this.domainSettingsService.showFavicons$); - const overlayCiphersArray = Array.from(this.overlayLoginCiphers); - const overlayCipherData: OverlayCipherData[] = []; - let loginCipherIcon: WebsiteIconData; - - for (let cipherIndex = 0; cipherIndex < overlayCiphersArray.length; cipherIndex++) { - const [overlayCipherId, cipher] = overlayCiphersArray[cipherIndex]; - if (!loginCipherIcon && cipher.type === CipherType.Login) { - loginCipherIcon = buildCipherIcon(this.iconsServerUrl, cipher, showFavicons); - } - - overlayCipherData.push({ - id: overlayCipherId, - name: cipher.name, - type: cipher.type, - reprompt: cipher.reprompt, - favorite: cipher.favorite, - icon: - cipher.type === CipherType.Login - ? loginCipherIcon - : buildCipherIcon(this.iconsServerUrl, cipher, showFavicons), - login: cipher.type === CipherType.Login ? { username: cipher.login.username } : null, - card: cipher.type === CipherType.Card ? cipher.card.subTitle : null, - }); - } - - return overlayCipherData; - } - - /** - * Handles aggregation of page details for a tab. Stores the page details - * in association with the tabId of the tab that sent the message. - * - * @param message - Message received from the `collectPageDetailsResponse` command - * @param sender - The sender of the message - */ - private storePageDetails( - message: OverlayBackgroundExtensionMessage, - sender: chrome.runtime.MessageSender, - ) { - const pageDetails = { - frameId: sender.frameId, - tab: sender.tab, - details: message.details, - }; - - const pageDetailsMap = this.pageDetailsForTab[sender.tab.id]; - if (!pageDetailsMap) { - this.pageDetailsForTab[sender.tab.id] = new Map([[sender.frameId, pageDetails]]); - return; - } - - pageDetailsMap.set(sender.frameId, pageDetails); - } - - /** - * Triggers autofill for the selected cipher in the overlay list. Also places - * the selected cipher at the top of the list of ciphers. - * - * @param overlayCipherId - Cipher ID corresponding to the overlayLoginCiphers map. Does not correspond to the actual cipher's ID. - * @param sender - The sender of the port message - */ - private async fillSelectedOverlayListItem( - { overlayCipherId }: OverlayPortMessage, - { sender }: chrome.runtime.Port, - ) { - const pageDetails = this.pageDetailsForTab[sender.tab.id]; - if (!overlayCipherId || !pageDetails?.size) { - return; - } - - const cipher = this.overlayLoginCiphers.get(overlayCipherId); - - if (await this.autofillService.isPasswordRepromptRequired(cipher, sender.tab)) { - return; - } - const totpCode = await this.autofillService.doAutoFill({ - tab: sender.tab, - cipher: cipher, - pageDetails: Array.from(pageDetails.values()), - fillNewPassword: true, - allowTotpAutofill: true, - }); - - if (totpCode) { - this.platformUtilsService.copyToClipboard(totpCode); - } - - this.overlayLoginCiphers = new Map([[overlayCipherId, cipher], ...this.overlayLoginCiphers]); - } - - /** - * Checks if the overlay is focused. Will check the overlay list - * if it is open, otherwise it will check the overlay button. - */ - private checkOverlayFocused() { - if (this.overlayListPort) { - this.checkOverlayListFocused(); - - return; - } - - this.checkOverlayButtonFocused(); - } - - /** - * Posts a message to the overlay button iframe to check if it is focused. - */ - private checkOverlayButtonFocused() { - this.overlayButtonPort?.postMessage({ command: "checkAutofillOverlayButtonFocused" }); - } - - /** - * Posts a message to the overlay list iframe to check if it is focused. - */ - private checkOverlayListFocused() { - this.overlayListPort?.postMessage({ command: "checkAutofillOverlayListFocused" }); - } - - /** - * Sends a message to the sender tab to close the autofill overlay. - * - * @param sender - The sender of the port message - * @param forceCloseOverlay - Identifies whether the overlay should be force closed - */ - private closeOverlay({ sender }: chrome.runtime.Port, forceCloseOverlay = false) { - // FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling. - // eslint-disable-next-line @typescript-eslint/no-floating-promises - BrowserApi.tabSendMessageData(sender.tab, "closeAutofillOverlay", { forceCloseOverlay }); - } - - /** - * Handles cleanup when an overlay element is closed. Disconnects - * the list and button ports and sets them to null. - * - * @param overlayElement - The overlay element that was closed, either the list or button - * @param sender - The sender of the port message - */ - private overlayElementClosed( - { overlayElement }: OverlayBackgroundExtensionMessage, - sender: chrome.runtime.MessageSender, - ) { - if (sender.tab.id !== this.focusedFieldData?.tabId) { - this.expiredPorts.forEach((port) => port.disconnect()); - this.expiredPorts = []; - return; - } - - if (overlayElement === AutofillOverlayElement.Button) { - this.overlayButtonPort?.disconnect(); - this.overlayButtonPort = null; - - return; - } - - this.overlayListPort?.disconnect(); - this.overlayListPort = null; - } - - /** - * Updates the position of either the overlay list or button. The position - * is based on the focused field's position and dimensions. - * - * @param overlayElement - The overlay element to update, either the list or button - * @param sender - The sender of the port message - */ - private updateOverlayPosition( - { overlayElement }: { overlayElement?: string }, - sender: chrome.runtime.MessageSender, - ) { - if (!overlayElement || sender.tab.id !== this.focusedFieldData?.tabId) { - return; - } - - if (overlayElement === AutofillOverlayElement.Button) { - this.overlayButtonPort?.postMessage({ - command: "updateIframePosition", - styles: this.getOverlayButtonPosition(), - }); - - return; - } - - this.overlayListPort?.postMessage({ - command: "updateIframePosition", - styles: this.getOverlayListPosition(), - }); - } - - /** - * Gets the position of the focused field and calculates the position - * of the overlay button based on the focused field's position and dimensions. - */ - private getOverlayButtonPosition() { - if (!this.focusedFieldData) { - return; - } - - const { top, left, width, height } = this.focusedFieldData.focusedFieldRects; - const { paddingRight, paddingLeft } = this.focusedFieldData.focusedFieldStyles; - let elementOffset = height * 0.37; - if (height >= 35) { - elementOffset = height >= 50 ? height * 0.47 : height * 0.42; - } - - const elementHeight = height - elementOffset; - const elementTopPosition = top + elementOffset / 2; - let elementLeftPosition = left + width - height + elementOffset / 2; - - const fieldPaddingRight = parseInt(paddingRight, 10); - const fieldPaddingLeft = parseInt(paddingLeft, 10); - if (fieldPaddingRight > fieldPaddingLeft) { - elementLeftPosition = left + width - height - (fieldPaddingRight - elementOffset + 2); - } - - return { - top: `${Math.round(elementTopPosition)}px`, - left: `${Math.round(elementLeftPosition)}px`, - height: `${Math.round(elementHeight)}px`, - width: `${Math.round(elementHeight)}px`, - }; - } - - /** - * Gets the position of the focused field and calculates the position - * of the overlay list based on the focused field's position and dimensions. - */ - private getOverlayListPosition() { - if (!this.focusedFieldData) { - return; - } - - const { top, left, width, height } = this.focusedFieldData.focusedFieldRects; - return { - width: `${Math.round(width)}px`, - top: `${Math.round(top + height)}px`, - left: `${Math.round(left)}px`, - }; - } - - /** - * Sets the focused field data to the data passed in the extension message. - * - * @param focusedFieldData - Contains the rects and styles of the focused field. - * @param sender - The sender of the extension message - */ - private setFocusedFieldData( - { focusedFieldData }: OverlayBackgroundExtensionMessage, - sender: chrome.runtime.MessageSender, - ) { - this.focusedFieldData = { ...focusedFieldData, tabId: sender.tab.id }; - } - - /** - * Updates the overlay's visibility based on the display property passed in the extension message. - * - * @param display - The display property of the overlay, either "block" or "none" - */ - private updateOverlayHidden({ display }: OverlayBackgroundExtensionMessage) { - if (!display) { - return; - } - - const portMessage = { command: "updateOverlayHidden", styles: { display } }; - - this.overlayButtonPort?.postMessage(portMessage); - this.overlayListPort?.postMessage(portMessage); - } - - /** - * Sends a message to the currently active tab to open the autofill overlay. - * - * @param isFocusingFieldElement - Identifies whether the field element should be focused when the overlay is opened - * @param isOpeningFullOverlay - Identifies whether the full overlay should be forced open regardless of other states - */ - private async openOverlay(isFocusingFieldElement = false, isOpeningFullOverlay = false) { - const currentTab = await BrowserApi.getTabFromCurrentWindowId(); - - await BrowserApi.tabSendMessageData(currentTab, "openAutofillOverlay", { - isFocusingFieldElement, - isOpeningFullOverlay, - authStatus: await this.getAuthStatus(), - }); - } - - /** - * Gets the overlay's visibility setting from the settings service. - */ - private async getOverlayVisibility(): Promise { - return await firstValueFrom(this.autofillSettingsService.inlineMenuVisibility$); - } - - /** - * Gets the user's authentication status from the auth service. If the user's - * authentication status has changed, the overlay button's authentication status - * will be updated and the overlay list's ciphers will be updated. - */ - private async getAuthStatus() { - const formerAuthStatus = this.userAuthStatus; - this.userAuthStatus = await this.authService.getAuthStatus(); - - if ( - this.userAuthStatus !== formerAuthStatus && - this.userAuthStatus === AuthenticationStatus.Unlocked - ) { - this.updateOverlayButtonAuthStatus(); - await this.updateOverlayCiphers(); - } - - return this.userAuthStatus; - } - - /** - * Sends a message to the overlay button to update its authentication status. - */ - private updateOverlayButtonAuthStatus() { - this.overlayButtonPort?.postMessage({ - command: "updateOverlayButtonAuthStatus", - authStatus: this.userAuthStatus, - }); - } - - /** - * Handles the overlay button being clicked. If the user is not authenticated, - * the vault will be unlocked. If the user is authenticated, the overlay will - * be opened. - * - * @param port - The port of the overlay button - */ - private handleOverlayButtonClicked(port: chrome.runtime.Port) { - if (this.userAuthStatus !== AuthenticationStatus.Unlocked) { - // FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling. - // eslint-disable-next-line @typescript-eslint/no-floating-promises - this.unlockVault(port); - return; - } - - // FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling. - // eslint-disable-next-line @typescript-eslint/no-floating-promises - this.openOverlay(false, true); - } - - /** - * Facilitates opening the unlock popout window. - * - * @param port - The port of the overlay list - */ - private async unlockVault(port: chrome.runtime.Port) { - const { sender } = port; - - this.closeOverlay(port); - const retryMessage: LockedVaultPendingNotificationsData = { - commandToRetry: { message: { command: "openAutofillOverlay" }, sender }, - target: "overlay.background", - }; - await BrowserApi.tabSendMessageData( - sender.tab, - "addToLockedVaultPendingNotifications", - retryMessage, - ); - await this.openUnlockPopout(sender.tab, true); - } - - /** - * Triggers the opening of a vault item popout window associated - * with the passed cipher ID. - * @param overlayCipherId - Cipher ID corresponding to the overlayLoginCiphers map. Does not correspond to the actual cipher's ID. - * @param sender - The sender of the port message - */ - private async viewSelectedCipher( - { overlayCipherId }: OverlayPortMessage, - { sender }: chrome.runtime.Port, - ) { - const cipher = this.overlayLoginCiphers.get(overlayCipherId); - if (!cipher) { - return; - } - - await this.openViewVaultItemPopout(sender.tab, { - cipherId: cipher.id, - action: SHOW_AUTOFILL_BUTTON, - }); - } - - /** - * Facilitates redirecting focus to the overlay list. - */ - private focusOverlayList() { - this.overlayListPort?.postMessage({ command: "focusOverlayList" }); - } - - /** - * Updates the authentication status for the user and opens the overlay if - * a followup command is present in the message. - * - * @param message - Extension message received from the `unlockCompleted` command - */ - private async unlockCompleted(message: OverlayBackgroundExtensionMessage) { - await this.getAuthStatus(); - - if (message.data?.commandToRetry?.message?.command === "openAutofillOverlay") { - await this.openOverlay(true); - } - } - - /** - * Gets the translations for the overlay page. - */ - private getTranslations() { - if (!this.overlayPageTranslations) { - this.overlayPageTranslations = { - locale: BrowserApi.getUILanguage(), - opensInANewWindow: this.i18nService.translate("opensInANewWindow"), - buttonPageTitle: this.i18nService.translate("bitwardenOverlayButton"), - toggleBitwardenVaultOverlay: this.i18nService.translate("toggleBitwardenVaultOverlay"), - listPageTitle: this.i18nService.translate("bitwardenVault"), - unlockYourAccount: this.i18nService.translate("unlockYourAccountToViewMatchingLogins"), - unlockAccount: this.i18nService.translate("unlockAccount"), - fillCredentialsFor: this.i18nService.translate("fillCredentialsFor"), - partialUsername: this.i18nService.translate("partialUsername"), - view: this.i18nService.translate("view"), - noItemsToShow: this.i18nService.translate("noItemsToShow"), - newItem: this.i18nService.translate("newItem"), - addNewVaultItem: this.i18nService.translate("addNewVaultItem"), - }; - } - - return this.overlayPageTranslations; - } - - /** - * Facilitates redirecting focus out of one of the - * overlay elements to elements on the page. - * - * @param direction - The direction to redirect focus to (either "next", "previous" or "current) - * @param sender - The sender of the port message - */ - private redirectOverlayFocusOut( - { direction }: OverlayPortMessage, - { sender }: chrome.runtime.Port, - ) { - if (!direction) { - return; - } - - // FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling. - // eslint-disable-next-line @typescript-eslint/no-floating-promises - BrowserApi.tabSendMessageData(sender.tab, "redirectOverlayFocusOut", { direction }); - } - - /** - * Triggers adding a new vault item from the overlay. Gathers data - * input by the user before calling to open the add/edit window. - * - * @param sender - The sender of the port message - */ - private getNewVaultItemDetails({ sender }: chrome.runtime.Port) { - void BrowserApi.tabSendMessage(sender.tab, { command: "addNewVaultItemFromOverlay" }); - } - - /** - * Handles adding a new vault item from the overlay. Gathers data login - * data captured in the extension message. - * - * @param login - The login data captured from the extension message - * @param sender - The sender of the extension message - */ - private async addNewVaultItem( - { login }: OverlayAddNewItemMessage, - sender: chrome.runtime.MessageSender, - ) { - if (!login) { - return; - } - - const uriView = new LoginUriView(); - uriView.uri = login.uri; - - const loginView = new LoginView(); - loginView.uris = [uriView]; - loginView.username = login.username || ""; - loginView.password = login.password || ""; - - const cipherView = new CipherView(); - cipherView.name = (Utils.getHostname(login.uri) || login.hostname).replace(/^www\./, ""); - cipherView.folderId = null; - cipherView.type = CipherType.Login; - cipherView.login = loginView; - - const activeUserId = await firstValueFrom( - this.accountService.activeAccount$.pipe(map((a) => a?.id)), - ); - await this.cipherService.setAddEditCipherInfo( - { - cipher: cipherView, - collectionIds: cipherView.collectionIds, - }, - activeUserId, - ); - - await this.openAddEditVaultItemPopout(sender.tab, { cipherId: cipherView.id }); - } - - /** - * Sets up the extension message listeners for the overlay. - */ - private setupExtensionMessageListeners() { - BrowserApi.messageListener("overlay.background", this.handleExtensionMessage); - BrowserApi.addListener(chrome.runtime.onConnect, this.handlePortOnConnect); - } - - /** - * Handles extension messages sent to the extension background. - * - * @param message - The message received from the extension - * @param sender - The sender of the message - * @param sendResponse - The response to send back to the sender - */ - private handleExtensionMessage = ( - message: OverlayBackgroundExtensionMessage, - sender: chrome.runtime.MessageSender, - sendResponse: (response?: any) => void, - ) => { - const handler: CallableFunction | undefined = this.extensionMessageHandlers[message?.command]; - if (!handler) { - return null; - } - - const messageResponse = handler({ message, sender }); - if (typeof messageResponse === "undefined") { - return null; - } - - // FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling. - // eslint-disable-next-line @typescript-eslint/no-floating-promises - Promise.resolve(messageResponse).then((response) => sendResponse(response)); - return true; - }; - - /** - * Handles the connection of a port to the extension background. - * - * @param port - The port that connected to the extension background - */ - private handlePortOnConnect = async (port: chrome.runtime.Port) => { - const isOverlayListPort = port.name === AutofillOverlayPort.List; - const isOverlayButtonPort = port.name === AutofillOverlayPort.Button; - if (!isOverlayListPort && !isOverlayButtonPort) { - return; - } - - this.storeOverlayPort(port); - port.onMessage.addListener(this.handleOverlayElementPortMessage); - port.postMessage({ - command: `initAutofillOverlay${isOverlayListPort ? "List" : "Button"}`, - authStatus: await this.getAuthStatus(), - styleSheetUrl: chrome.runtime.getURL(`overlay/${isOverlayListPort ? "list" : "button"}.css`), - theme: await firstValueFrom(this.themeStateService.selectedTheme$), - translations: this.getTranslations(), - ciphers: isOverlayListPort ? await this.getOverlayCipherData() : null, - }); - this.updateOverlayPosition( - { - overlayElement: isOverlayListPort - ? AutofillOverlayElement.List - : AutofillOverlayElement.Button, - }, - port.sender, - ); - }; - - /** - * Stores the connected overlay port and sets up any existing ports to be disconnected. - * - * @param port - The port to store -| */ - private storeOverlayPort(port: chrome.runtime.Port) { - if (port.name === AutofillOverlayPort.List) { - this.storeExpiredOverlayPort(this.overlayListPort); - this.overlayListPort = port; - return; - } - - if (port.name === AutofillOverlayPort.Button) { - this.storeExpiredOverlayPort(this.overlayButtonPort); - this.overlayButtonPort = port; - } - } - - /** - * When registering a new connection, we want to ensure that the port is disconnected. - * This method places an existing port in the expiredPorts array to be disconnected - * at a later time. - * - * @param port - The port to store in the expiredPorts array - */ - private storeExpiredOverlayPort(port: chrome.runtime.Port | null) { - if (port) { - this.expiredPorts.push(port); - } - } - - /** - * Handles messages sent to the overlay list or button ports. - * - * @param message - The message received from the port - * @param port - The port that sent the message - */ - private handleOverlayElementPortMessage = ( - message: OverlayBackgroundExtensionMessage, - port: chrome.runtime.Port, - ) => { - const command = message?.command; - let handler: CallableFunction | undefined; - - if (port.name === AutofillOverlayPort.Button) { - handler = this.overlayButtonPortMessageHandlers[command]; - } - - if (port.name === AutofillOverlayPort.List) { - handler = this.overlayListPortMessageHandlers[command]; - } - - if (!handler) { - return; - } - - handler({ message, port }); - }; -} - -export default LegacyOverlayBackground; diff --git a/apps/browser/src/autofill/deprecated/content/abstractions/autofill-init.deprecated.ts b/apps/browser/src/autofill/deprecated/content/abstractions/autofill-init.deprecated.ts deleted file mode 100644 index ed422822b36..00000000000 --- a/apps/browser/src/autofill/deprecated/content/abstractions/autofill-init.deprecated.ts +++ /dev/null @@ -1,41 +0,0 @@ -import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status"; - -import AutofillScript from "../../../models/autofill-script"; - -type AutofillExtensionMessage = { - command: string; - tab?: chrome.tabs.Tab; - sender?: string; - fillScript?: AutofillScript; - url?: string; - pageDetailsUrl?: string; - ciphers?: any; - data?: { - authStatus?: AuthenticationStatus; - isFocusingFieldElement?: boolean; - isOverlayCiphersPopulated?: boolean; - direction?: "previous" | "next"; - isOpeningFullOverlay?: boolean; - forceCloseOverlay?: boolean; - autofillOverlayVisibility?: number; - }; -}; - -type AutofillExtensionMessageParam = { message: AutofillExtensionMessage }; - -type AutofillExtensionMessageHandlers = { - [key: string]: CallableFunction; - collectPageDetails: ({ message }: AutofillExtensionMessageParam) => void; - collectPageDetailsImmediately: ({ message }: AutofillExtensionMessageParam) => void; - fillForm: ({ message }: AutofillExtensionMessageParam) => void; - openAutofillOverlay: ({ message }: AutofillExtensionMessageParam) => void; - closeAutofillOverlay: ({ message }: AutofillExtensionMessageParam) => void; - addNewVaultItemFromOverlay: () => void; - redirectOverlayFocusOut: ({ message }: AutofillExtensionMessageParam) => void; - updateIsOverlayCiphersPopulated: ({ message }: AutofillExtensionMessageParam) => void; - bgUnlockPopoutOpened: () => void; - bgVaultItemRepromptPopoutOpened: () => void; - updateAutofillOverlayVisibility: ({ message }: AutofillExtensionMessageParam) => void; -}; - -export { AutofillExtensionMessage, AutofillExtensionMessageHandlers }; diff --git a/apps/browser/src/autofill/deprecated/content/autofill-init.deprecated.spec.ts b/apps/browser/src/autofill/deprecated/content/autofill-init.deprecated.spec.ts deleted file mode 100644 index 96d5e85ca34..00000000000 --- a/apps/browser/src/autofill/deprecated/content/autofill-init.deprecated.spec.ts +++ /dev/null @@ -1,604 +0,0 @@ -import { mock } from "jest-mock-extended"; - -import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status"; -import { AutofillOverlayVisibility } from "@bitwarden/common/autofill/constants"; - -import { RedirectFocusDirection } from "../../enums/autofill-overlay.enum"; -import AutofillPageDetails from "../../models/autofill-page-details"; -import AutofillScript from "../../models/autofill-script"; -import { - flushPromises, - mockQuerySelectorAllDefinedCall, - sendMockExtensionMessage, -} from "../../spec/testing-utils"; -import AutofillOverlayContentServiceDeprecated from "../services/autofill-overlay-content.service.deprecated"; - -import { AutofillExtensionMessage } from "./abstractions/autofill-init.deprecated"; -import AutofillInitDeprecated from "./autofill-init.deprecated"; - -describe("AutofillInit", () => { - let autofillInit: AutofillInitDeprecated; - const autofillOverlayContentService = mock(); - const originalDocumentReadyState = document.readyState; - const mockQuerySelectorAll = mockQuerySelectorAllDefinedCall(); - - beforeEach(() => { - chrome.runtime.connect = jest.fn().mockReturnValue({ - onDisconnect: { - addListener: jest.fn(), - }, - }); - autofillInit = new AutofillInitDeprecated(autofillOverlayContentService); - window.IntersectionObserver = jest.fn(() => mock()); - }); - - afterEach(() => { - jest.resetModules(); - jest.clearAllMocks(); - Object.defineProperty(document, "readyState", { - value: originalDocumentReadyState, - writable: true, - }); - }); - - afterAll(() => { - mockQuerySelectorAll.mockRestore(); - }); - - describe("init", () => { - it("sets up the extension message listeners", () => { - jest.spyOn(autofillInit as any, "setupExtensionMessageListeners"); - - autofillInit.init(); - - expect(autofillInit["setupExtensionMessageListeners"]).toHaveBeenCalled(); - }); - - it("triggers a collection of page details if the document is in a `complete` ready state", () => { - jest.useFakeTimers(); - Object.defineProperty(document, "readyState", { value: "complete", writable: true }); - - autofillInit.init(); - jest.advanceTimersByTime(250); - - expect(chrome.runtime.sendMessage).toHaveBeenCalledWith( - { - command: "bgCollectPageDetails", - sender: "autofillInit", - }, - expect.any(Function), - ); - }); - - it("registers a window load listener to collect the page details if the document is not in a `complete` ready state", () => { - jest.spyOn(window, "addEventListener"); - Object.defineProperty(document, "readyState", { value: "loading", writable: true }); - - autofillInit.init(); - - expect(window.addEventListener).toHaveBeenCalledWith("load", expect.any(Function)); - }); - }); - - describe("setupExtensionMessageListeners", () => { - it("sets up a chrome runtime on message listener", () => { - jest.spyOn(chrome.runtime.onMessage, "addListener"); - - autofillInit["setupExtensionMessageListeners"](); - - expect(chrome.runtime.onMessage.addListener).toHaveBeenCalledWith( - autofillInit["handleExtensionMessage"], - ); - }); - }); - - describe("handleExtensionMessage", () => { - let message: AutofillExtensionMessage; - let sender: chrome.runtime.MessageSender; - const sendResponse = jest.fn(); - - beforeEach(() => { - message = { - command: "collectPageDetails", - tab: mock(), - sender: "sender", - }; - sender = mock(); - }); - - it("returns a undefined value if a extension message handler is not found with the given message command", () => { - message.command = "unknownCommand"; - - const response = autofillInit["handleExtensionMessage"](message, sender, sendResponse); - - expect(response).toBe(null); - }); - - it("returns a undefined value if the message handler does not return a response", async () => { - const response1 = await autofillInit["handleExtensionMessage"](message, sender, sendResponse); - await flushPromises(); - - expect(response1).not.toBe(false); - - message.command = "removeAutofillOverlay"; - message.fillScript = mock(); - - const response2 = autofillInit["handleExtensionMessage"](message, sender, sendResponse); - await flushPromises(); - - expect(response2).toBe(null); - }); - - it("returns a true value and calls sendResponse if the message handler returns a response", async () => { - message.command = "collectPageDetailsImmediately"; - const pageDetails: AutofillPageDetails = { - title: "title", - url: "http://example.com", - documentUrl: "documentUrl", - forms: {}, - fields: [], - collectedTimestamp: 0, - }; - jest - .spyOn(autofillInit["collectAutofillContentService"], "getPageDetails") - .mockResolvedValue(pageDetails); - - const response = await autofillInit["handleExtensionMessage"](message, sender, sendResponse); - await flushPromises(); - - expect(response).toBe(true); - expect(sendResponse).toHaveBeenCalledWith(pageDetails); - }); - - describe("extension message handlers", () => { - beforeEach(() => { - autofillInit.init(); - }); - - describe("collectPageDetails", () => { - it("sends the collected page details for autofill using a background script message", async () => { - const pageDetails: AutofillPageDetails = { - title: "title", - url: "http://example.com", - documentUrl: "documentUrl", - forms: {}, - fields: [], - collectedTimestamp: 0, - }; - const message = { - command: "collectPageDetails", - sender: "sender", - tab: mock(), - }; - jest - .spyOn(autofillInit["collectAutofillContentService"], "getPageDetails") - .mockResolvedValue(pageDetails); - - sendMockExtensionMessage(message, sender, sendResponse); - await flushPromises(); - - expect(chrome.runtime.sendMessage).toHaveBeenCalledWith({ - command: "collectPageDetailsResponse", - tab: message.tab, - details: pageDetails, - sender: message.sender, - }); - }); - }); - - describe("collectPageDetailsImmediately", () => { - it("returns collected page details for autofill if set to send the details in the response", async () => { - const pageDetails: AutofillPageDetails = { - title: "title", - url: "http://example.com", - documentUrl: "documentUrl", - forms: {}, - fields: [], - collectedTimestamp: 0, - }; - jest - .spyOn(autofillInit["collectAutofillContentService"], "getPageDetails") - .mockResolvedValue(pageDetails); - - sendMockExtensionMessage( - { command: "collectPageDetailsImmediately" }, - sender, - sendResponse, - ); - await flushPromises(); - - expect(autofillInit["collectAutofillContentService"].getPageDetails).toHaveBeenCalled(); - expect(sendResponse).toBeCalledWith(pageDetails); - expect(chrome.runtime.sendMessage).not.toHaveBeenCalledWith({ - command: "collectPageDetailsResponse", - tab: message.tab, - details: pageDetails, - sender: message.sender, - }); - }); - }); - - describe("fillForm", () => { - let fillScript: AutofillScript; - beforeEach(() => { - fillScript = mock(); - jest.spyOn(autofillInit["insertAutofillContentService"], "fillForm").mockImplementation(); - }); - - it("skips calling the InsertAutofillContentService and does not fill the form if the url to fill is not equal to the current tab url", async () => { - const fillScript = mock(); - const message = { - command: "fillForm", - fillScript, - pageDetailsUrl: "https://a-different-url.com", - }; - - sendMockExtensionMessage(message); - await flushPromises(); - - expect(autofillInit["insertAutofillContentService"].fillForm).not.toHaveBeenCalledWith( - fillScript, - ); - }); - - it("calls the InsertAutofillContentService to fill the form", async () => { - sendMockExtensionMessage({ - command: "fillForm", - fillScript, - pageDetailsUrl: window.location.href, - }); - await flushPromises(); - - expect(autofillInit["insertAutofillContentService"].fillForm).toHaveBeenCalledWith( - fillScript, - ); - }); - - it("removes the overlay when filling the form", async () => { - const blurAndRemoveOverlaySpy = jest.spyOn(autofillInit as any, "blurAndRemoveOverlay"); - sendMockExtensionMessage({ - command: "fillForm", - fillScript, - pageDetailsUrl: window.location.href, - }); - await flushPromises(); - - expect(blurAndRemoveOverlaySpy).toHaveBeenCalled(); - }); - - it("updates the isCurrentlyFilling property of the overlay to true after filling", async () => { - jest.useFakeTimers(); - jest.spyOn(autofillInit as any, "updateOverlayIsCurrentlyFilling"); - jest - .spyOn(autofillInit["autofillOverlayContentService"], "focusMostRecentOverlayField") - .mockImplementation(); - - sendMockExtensionMessage({ - command: "fillForm", - fillScript, - pageDetailsUrl: window.location.href, - }); - await flushPromises(); - jest.advanceTimersByTime(300); - - expect(autofillInit["updateOverlayIsCurrentlyFilling"]).toHaveBeenNthCalledWith(1, true); - expect(autofillInit["insertAutofillContentService"].fillForm).toHaveBeenCalledWith( - fillScript, - ); - expect(autofillInit["updateOverlayIsCurrentlyFilling"]).toHaveBeenNthCalledWith(2, false); - }); - - it("skips attempting to focus the most recent field if the autofillOverlayContentService is not present", async () => { - jest.useFakeTimers(); - const newAutofillInit = new AutofillInitDeprecated(undefined); - newAutofillInit.init(); - jest.spyOn(newAutofillInit as any, "updateOverlayIsCurrentlyFilling"); - jest - .spyOn(newAutofillInit["insertAutofillContentService"], "fillForm") - .mockImplementation(); - - sendMockExtensionMessage({ - command: "fillForm", - fillScript, - pageDetailsUrl: window.location.href, - }); - await flushPromises(); - jest.advanceTimersByTime(300); - - expect(newAutofillInit["updateOverlayIsCurrentlyFilling"]).toHaveBeenNthCalledWith( - 1, - true, - ); - expect(newAutofillInit["insertAutofillContentService"].fillForm).toHaveBeenCalledWith( - fillScript, - ); - expect(newAutofillInit["updateOverlayIsCurrentlyFilling"]).not.toHaveBeenNthCalledWith( - 2, - false, - ); - }); - }); - - describe("openAutofillOverlay", () => { - const message = { - command: "openAutofillOverlay", - data: { - isFocusingFieldElement: true, - isOpeningFullOverlay: true, - authStatus: AuthenticationStatus.Unlocked, - }, - }; - - it("skips attempting to open the autofill overlay if the autofillOverlayContentService is not present", () => { - const newAutofillInit = new AutofillInitDeprecated(undefined); - newAutofillInit.init(); - - sendMockExtensionMessage(message); - - expect(newAutofillInit["autofillOverlayContentService"]).toBe(undefined); - }); - - it("opens the autofill overlay", () => { - sendMockExtensionMessage(message); - - expect( - autofillInit["autofillOverlayContentService"].openAutofillOverlay, - ).toHaveBeenCalledWith({ - isFocusingFieldElement: message.data.isFocusingFieldElement, - isOpeningFullOverlay: message.data.isOpeningFullOverlay, - authStatus: message.data.authStatus, - }); - }); - }); - - describe("closeAutofillOverlay", () => { - beforeEach(() => { - autofillInit["autofillOverlayContentService"].isFieldCurrentlyFocused = false; - autofillInit["autofillOverlayContentService"].isCurrentlyFilling = false; - }); - - it("skips attempting to remove the overlay if the autofillOverlayContentService is not present", () => { - const newAutofillInit = new AutofillInitDeprecated(undefined); - newAutofillInit.init(); - jest.spyOn(newAutofillInit as any, "removeAutofillOverlay"); - - sendMockExtensionMessage({ - command: "closeAutofillOverlay", - data: { forceCloseOverlay: false }, - }); - - expect(newAutofillInit["autofillOverlayContentService"]).toBe(undefined); - }); - - it("removes the autofill overlay if the message flags a forced closure", () => { - sendMockExtensionMessage({ - command: "closeAutofillOverlay", - data: { forceCloseOverlay: true }, - }); - - expect( - autofillInit["autofillOverlayContentService"].removeAutofillOverlay, - ).toHaveBeenCalled(); - }); - - it("ignores the message if a field is currently focused", () => { - autofillInit["autofillOverlayContentService"].isFieldCurrentlyFocused = true; - - sendMockExtensionMessage({ command: "closeAutofillOverlay" }); - - expect( - autofillInit["autofillOverlayContentService"].removeAutofillOverlayList, - ).not.toHaveBeenCalled(); - expect( - autofillInit["autofillOverlayContentService"].removeAutofillOverlay, - ).not.toHaveBeenCalled(); - }); - - it("removes the autofill overlay list if the overlay is currently filling", () => { - autofillInit["autofillOverlayContentService"].isCurrentlyFilling = true; - - sendMockExtensionMessage({ command: "closeAutofillOverlay" }); - - expect( - autofillInit["autofillOverlayContentService"].removeAutofillOverlayList, - ).toHaveBeenCalled(); - expect( - autofillInit["autofillOverlayContentService"].removeAutofillOverlay, - ).not.toHaveBeenCalled(); - }); - - it("removes the entire overlay if the overlay is not currently filling", () => { - sendMockExtensionMessage({ command: "closeAutofillOverlay" }); - - expect( - autofillInit["autofillOverlayContentService"].removeAutofillOverlayList, - ).not.toHaveBeenCalled(); - expect( - autofillInit["autofillOverlayContentService"].removeAutofillOverlay, - ).toHaveBeenCalled(); - }); - }); - - describe("addNewVaultItemFromOverlay", () => { - it("will not add a new vault item if the autofillOverlayContentService is not present", () => { - const newAutofillInit = new AutofillInitDeprecated(undefined); - newAutofillInit.init(); - - sendMockExtensionMessage({ command: "addNewVaultItemFromOverlay" }); - - expect(newAutofillInit["autofillOverlayContentService"]).toBe(undefined); - }); - - it("will add a new vault item", () => { - sendMockExtensionMessage({ command: "addNewVaultItemFromOverlay" }); - - expect(autofillInit["autofillOverlayContentService"].addNewVaultItem).toHaveBeenCalled(); - }); - }); - - describe("redirectOverlayFocusOut", () => { - const message = { - command: "redirectOverlayFocusOut", - data: { - direction: RedirectFocusDirection.Next, - }, - }; - - it("ignores the message to redirect focus if the autofillOverlayContentService does not exist", () => { - const newAutofillInit = new AutofillInitDeprecated(undefined); - newAutofillInit.init(); - - sendMockExtensionMessage(message); - - expect(newAutofillInit["autofillOverlayContentService"]).toBe(undefined); - }); - - it("redirects the overlay focus", () => { - sendMockExtensionMessage(message); - - expect( - autofillInit["autofillOverlayContentService"].redirectOverlayFocusOut, - ).toHaveBeenCalledWith(message.data.direction); - }); - }); - - describe("updateIsOverlayCiphersPopulated", () => { - const message = { - command: "updateIsOverlayCiphersPopulated", - data: { - isOverlayCiphersPopulated: true, - }, - }; - - it("skips updating whether the ciphers are populated if the autofillOverlayContentService does note exist", () => { - const newAutofillInit = new AutofillInitDeprecated(undefined); - newAutofillInit.init(); - - sendMockExtensionMessage(message); - - expect(newAutofillInit["autofillOverlayContentService"]).toBe(undefined); - }); - - it("updates whether the overlay ciphers are populated", () => { - sendMockExtensionMessage(message); - - expect(autofillInit["autofillOverlayContentService"].isOverlayCiphersPopulated).toEqual( - message.data.isOverlayCiphersPopulated, - ); - }); - }); - - describe("bgUnlockPopoutOpened", () => { - it("skips attempting to blur and remove the overlay if the autofillOverlayContentService is not present", () => { - const newAutofillInit = new AutofillInitDeprecated(undefined); - newAutofillInit.init(); - jest.spyOn(newAutofillInit as any, "removeAutofillOverlay"); - - sendMockExtensionMessage({ command: "bgUnlockPopoutOpened" }); - - expect(newAutofillInit["autofillOverlayContentService"]).toBe(undefined); - expect(newAutofillInit["removeAutofillOverlay"]).not.toHaveBeenCalled(); - }); - - it("blurs the most recently focused feel and remove the autofill overlay", () => { - jest.spyOn(autofillInit["autofillOverlayContentService"], "blurMostRecentOverlayField"); - jest.spyOn(autofillInit as any, "removeAutofillOverlay"); - - sendMockExtensionMessage({ command: "bgUnlockPopoutOpened" }); - - expect( - autofillInit["autofillOverlayContentService"].blurMostRecentOverlayField, - ).toHaveBeenCalled(); - expect(autofillInit["removeAutofillOverlay"]).toHaveBeenCalled(); - }); - }); - - describe("bgVaultItemRepromptPopoutOpened", () => { - it("skips attempting to blur and remove the overlay if the autofillOverlayContentService is not present", () => { - const newAutofillInit = new AutofillInitDeprecated(undefined); - newAutofillInit.init(); - jest.spyOn(newAutofillInit as any, "removeAutofillOverlay"); - - sendMockExtensionMessage({ command: "bgVaultItemRepromptPopoutOpened" }); - - expect(newAutofillInit["autofillOverlayContentService"]).toBe(undefined); - expect(newAutofillInit["removeAutofillOverlay"]).not.toHaveBeenCalled(); - }); - - it("blurs the most recently focused feel and remove the autofill overlay", () => { - jest.spyOn(autofillInit["autofillOverlayContentService"], "blurMostRecentOverlayField"); - jest.spyOn(autofillInit as any, "removeAutofillOverlay"); - - sendMockExtensionMessage({ command: "bgVaultItemRepromptPopoutOpened" }); - - expect( - autofillInit["autofillOverlayContentService"].blurMostRecentOverlayField, - ).toHaveBeenCalled(); - expect(autofillInit["removeAutofillOverlay"]).toHaveBeenCalled(); - }); - }); - - describe("updateAutofillOverlayVisibility", () => { - beforeEach(() => { - autofillInit["autofillOverlayContentService"].autofillOverlayVisibility = - AutofillOverlayVisibility.OnButtonClick; - }); - - it("skips attempting to update the overlay visibility if the autofillOverlayVisibility data value is not present", () => { - sendMockExtensionMessage({ - command: "updateAutofillOverlayVisibility", - data: {}, - }); - - expect(autofillInit["autofillOverlayContentService"].autofillOverlayVisibility).toEqual( - AutofillOverlayVisibility.OnButtonClick, - ); - }); - - it("updates the overlay visibility value", () => { - const message = { - command: "updateAutofillOverlayVisibility", - data: { - autofillOverlayVisibility: AutofillOverlayVisibility.Off, - }, - }; - - sendMockExtensionMessage(message); - - expect(autofillInit["autofillOverlayContentService"].autofillOverlayVisibility).toEqual( - message.data.autofillOverlayVisibility, - ); - }); - }); - }); - }); - - describe("destroy", () => { - it("clears the timeout used to collect page details on load", () => { - jest.spyOn(window, "clearTimeout"); - - autofillInit.init(); - autofillInit.destroy(); - - expect(window.clearTimeout).toHaveBeenCalledWith( - autofillInit["collectPageDetailsOnLoadTimeout"], - ); - }); - - it("removes the extension message listeners", () => { - autofillInit.destroy(); - - expect(chrome.runtime.onMessage.removeListener).toHaveBeenCalledWith( - autofillInit["handleExtensionMessage"], - ); - }); - - it("destroys the collectAutofillContentService", () => { - jest.spyOn(autofillInit["collectAutofillContentService"], "destroy"); - - autofillInit.destroy(); - - expect(autofillInit["collectAutofillContentService"].destroy).toHaveBeenCalled(); - }); - }); -}); diff --git a/apps/browser/src/autofill/deprecated/content/autofill-init.deprecated.ts b/apps/browser/src/autofill/deprecated/content/autofill-init.deprecated.ts deleted file mode 100644 index fac9c0852f5..00000000000 --- a/apps/browser/src/autofill/deprecated/content/autofill-init.deprecated.ts +++ /dev/null @@ -1,315 +0,0 @@ -// FIXME: Update this file to be type safe and remove this and next line -// @ts-strict-ignore -import { AutofillInit } from "../../content/abstractions/autofill-init"; -import AutofillPageDetails from "../../models/autofill-page-details"; -import { CollectAutofillContentService } from "../../services/collect-autofill-content.service"; -import DomElementVisibilityService from "../../services/dom-element-visibility.service"; -import { DomQueryService } from "../../services/dom-query.service"; -import InsertAutofillContentService from "../../services/insert-autofill-content.service"; -import { sendExtensionMessage } from "../../utils"; -import { LegacyAutofillOverlayContentService } from "../services/abstractions/autofill-overlay-content.service"; - -import { - AutofillExtensionMessage, - AutofillExtensionMessageHandlers, -} from "./abstractions/autofill-init.deprecated"; - -class LegacyAutofillInit implements AutofillInit { - private readonly autofillOverlayContentService: LegacyAutofillOverlayContentService | undefined; - private readonly domElementVisibilityService: DomElementVisibilityService; - private readonly collectAutofillContentService: CollectAutofillContentService; - private readonly insertAutofillContentService: InsertAutofillContentService; - private collectPageDetailsOnLoadTimeout: number | NodeJS.Timeout | undefined; - private readonly extensionMessageHandlers: AutofillExtensionMessageHandlers = { - collectPageDetails: ({ message }) => this.collectPageDetails(message), - collectPageDetailsImmediately: ({ message }) => this.collectPageDetails(message, true), - fillForm: ({ message }) => this.fillForm(message), - openAutofillOverlay: ({ message }) => this.openAutofillOverlay(message), - closeAutofillOverlay: ({ message }) => this.removeAutofillOverlay(message), - addNewVaultItemFromOverlay: () => this.addNewVaultItemFromOverlay(), - redirectOverlayFocusOut: ({ message }) => this.redirectOverlayFocusOut(message), - updateIsOverlayCiphersPopulated: ({ message }) => this.updateIsOverlayCiphersPopulated(message), - bgUnlockPopoutOpened: () => this.blurAndRemoveOverlay(), - bgVaultItemRepromptPopoutOpened: () => this.blurAndRemoveOverlay(), - updateAutofillOverlayVisibility: ({ message }) => this.updateAutofillOverlayVisibility(message), - }; - - /** - * AutofillInit constructor. Initializes the DomElementVisibilityService, - * CollectAutofillContentService and InsertAutofillContentService classes. - * - * @param autofillOverlayContentService - The autofill overlay content service, potentially undefined. - */ - constructor(autofillOverlayContentService?: LegacyAutofillOverlayContentService) { - this.autofillOverlayContentService = autofillOverlayContentService; - this.domElementVisibilityService = new DomElementVisibilityService(); - const domQueryService = new DomQueryService(); - this.collectAutofillContentService = new CollectAutofillContentService( - this.domElementVisibilityService, - domQueryService, - this.autofillOverlayContentService, - ); - this.insertAutofillContentService = new InsertAutofillContentService( - this.domElementVisibilityService, - this.collectAutofillContentService, - ); - } - - /** - * Initializes the autofill content script, setting up - * the extension message listeners. This method should - * be called once when the content script is loaded. - */ - init() { - this.setupExtensionMessageListeners(); - this.autofillOverlayContentService?.init(); - this.collectPageDetailsOnLoad(); - } - - /** - * Triggers a collection of the page details from the - * background script, ensuring that autofill is ready - * to act on the page. - */ - private collectPageDetailsOnLoad() { - const sendCollectDetailsMessage = () => { - this.clearCollectPageDetailsOnLoadTimeout(); - this.collectPageDetailsOnLoadTimeout = setTimeout( - () => sendExtensionMessage("bgCollectPageDetails", { sender: "autofillInit" }), - 250, - ); - }; - - if (globalThis.document.readyState === "complete") { - sendCollectDetailsMessage(); - } - - globalThis.addEventListener("load", sendCollectDetailsMessage); - } - - /** - * Collects the page details and sends them to the - * extension background script. If the `sendDetailsInResponse` - * parameter is set to true, the page details will be - * returned to facilitate sending the details in the - * response to the extension message. - * - * @param message - The extension message. - * @param sendDetailsInResponse - Determines whether to send the details in the response. - */ - private async collectPageDetails( - message: AutofillExtensionMessage, - sendDetailsInResponse = false, - ): Promise { - const pageDetails: AutofillPageDetails = - await this.collectAutofillContentService.getPageDetails(); - if (sendDetailsInResponse) { - return pageDetails; - } - - void chrome.runtime.sendMessage({ - command: "collectPageDetailsResponse", - tab: message.tab, - details: pageDetails, - sender: message.sender, - }); - } - - /** - * Fills the form with the given fill script. - * - * @param {AutofillExtensionMessage} message - */ - private async fillForm({ fillScript, pageDetailsUrl }: AutofillExtensionMessage) { - if ((document.defaultView || window).location.href !== pageDetailsUrl) { - return; - } - - this.blurAndRemoveOverlay(); - this.updateOverlayIsCurrentlyFilling(true); - await this.insertAutofillContentService.fillForm(fillScript); - - if (!this.autofillOverlayContentService) { - return; - } - - setTimeout(() => this.updateOverlayIsCurrentlyFilling(false), 250); - } - - /** - * Handles updating the overlay is currently filling value. - * - * @param isCurrentlyFilling - Indicates if the overlay is currently filling - */ - private updateOverlayIsCurrentlyFilling(isCurrentlyFilling: boolean) { - if (!this.autofillOverlayContentService) { - return; - } - - this.autofillOverlayContentService.isCurrentlyFilling = isCurrentlyFilling; - } - - /** - * Opens the autofill overlay. - * - * @param data - The extension message data. - */ - private openAutofillOverlay({ data }: AutofillExtensionMessage) { - if (!this.autofillOverlayContentService) { - return; - } - - this.autofillOverlayContentService.openAutofillOverlay(data); - } - - /** - * Blurs the most recent overlay field and removes the overlay. Used - * in cases where the background unlock or vault item reprompt popout - * is opened. - */ - private blurAndRemoveOverlay() { - if (!this.autofillOverlayContentService) { - return; - } - - this.autofillOverlayContentService.blurMostRecentOverlayField(); - this.removeAutofillOverlay(); - } - - /** - * Removes the autofill overlay if the field is not currently focused. - * If the autofill is currently filling, only the overlay list will be - * removed. - */ - private removeAutofillOverlay(message?: AutofillExtensionMessage) { - if (message?.data?.forceCloseOverlay) { - this.autofillOverlayContentService?.removeAutofillOverlay(); - return; - } - - if ( - !this.autofillOverlayContentService || - this.autofillOverlayContentService.isFieldCurrentlyFocused - ) { - return; - } - - if (this.autofillOverlayContentService.isCurrentlyFilling) { - this.autofillOverlayContentService.removeAutofillOverlayList(); - return; - } - - this.autofillOverlayContentService.removeAutofillOverlay(); - } - - /** - * Adds a new vault item from the overlay. - */ - private addNewVaultItemFromOverlay() { - if (!this.autofillOverlayContentService) { - return; - } - - this.autofillOverlayContentService.addNewVaultItem(); - } - - /** - * Redirects the overlay focus out of an overlay iframe. - * - * @param data - Contains the direction to redirect the focus. - */ - private redirectOverlayFocusOut({ data }: AutofillExtensionMessage) { - if (!this.autofillOverlayContentService) { - return; - } - - this.autofillOverlayContentService.redirectOverlayFocusOut(data?.direction); - } - - /** - * Updates whether the current tab has ciphers that can populate the overlay list - * - * @param data - Contains the isOverlayCiphersPopulated value - * - */ - private updateIsOverlayCiphersPopulated({ data }: AutofillExtensionMessage) { - if (!this.autofillOverlayContentService) { - return; - } - - this.autofillOverlayContentService.isOverlayCiphersPopulated = Boolean( - data?.isOverlayCiphersPopulated, - ); - } - - /** - * Updates the autofill overlay visibility. - * - * @param data - Contains the autoFillOverlayVisibility value - */ - private updateAutofillOverlayVisibility({ data }: AutofillExtensionMessage) { - if (!this.autofillOverlayContentService || isNaN(data?.autofillOverlayVisibility)) { - return; - } - - this.autofillOverlayContentService.autofillOverlayVisibility = data?.autofillOverlayVisibility; - } - - /** - * Clears the send collect details message timeout. - */ - private clearCollectPageDetailsOnLoadTimeout() { - if (this.collectPageDetailsOnLoadTimeout) { - clearTimeout(this.collectPageDetailsOnLoadTimeout); - } - } - - /** - * Sets up the extension message listeners for the content script. - */ - private setupExtensionMessageListeners() { - chrome.runtime.onMessage.addListener(this.handleExtensionMessage); - } - - /** - * Handles the extension messages sent to the content script. - * - * @param message - The extension message. - * @param sender - The message sender. - * @param sendResponse - The send response callback. - */ - private handleExtensionMessage = ( - message: AutofillExtensionMessage, - sender: chrome.runtime.MessageSender, - sendResponse: (response?: any) => void, - ): boolean => { - const command: string = message.command; - const handler: CallableFunction | undefined = this.extensionMessageHandlers[command]; - if (!handler) { - return null; - } - - const messageResponse = handler({ message, sender }); - if (typeof messageResponse === "undefined") { - return null; - } - - // FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling. - // eslint-disable-next-line @typescript-eslint/no-floating-promises - Promise.resolve(messageResponse).then((response) => sendResponse(response)); - return true; - }; - - /** - * Handles destroying the autofill init content script. Removes all - * listeners, timeouts, and object instances to prevent memory leaks. - */ - destroy() { - this.clearCollectPageDetailsOnLoadTimeout(); - chrome.runtime.onMessage.removeListener(this.handleExtensionMessage); - this.collectAutofillContentService.destroy(); - this.autofillOverlayContentService?.destroy(); - } -} - -export default LegacyAutofillInit; diff --git a/apps/browser/src/autofill/deprecated/content/bootstrap-legacy-autofill-overlay.ts b/apps/browser/src/autofill/deprecated/content/bootstrap-legacy-autofill-overlay.ts deleted file mode 100644 index 66d672172ae..00000000000 --- a/apps/browser/src/autofill/deprecated/content/bootstrap-legacy-autofill-overlay.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { setupAutofillInitDisconnectAction } from "../../utils"; -import LegacyAutofillOverlayContentService from "../services/autofill-overlay-content.service.deprecated"; - -import LegacyAutofillInit from "./autofill-init.deprecated"; - -(function (windowContext) { - if (!windowContext.bitwardenAutofillInit) { - const autofillOverlayContentService = new LegacyAutofillOverlayContentService(); - windowContext.bitwardenAutofillInit = new LegacyAutofillInit(autofillOverlayContentService); - setupAutofillInitDisconnectAction(windowContext); - - windowContext.bitwardenAutofillInit.init(); - } -})(window); diff --git a/apps/browser/src/autofill/deprecated/overlay/abstractions/autofill-overlay-button.deprecated.ts b/apps/browser/src/autofill/deprecated/overlay/abstractions/autofill-overlay-button.deprecated.ts deleted file mode 100644 index b6b22be9439..00000000000 --- a/apps/browser/src/autofill/deprecated/overlay/abstractions/autofill-overlay-button.deprecated.ts +++ /dev/null @@ -1,29 +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; -}; - -type OverlayButtonWindowMessageHandlers = { - [key: string]: CallableFunction; - initAutofillOverlayButton: ({ message }: { message: InitAutofillOverlayButtonMessage }) => void; - checkAutofillOverlayButtonFocused: () => void; - updateAutofillOverlayButtonAuthStatus: ({ - message, - }: { - message: UpdateAuthStatusMessage; - }) => void; - updateOverlayPageColorScheme: ({ message }: { message: OverlayButtonMessage }) => void; -}; - -export { - UpdateAuthStatusMessage, - OverlayButtonMessage, - InitAutofillOverlayButtonMessage, - OverlayButtonWindowMessageHandlers, -}; diff --git a/apps/browser/src/autofill/deprecated/overlay/abstractions/autofill-overlay-iframe.service.deprecated.ts b/apps/browser/src/autofill/deprecated/overlay/abstractions/autofill-overlay-iframe.service.deprecated.ts deleted file mode 100644 index 0c4160a0709..00000000000 --- a/apps/browser/src/autofill/deprecated/overlay/abstractions/autofill-overlay-iframe.service.deprecated.ts +++ /dev/null @@ -1,33 +0,0 @@ -type AutofillOverlayIframeExtensionMessage = { - command: string; - styles?: Partial; - theme?: string; -}; - -type AutofillOverlayIframeWindowMessageHandlers = { - [key: string]: CallableFunction; - updateAutofillOverlayListHeight: (message: AutofillOverlayIframeExtensionMessage) => void; - getPageColorScheme: () => void; -}; - -type AutofillOverlayIframeExtensionMessageParam = { - message: AutofillOverlayIframeExtensionMessage; -}; - -type BackgroundPortMessageHandlers = { - [key: string]: CallableFunction; - initAutofillOverlayList: ({ message }: AutofillOverlayIframeExtensionMessageParam) => void; - updateIframePosition: ({ message }: AutofillOverlayIframeExtensionMessageParam) => void; - updateOverlayHidden: ({ message }: AutofillOverlayIframeExtensionMessageParam) => void; -}; - -interface AutofillOverlayIframeService { - initOverlayIframe(initStyles: Partial, ariaAlert?: string): void; -} - -export { - AutofillOverlayIframeExtensionMessage, - AutofillOverlayIframeWindowMessageHandlers, - BackgroundPortMessageHandlers, - AutofillOverlayIframeService, -}; diff --git a/apps/browser/src/autofill/deprecated/overlay/abstractions/autofill-overlay-list.deprecated.ts b/apps/browser/src/autofill/deprecated/overlay/abstractions/autofill-overlay-list.deprecated.ts deleted file mode 100644 index 83578b13043..00000000000 --- a/apps/browser/src/autofill/deprecated/overlay/abstractions/autofill-overlay-list.deprecated.ts +++ /dev/null @@ -1,31 +0,0 @@ -import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status"; - -import { OverlayCipherData } from "../../background/abstractions/overlay.background.deprecated"; - -type OverlayListMessage = { command: string }; - -type UpdateOverlayListCiphersMessage = OverlayListMessage & { - ciphers: OverlayCipherData[]; -}; - -type InitAutofillOverlayListMessage = OverlayListMessage & { - authStatus: AuthenticationStatus; - styleSheetUrl: string; - theme: string; - translations: Record; - ciphers?: OverlayCipherData[]; -}; - -type OverlayListWindowMessageHandlers = { - [key: string]: CallableFunction; - initAutofillOverlayList: ({ message }: { message: InitAutofillOverlayListMessage }) => void; - checkAutofillOverlayListFocused: () => void; - updateOverlayListCiphers: ({ message }: { message: UpdateOverlayListCiphersMessage }) => void; - focusOverlayList: () => void; -}; - -export { - UpdateOverlayListCiphersMessage, - InitAutofillOverlayListMessage, - OverlayListWindowMessageHandlers, -}; diff --git a/apps/browser/src/autofill/deprecated/overlay/abstractions/autofill-overlay-page-element.deprecated.ts b/apps/browser/src/autofill/deprecated/overlay/abstractions/autofill-overlay-page-element.deprecated.ts deleted file mode 100644 index 368ae4e7303..00000000000 --- a/apps/browser/src/autofill/deprecated/overlay/abstractions/autofill-overlay-page-element.deprecated.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { OverlayButtonWindowMessageHandlers } from "./autofill-overlay-button.deprecated"; -import { OverlayListWindowMessageHandlers } from "./autofill-overlay-list.deprecated"; - -type WindowMessageHandlers = OverlayButtonWindowMessageHandlers | OverlayListWindowMessageHandlers; - -type AutofillOverlayPageElementWindowMessage = { - [key: string]: any; - command: string; - overlayCipherId?: string; - height?: number; -}; - -export { WindowMessageHandlers, AutofillOverlayPageElementWindowMessage }; diff --git a/apps/browser/src/autofill/deprecated/overlay/iframe-content/__snapshots__/autofill-overlay-iframe.service.deprecated.spec.ts.snap b/apps/browser/src/autofill/deprecated/overlay/iframe-content/__snapshots__/autofill-overlay-iframe.service.deprecated.spec.ts.snap deleted file mode 100644 index 132bd968899..00000000000 --- a/apps/browser/src/autofill/deprecated/overlay/iframe-content/__snapshots__/autofill-overlay-iframe.service.deprecated.spec.ts.snap +++ /dev/null @@ -1,23 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`AutofillOverlayIframeService initOverlayIframe creates an aria alert element if the ariaAlert param is passed 1`] = ` -
- aria alert -
-`; - -exports[`AutofillOverlayIframeService initOverlayIframe sets up the iframe's attributes 1`] = ` -