diff --git a/apps/browser/src/_locales/en/messages.json b/apps/browser/src/_locales/en/messages.json index a29a42a07ad0..d908b267f4b0 100644 --- a/apps/browser/src/_locales/en/messages.json +++ b/apps/browser/src/_locales/en/messages.json @@ -3046,6 +3046,10 @@ "message": "Unlock your account to view matching logins", "description": "Text to display in overlay when the account is locked." }, + "unlockYourAccountToViewAutofillSuggestions": { + "message": "Unlock your account to view autofill suggestions", + "description": "Text to display in overlay when the account is locked." + }, "unlockAccount": { "message": "Unlock account", "description": "Button text to display in overlay when the account is locked." @@ -3070,6 +3074,22 @@ "message": "Add new vault item", "description": "Screen reader text (aria-label) for new item button in overlay" }, + "newLogin": { + "message": "New login", + "description": "Button text to display within inline menu when there are no matching items on a login field" + }, + "addNewLoginItem": { + "message": "Add new vault login item", + "description": "Screen reader text (aria-label) for new login button within inline menu" + }, + "newCard": { + "message": "New card", + "description": "Button text to display within inline menu when there are no matching items on a credit card field" + }, + "addNewCardItem": { + "message": "Add new vault card item", + "description": "Screen reader text (aria-label) for new card button within inline menu" + }, "bitwardenOverlayMenuAvailable": { "message": "Bitwarden auto-fill menu available. Press the down arrow key to select.", "description": "Screen reader text for announcing when the overlay opens on the page" @@ -3743,6 +3763,10 @@ } } }, + "cardNumberEndsWith": { + "message": "card number ends with", + "description": "Used within the inline menu to provide an aria description when users are attempting to fill a card cipher." + }, "loginCredentials": { "message": "Login credentials" }, diff --git a/apps/browser/src/autofill/background/abstractions/overlay.background.ts b/apps/browser/src/autofill/background/abstractions/overlay.background.ts index 3c67872e238d..763261cae266 100644 --- a/apps/browser/src/autofill/background/abstractions/overlay.background.ts +++ b/apps/browser/src/autofill/background/abstractions/overlay.background.ts @@ -34,6 +34,7 @@ export type WebsiteIconData = { export type FocusedFieldData = { focusedFieldStyles: Partial; focusedFieldRects: Partial; + filledByCipherType?: CipherType; tabId?: number; frameId?: number; }; @@ -50,13 +51,26 @@ export type InlineMenuPosition = { list?: InlineMenuElementPosition; }; +export type NewLoginCipherData = { + uri?: string; + hostname: string; + username: string; + password: string; +}; + +export type NewCardCipherData = { + cardholderName: string; + number: string; + expirationMonth: string; + expirationYear: string; + expirationDate?: string; + cvv: string; +}; + export type OverlayAddNewItemMessage = { - login?: { - uri?: string; - hostname: string; - username: string; - password: string; - }; + addNewCipherType?: CipherType; + login?: NewLoginCipherData; + card?: NewCardCipherData; }; export type CloseInlineMenuMessage = { @@ -91,6 +105,7 @@ export type OverlayPortMessage = { command: string; direction?: string; inlineMenuCipherId?: string; + addNewCipherType?: CipherType; }; export type InlineMenuCipherData = { @@ -178,7 +193,7 @@ export type InlineMenuListPortMessageHandlers = { autofillInlineMenuBlurred: () => void; unlockVault: ({ port }: PortConnectionParam) => void; fillAutofillInlineMenuCipher: ({ message, port }: PortOnMessageHandlerParams) => void; - addNewVaultItem: ({ port }: PortConnectionParam) => void; + addNewVaultItem: ({ message, port }: PortOnMessageHandlerParams) => void; viewSelectedCipher: ({ message, port }: PortOnMessageHandlerParams) => void; redirectAutofillInlineMenuFocusOut: ({ message, port }: PortOnMessageHandlerParams) => void; updateAutofillInlineMenuListHeight: ({ message, port }: PortOnMessageHandlerParams) => void; @@ -187,5 +202,5 @@ export type InlineMenuListPortMessageHandlers = { export interface OverlayBackground { init(): Promise; removePageDetails(tabId: number): void; - updateOverlayCiphers(): Promise; + updateOverlayCiphers(updateAllCipherTypes?: boolean): Promise; } diff --git a/apps/browser/src/autofill/background/overlay.background.spec.ts b/apps/browser/src/autofill/background/overlay.background.spec.ts index 41d9d8ec32e3..de668cd81780 100644 --- a/apps/browser/src/autofill/background/overlay.background.spec.ts +++ b/apps/browser/src/autofill/background/overlay.background.spec.ts @@ -322,6 +322,7 @@ describe("OverlayBackground", () => { it("removes the page details and port key for a specific tab from the pageDetailsForTab object", async () => { await initOverlayElementPorts(); const tabId = 1; + portKeyForTabSpy[tabId] = "portKey"; sendMockExtensionMessage( { command: "collectPageDetailsResponse", details: createAutofillPageDetailsMock() }, mock({ tab: createChromeTabMock({ id: tabId }), frameId: 1 }), @@ -705,6 +706,13 @@ describe("OverlayBackground", () => { type: CipherType.Card, card: { subTitle: "subtitle-2" }, }); + const cipher3 = mock({ + id: "id-3", + localData: { lastUsedDate: 222 }, + name: "name-3", + type: CipherType.Login, + login: { username: "username-3", uri: url }, + }); beforeEach(() => { activeAccountStatusMock$.next(AuthenticationStatus.Unlocked); @@ -751,16 +759,53 @@ describe("OverlayBackground", () => { ); }); - it("queries all ciphers for the given url, sort them by last used, and format them for usage in the overlay", async () => { + it("queries all cipher types, sorts them by last used, and formats them for usage in the overlay", async () => { getTabFromCurrentWindowIdSpy.mockResolvedValueOnce(tab); cipherService.getAllDecryptedForUrl.mockResolvedValue([cipher1, cipher2]); cipherService.sortCiphersByLastUsedThenName.mockReturnValue(-1); await overlayBackground.updateOverlayCiphers(); + expect(BrowserApi.getTabFromCurrentWindowId).toHaveBeenCalled(); + expect(cipherService.getAllDecryptedForUrl).toHaveBeenCalledWith(url, [CipherType.Card]); + expect(cipherService.sortCiphersByLastUsedThenName).toHaveBeenCalled(); + expect(overlayBackground["inlineMenuCiphers"]).toStrictEqual( + new Map([ + ["inline-menu-cipher-0", cipher2], + ["inline-menu-cipher-1", cipher1], + ]), + ); + }); + + it("queries only login ciphers when not updating all cipher types", async () => { + overlayBackground["cardAndIdentityCiphers"] = new Set([]); + getTabFromCurrentWindowIdSpy.mockResolvedValueOnce(tab); + cipherService.getAllDecryptedForUrl.mockResolvedValue([cipher3, cipher1]); + cipherService.sortCiphersByLastUsedThenName.mockReturnValue(-1); + + await overlayBackground.updateOverlayCiphers(false); + expect(BrowserApi.getTabFromCurrentWindowId).toHaveBeenCalled(); expect(cipherService.getAllDecryptedForUrl).toHaveBeenCalledWith(url); expect(cipherService.sortCiphersByLastUsedThenName).toHaveBeenCalled(); + expect(overlayBackground["inlineMenuCiphers"]).toStrictEqual( + new Map([ + ["inline-menu-cipher-0", cipher1], + ["inline-menu-cipher-1", cipher3], + ]), + ); + }); + + it("queries all cipher types when the card and identity ciphers set is not built when only updating login ciphers", async () => { + getTabFromCurrentWindowIdSpy.mockResolvedValueOnce(tab); + cipherService.getAllDecryptedForUrl.mockResolvedValue([cipher1, cipher2]); + cipherService.sortCiphersByLastUsedThenName.mockReturnValue(-1); + + await overlayBackground.updateOverlayCiphers(false); + + expect(BrowserApi.getTabFromCurrentWindowId).toHaveBeenCalled(); + expect(cipherService.getAllDecryptedForUrl).toHaveBeenCalledWith(url, [CipherType.Card]); + expect(cipherService.sortCiphersByLastUsedThenName).toHaveBeenCalled(); expect(overlayBackground["inlineMenuCiphers"]).toStrictEqual( new Map([ ["inline-menu-cipher-0", cipher2], @@ -771,6 +816,7 @@ describe("OverlayBackground", () => { it("posts an `updateOverlayListCiphers` message to the overlay list port, and send a `updateAutofillInlineMenuListCiphers` message to the tab indicating that the list of ciphers is populated", async () => { overlayBackground["inlineMenuListPort"] = mock(); + overlayBackground["focusedFieldData"] = createFocusedFieldDataMock({ tabId: tab.id }); cipherService.getAllDecryptedForUrl.mockResolvedValue([cipher1, cipher2]); cipherService.sortCiphersByLastUsedThenName.mockReturnValue(-1); getTabFromCurrentWindowIdSpy.mockResolvedValueOnce(tab); @@ -780,21 +826,6 @@ describe("OverlayBackground", () => { expect(overlayBackground["inlineMenuListPort"].postMessage).toHaveBeenCalledWith({ command: "updateAutofillInlineMenuListCiphers", ciphers: [ - { - card: cipher2.card.subTitle, - favorite: cipher2.favorite, - icon: { - fallbackImage: "", - icon: "bwi-credit-card", - image: undefined, - imageEnabled: true, - }, - id: "inline-menu-cipher-0", - login: null, - name: "name-2", - reprompt: cipher2.reprompt, - type: 3, - }, { card: null, favorite: cipher1.favorite, @@ -810,7 +841,7 @@ describe("OverlayBackground", () => { }, name: "name-1", reprompt: cipher1.reprompt, - type: 1, + type: CipherType.Login, }, ], }); @@ -884,6 +915,7 @@ describe("OverlayBackground", () => { sendMockExtensionMessage( { command: "autofillOverlayAddNewVaultItem", + addNewCipherType: CipherType.Login, login: { uri: "https://tacos.com", hostname: "", @@ -899,6 +931,29 @@ describe("OverlayBackground", () => { expect(sendMessageSpy).toHaveBeenCalledWith("inlineAutofillMenuRefreshAddEditCipher"); expect(openAddEditVaultItemPopoutSpy).toHaveBeenCalled(); }); + + it("creates a new card cipher", async () => { + sendMockExtensionMessage( + { + command: "autofillOverlayAddNewVaultItem", + addNewCipherType: CipherType.Card, + card: { + cardholderName: "cardholderName", + number: "4242424242424242", + expirationMonth: "12", + expirationYear: "2025", + expirationDate: "12/25", + cvv: "123", + }, + }, + sender, + ); + await flushPromises(); + + expect(cipherService.setAddEditCipherInfo).toHaveBeenCalled(); + expect(sendMessageSpy).toHaveBeenCalledWith("inlineAutofillMenuRefreshAddEditCipher"); + expect(openAddEditVaultItemPopoutSpy).toHaveBeenCalled(); + }); }); describe("checkIsInlineMenuCiphersPopulated message handler", () => { @@ -929,8 +984,9 @@ describe("OverlayBackground", () => { it("returns true if the overlay login ciphers are populated", async () => { overlayBackground["inlineMenuCiphers"] = new Map([ - ["inline-menu-cipher-0", mock()], + ["inline-menu-cipher-0", mock({ type: CipherType.Login })], ]); + await overlayBackground["getInlineMenuCipherData"](); sendMockExtensionMessage( { command: "checkIsInlineMenuCiphersPopulated" }, @@ -2029,12 +2085,16 @@ describe("OverlayBackground", () => { sendMockExtensionMessage({ command: "updateFocusedFieldData", focusedFieldData }, sender); await flushPromises(); - sendPortMessage(listMessageConnectorSpy, { command: "addNewVaultItem", portKey }); + sendPortMessage(listMessageConnectorSpy, { + command: "addNewVaultItem", + portKey, + addNewCipherType: CipherType.Login, + }); await flushPromises(); expect(tabsSendMessageSpy).toHaveBeenCalledWith( sender.tab, - { command: "addNewVaultItemFromOverlay" }, + { command: "addNewVaultItemFromOverlay", addNewCipherType: CipherType.Login }, { frameId: sender.frameId }, ); }); diff --git a/apps/browser/src/autofill/background/overlay.background.ts b/apps/browser/src/autofill/background/overlay.background.ts index 74ec50710990..76b0f4b76ecb 100644 --- a/apps/browser/src/autofill/background/overlay.background.ts +++ b/apps/browser/src/autofill/background/overlay.background.ts @@ -19,6 +19,7 @@ import { ThemeStateService } from "@bitwarden/common/platform/theming/theme-stat 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 { CardView } from "@bitwarden/common/vault/models/view/card.view"; 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"; @@ -54,6 +55,8 @@ import { CloseInlineMenuMessage, InlineMenuPosition, ToggleInlineMenuHiddenMessage, + NewLoginCipherData, + NewCardCipherData, } from "./abstractions/overlay.background"; export class OverlayBackground implements OverlayBackgroundInterface { @@ -69,6 +72,8 @@ export class OverlayBackground implements OverlayBackgroundInterface { private inlineMenuCiphers: Map = new Map(); private inlineMenuPageTranslations: Record; private inlineMenuPosition: InlineMenuPosition = {}; + private cardAndIdentityCiphers: Set | null = null; + private currentInlineMenuCiphersCount: number = 0; private delayedCloseTimeout: number | NodeJS.Timeout; private startInlineMenuFadeInSubject = new Subject(); private cancelInlineMenuFadeInSubject = new Subject(); @@ -132,7 +137,7 @@ export class OverlayBackground implements OverlayBackgroundInterface { autofillInlineMenuBlurred: () => this.checkInlineMenuButtonFocused(), unlockVault: ({ port }) => this.unlockVault(port), fillAutofillInlineMenuCipher: ({ message, port }) => this.fillInlineMenuCipher(message, port), - addNewVaultItem: ({ port }) => this.getNewVaultItemDetails(port), + addNewVaultItem: ({ message, port }) => this.getNewVaultItemDetails(message, port), viewSelectedCipher: ({ message, port }) => this.viewSelectedCipher(message, port), redirectAutofillInlineMenuFocusOut: ({ message, port }) => this.redirectInlineMenuFocusOut(message, port), @@ -220,7 +225,7 @@ export class OverlayBackground implements OverlayBackgroundInterface { * 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() { + async updateOverlayCiphers(updateAllCipherTypes = true) { const authStatus = await firstValueFrom(this.authService.activeAccountStatus$); if (authStatus !== AuthenticationStatus.Unlocked) { if (this.focusedFieldData) { @@ -235,9 +240,7 @@ export class OverlayBackground implements OverlayBackgroundInterface { } this.inlineMenuCiphers = new Map(); - const ciphersViews = ( - await this.cipherService.getAllDecryptedForUrl(currentTab?.url || "") - ).sort((a, b) => this.cipherService.sortCiphersByLastUsedThenName(a, b)); + const ciphersViews = await this.getCipherViews(currentTab, updateAllCipherTypes); for (let cipherIndex = 0; cipherIndex < ciphersViews.length; cipherIndex++) { this.inlineMenuCiphers.set(`inline-menu-cipher-${cipherIndex}`, ciphersViews[cipherIndex]); } @@ -249,6 +252,51 @@ export class OverlayBackground implements OverlayBackgroundInterface { }); } + /** + * Gets the decrypted ciphers within a user's vault based on the current tab's URL. + * + * @param currentTab - The current tab + * @param updateAllCipherTypes - Identifies credit card and identity cipher types should also be updated + */ + private async getCipherViews( + currentTab: chrome.tabs.Tab, + updateAllCipherTypes: boolean, + ): Promise { + if (updateAllCipherTypes || !this.cardAndIdentityCiphers) { + return this.getAllCipherTypeViews(currentTab); + } + + const cipherViews = ( + await this.cipherService.getAllDecryptedForUrl(currentTab?.url || "") + ).sort((a, b) => this.cipherService.sortCiphersByLastUsedThenName(a, b)); + + return cipherViews.concat(...this.cardAndIdentityCiphers); + } + + /** + * Queries all cipher types from the user's vault returns them sorted by last used. + * + * @param currentTab - The current tab + */ + private async getAllCipherTypeViews(currentTab: chrome.tabs.Tab): Promise { + if (!this.cardAndIdentityCiphers) { + this.cardAndIdentityCiphers = new Set([]); + } + + this.cardAndIdentityCiphers.clear(); + const cipherViews = ( + await this.cipherService.getAllDecryptedForUrl(currentTab.url, [CipherType.Card]) + ).sort((a, b) => this.cipherService.sortCiphersByLastUsedThenName(a, b)); + for (let cipherIndex = 0; cipherIndex < cipherViews.length; cipherIndex++) { + const cipherView = cipherViews[cipherIndex]; + if (cipherView.type === CipherType.Card && !this.cardAndIdentityCiphers.has(cipherView)) { + this.cardAndIdentityCiphers.add(cipherView); + } + } + + return cipherViews; + } + /** * Strips out unnecessary data from the ciphers and returns an array of * objects that contain the cipher data needed for the inline menu list. @@ -260,6 +308,9 @@ export class OverlayBackground implements OverlayBackgroundInterface { for (let cipherIndex = 0; cipherIndex < inlineMenuCiphersArray.length; cipherIndex++) { const [inlineMenuCipherId, cipher] = inlineMenuCiphersArray[cipherIndex]; + if (this.focusedFieldData?.filledByCipherType !== cipher.type) { + continue; + } inlineMenuCipherData.push({ id: inlineMenuCipherId, @@ -273,6 +324,7 @@ export class OverlayBackground implements OverlayBackgroundInterface { }); } + this.currentInlineMenuCiphersCount = inlineMenuCipherData.length; return inlineMenuCipherData; } @@ -1062,7 +1114,7 @@ export class OverlayBackground implements OverlayBackgroundInterface { buttonPageTitle: this.i18nService.translate("bitwardenOverlayButton"), toggleBitwardenVaultOverlay: this.i18nService.translate("toggleBitwardenVaultOverlay"), listPageTitle: this.i18nService.translate("bitwardenVault"), - unlockYourAccount: this.i18nService.translate("unlockYourAccountToViewMatchingLogins"), + unlockYourAccount: this.i18nService.translate("unlockYourAccountToViewAutofillSuggestions"), unlockAccount: this.i18nService.translate("unlockAccount"), fillCredentialsFor: this.i18nService.translate("fillCredentialsFor"), username: this.i18nService.translate("username")?.toLowerCase(), @@ -1070,6 +1122,11 @@ export class OverlayBackground implements OverlayBackgroundInterface { noItemsToShow: this.i18nService.translate("noItemsToShow"), newItem: this.i18nService.translate("newItem"), addNewVaultItem: this.i18nService.translate("addNewVaultItem"), + newLogin: this.i18nService.translate("newLogin"), + addNewLoginItem: this.i18nService.translate("addNewLoginItem"), + newCard: this.i18nService.translate("newCard"), + addNewCardItem: this.i18nService.translate("addNewCardItem"), + cardNumberEndsWith: this.i18nService.translate("cardNumberEndsWith"), }; } @@ -1100,16 +1157,20 @@ export class OverlayBackground implements OverlayBackgroundInterface { * Triggers adding a new vault item from the overlay. Gathers data * input by the user before calling to open the add/edit window. * + * @param addNewCipherType - The type of cipher to add * @param sender - The sender of the port message */ - private getNewVaultItemDetails({ sender }: chrome.runtime.Port) { - if (!this.senderTabHasFocusedField(sender)) { + private getNewVaultItemDetails( + { addNewCipherType }: OverlayPortMessage, + { sender }: chrome.runtime.Port, + ) { + if (!addNewCipherType || !this.senderTabHasFocusedField(sender)) { return; } void BrowserApi.tabSendMessage( sender.tab, - { command: "addNewVaultItemFromOverlay" }, + { command: "addNewVaultItemFromOverlay", addNewCipherType }, { frameId: this.focusedFieldData.frameId || 0, }, @@ -1120,18 +1181,60 @@ export class OverlayBackground implements OverlayBackgroundInterface { * Handles adding a new vault item from the overlay. Gathers data login * data captured in the extension message. * + * @param addNewCipherType - The type of cipher to add * @param login - The login data captured from the extension message + * @param card - The card data captured from the extension message * @param sender - The sender of the extension message */ private async addNewVaultItem( - { login }: OverlayAddNewItemMessage, + { addNewCipherType, login, card }: OverlayAddNewItemMessage, sender: chrome.runtime.MessageSender, ) { - if (!login) { + if (!addNewCipherType) { return; } - this.closeInlineMenu(sender); + const cipherView: CipherView = this.buildNewVaultItemCipherView({ + addNewCipherType, + login, + card, + }); + + if (cipherView) { + this.closeInlineMenu(sender); + await this.cipherService.setAddEditCipherInfo({ + cipher: cipherView, + collectionIds: cipherView.collectionIds, + }); + + await this.openAddEditVaultItemPopout(sender.tab, { cipherId: cipherView.id }); + await BrowserApi.sendMessage("inlineAutofillMenuRefreshAddEditCipher"); + } + } + + /** + * Builds and returns a new cipher view with the provided vault item data. + * + * @param addNewCipherType - The type of cipher to add + * @param login - The login data captured from the extension message + * @param card - The card data captured from the extension message + */ + private buildNewVaultItemCipherView({ addNewCipherType, login, card }: OverlayAddNewItemMessage) { + if (login && addNewCipherType === CipherType.Login) { + return this.buildLoginCipherView(login); + } + + if (card && addNewCipherType === CipherType.Card) { + return this.buildCardCipherView(card); + } + } + + /** + * Builds a new login cipher view with the provided login data. + * + * @param login - The login data captured from the extension message + */ + private buildLoginCipherView(login: NewLoginCipherData) { const uriView = new LoginUriView(); uriView.uri = login.uri; @@ -1146,13 +1249,30 @@ export class OverlayBackground implements OverlayBackgroundInterface { cipherView.type = CipherType.Login; cipherView.login = loginView; - await this.cipherService.setAddEditCipherInfo({ - cipher: cipherView, - collectionIds: cipherView.collectionIds, - }); + return cipherView; + } + + /** + * Builds a new card cipher view with the provided card data. + * + * @param card - The card data captured from the extension message + */ + private buildCardCipherView(card: NewCardCipherData) { + const cardView = new CardView(); + cardView.cardholderName = card.cardholderName || ""; + cardView.number = card.number || ""; + cardView.expMonth = card.expirationMonth || ""; + cardView.expYear = card.expirationYear || ""; + cardView.code = card.cvv || ""; + cardView.brand = card.number ? CardView.getCardBrandByPatterns(card.number) : ""; + + const cipherView = new CipherView(); + cipherView.name = ""; + cipherView.folderId = null; + cipherView.type = CipherType.Card; + cipherView.card = cardView; - await this.openAddEditVaultItemPopout(sender.tab, { cipherId: cipherView.id }); - await BrowserApi.sendMessage("inlineAutofillMenuRefreshAddEditCipher"); + return cipherView; } /** @@ -1209,7 +1329,7 @@ export class OverlayBackground implements OverlayBackgroundInterface { * @param sender - The sender of the message */ private checkIsInlineMenuCiphersPopulated(sender: chrome.runtime.MessageSender) { - return this.senderTabHasFocusedField(sender) && this.inlineMenuCiphers.size > 0; + return this.senderTabHasFocusedField(sender) && this.currentInlineMenuCiphersCount > 0; } /** @@ -1477,6 +1597,7 @@ export class OverlayBackground implements OverlayBackgroundInterface { portName: isInlineMenuListPort ? AutofillOverlayPort.ListMessageConnector : AutofillOverlayPort.ButtonMessageConnector, + filledByCipherType: this.focusedFieldData?.filledByCipherType, }); void this.updateInlineMenuPosition( { diff --git a/apps/browser/src/autofill/background/tabs.background.ts b/apps/browser/src/autofill/background/tabs.background.ts index f68ae6c6edc5..0513220c277c 100644 --- a/apps/browser/src/autofill/background/tabs.background.ts +++ b/apps/browser/src/autofill/background/tabs.background.ts @@ -104,7 +104,7 @@ export default class TabsBackground { return; } - await this.overlayBackground.updateOverlayCiphers(); + await this.overlayBackground.updateOverlayCiphers(false); if (this.main.onUpdatedRan) { return; @@ -134,7 +134,7 @@ export default class TabsBackground { await Promise.all([ this.main.refreshBadge(), this.main.refreshMenu(), - this.overlayBackground.updateOverlayCiphers(), + this.overlayBackground.updateOverlayCiphers(false), ]); }; } diff --git a/apps/browser/src/autofill/content/abstractions/autofill-init.ts b/apps/browser/src/autofill/content/abstractions/autofill-init.ts index 8b00b4ecc9e9..ba815a0f29ae 100644 --- a/apps/browser/src/autofill/content/abstractions/autofill-init.ts +++ b/apps/browser/src/autofill/content/abstractions/autofill-init.ts @@ -1,4 +1,5 @@ import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status"; +import { CipherType } from "@bitwarden/common/vault/enums"; import { AutofillOverlayElementType } from "../../enums/autofill-overlay.enum"; import AutofillScript from "../../models/autofill-script"; @@ -18,6 +19,7 @@ export type AutofillExtensionMessage = { isFocusingFieldElement?: boolean; authStatus?: AuthenticationStatus; isOpeningFullInlineMenu?: boolean; + addNewCipherType?: CipherType; data?: { direction?: "previous" | "next" | "current"; forceCloseInlineMenu?: boolean; diff --git a/apps/browser/src/autofill/models/autofill-field.ts b/apps/browser/src/autofill/models/autofill-field.ts index 0636190d955c..26f01bdeac84 100644 --- a/apps/browser/src/autofill/models/autofill-field.ts +++ b/apps/browser/src/autofill/models/autofill-field.ts @@ -1,3 +1,5 @@ +import { CipherType } from "@bitwarden/common/vault/enums"; + /** * Represents a single field that is collected from the page source and is potentially autofilled. */ @@ -106,4 +108,6 @@ export default class AutofillField { rel?: string | null; checked?: boolean; + + filledByCipherType?: CipherType; } diff --git a/apps/browser/src/autofill/overlay/inline-menu/abstractions/autofill-inline-menu-list.ts b/apps/browser/src/autofill/overlay/inline-menu/abstractions/autofill-inline-menu-list.ts index 1687029074b9..5a00ffbaaa8f 100644 --- a/apps/browser/src/autofill/overlay/inline-menu/abstractions/autofill-inline-menu-list.ts +++ b/apps/browser/src/autofill/overlay/inline-menu/abstractions/autofill-inline-menu-list.ts @@ -1,4 +1,5 @@ import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status"; +import { CipherType } from "@bitwarden/common/vault/enums"; import { InlineMenuCipherData } from "../../../background/abstractions/overlay.background"; @@ -14,6 +15,7 @@ export type InitAutofillInlineMenuListMessage = AutofillInlineMenuListMessage & theme: string; translations: Record; ciphers?: InlineMenuCipherData[]; + filledByCipherType?: CipherType; portKey: string; }; diff --git a/apps/browser/src/autofill/overlay/inline-menu/pages/list/__snapshots__/autofill-inline-menu-list.spec.ts.snap b/apps/browser/src/autofill/overlay/inline-menu/pages/list/__snapshots__/autofill-inline-menu-list.spec.ts.snap index 1b44636b0891..3b0e84514f2a 100644 --- a/apps/browser/src/autofill/overlay/inline-menu/pages/list/__snapshots__/autofill-inline-menu-list.spec.ts.snap +++ b/apps/browser/src/autofill/overlay/inline-menu/pages/list/__snapshots__/autofill-inline-menu-list.spec.ts.snap @@ -1,6 +1,6 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`AutofillInlineMenuList initAutofillInlineMenuList the inline menu with an empty list of ciphers creates the views for the no results inline menu 1`] = ` +exports[`AutofillInlineMenuList initAutofillInlineMenuList the inline menu with an empty list of ciphers creates the views for the no results inline menu that does not have a fill by cipher type 1`] = `
@@ -13,7 +13,7 @@ exports[`AutofillInlineMenuList initAutofillInlineMenuList the inline menu with class="inline-menu-list-button-container" >
`; -exports[`AutofillInlineMenuList initAutofillInlineMenuList the list of ciphers for an authenticated user creates the view for a list of ciphers 1`] = ` +exports[`AutofillInlineMenuList initAutofillInlineMenuList the inline menu with an empty list of ciphers creates the views for the no results inline menu that should be filled by a card cipher 1`] = ` +
+
+ noItemsToShow +
+
+ +
+
+`; + +exports[`AutofillInlineMenuList initAutofillInlineMenuList the inline menu with an empty list of ciphers creates the views for the no results inline menu that should be filled by a login cipher 1`] = ` +
+
+ noItemsToShow +
+
+ +
+
+`; + +exports[`AutofillInlineMenuList initAutofillInlineMenuList the list of ciphers for an authenticated user creates the view for a list of login ciphers 1`] = ` +
+
    +
  • +
    + + +
    +
  • +
  • +
    + + +
    +
  • +
  • +
    + + +
    +
  • +
  • +
    + + +
    +
  • +
  • +
    + + +
    +
  • +
  • +
    + + +
    +
  • +
+
+`; + +exports[`AutofillInlineMenuList initAutofillInlineMenuList the list of ciphers for an authenticated user creates the views for a list of card ciphers 1`] = `
@@ -87,7 +618,7 @@ exports[`AutofillInlineMenuList initAutofillInlineMenuList the list of ciphers f website login 1