[PM-10552] Values input between separate iframes are not populated when adding a cipher from the inline menu (#10401)
* [PM-10554] Inline menu glitches on single form fields that are wrapped within inline menu * [PM-10554] Adding jest tests to validate expected behavior * [PM-10554] Adding jest tests to validate expected behavior * [PM-10552] Vaules input between separate iframes are not populated in add-edit cipher popout * [PM-10552] Working through issues found when attempting to add ciphers within iframes that trigger a blur event * [PM-10552] Working through issues found when attempting to add ciphers within iframes that trigger a blur event * [PM-10552] Fixing broken jest tests due to implementation changes * [PM-10552] Implementing jest tests to validate behavior within OverlayBackground
This commit is contained in:
parent
fdcf1c7ea2
commit
cecfbaeaad
|
@ -95,6 +95,10 @@ export type OverlayAddNewItemMessage = {
|
|||
identity?: NewIdentityCipherData;
|
||||
};
|
||||
|
||||
export type CurrentAddNewItemData = OverlayAddNewItemMessage & {
|
||||
sender: chrome.runtime.MessageSender;
|
||||
};
|
||||
|
||||
export type CloseInlineMenuMessage = {
|
||||
forceCloseInlineMenu?: boolean;
|
||||
overlayElement?: string;
|
||||
|
|
|
@ -176,8 +176,12 @@ describe("OverlayBackground", () => {
|
|||
parentFrameId: getFrameCounter,
|
||||
});
|
||||
});
|
||||
tabsSendMessageSpy = jest.spyOn(BrowserApi, "tabSendMessage");
|
||||
tabSendMessageDataSpy = jest.spyOn(BrowserApi, "tabSendMessageData");
|
||||
tabsSendMessageSpy = jest
|
||||
.spyOn(BrowserApi, "tabSendMessage")
|
||||
.mockImplementation(() => Promise.resolve());
|
||||
tabSendMessageDataSpy = jest
|
||||
.spyOn(BrowserApi, "tabSendMessageData")
|
||||
.mockImplementation(() => Promise.resolve());
|
||||
sendMessageSpy = jest.spyOn(BrowserApi, "sendMessage");
|
||||
getTabFromCurrentWindowIdSpy = jest.spyOn(BrowserApi, "getTabFromCurrentWindowId");
|
||||
getTabSpy = jest.spyOn(BrowserApi, "getTab");
|
||||
|
@ -838,7 +842,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["focusedFieldData"] = createFocusedFieldDataMock({ tabId: tab.id });
|
||||
cipherService.getAllDecryptedForUrl.mockResolvedValue([cipher1, cipher2]);
|
||||
cipherService.getAllDecryptedForUrl.mockResolvedValue([cipher1]);
|
||||
cipherService.sortCiphersByLastUsedThenName.mockReturnValue(-1);
|
||||
getTabFromCurrentWindowIdSpy.mockResolvedValueOnce(tab);
|
||||
|
||||
|
@ -857,7 +861,7 @@ describe("OverlayBackground", () => {
|
|||
image: "https://icons.bitwarden.com//jest-testing-website.com/icon.png",
|
||||
imageEnabled: true,
|
||||
},
|
||||
id: "inline-menu-cipher-1",
|
||||
id: "inline-menu-cipher-0",
|
||||
login: {
|
||||
username: "username-1",
|
||||
},
|
||||
|
@ -1119,10 +1123,12 @@ describe("OverlayBackground", () => {
|
|||
let openAddEditVaultItemPopoutSpy: jest.SpyInstance;
|
||||
|
||||
beforeEach(() => {
|
||||
jest.useFakeTimers();
|
||||
sender = mock<chrome.runtime.MessageSender>({ tab: { id: 1 } });
|
||||
openAddEditVaultItemPopoutSpy = jest
|
||||
.spyOn(overlayBackground as any, "openAddEditVaultItemPopout")
|
||||
.mockImplementation();
|
||||
overlayBackground["currentAddNewItemData"] = { sender, addNewCipherType: CipherType.Login };
|
||||
});
|
||||
|
||||
it("will not open the add edit popout window if the message does not have a login cipher provided", () => {
|
||||
|
@ -1132,6 +1138,28 @@ describe("OverlayBackground", () => {
|
|||
expect(openAddEditVaultItemPopoutSpy).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("resets the currentAddNewItemData to null when a cipher view is not successfully created", async () => {
|
||||
jest.spyOn(overlayBackground as any, "buildLoginCipherView").mockReturnValue(null);
|
||||
|
||||
sendMockExtensionMessage(
|
||||
{
|
||||
command: "autofillOverlayAddNewVaultItem",
|
||||
addNewCipherType: CipherType.Login,
|
||||
login: {
|
||||
uri: "https://tacos.com",
|
||||
hostname: "",
|
||||
username: "username",
|
||||
password: "password",
|
||||
},
|
||||
},
|
||||
sender,
|
||||
);
|
||||
jest.advanceTimersByTime(100);
|
||||
await flushPromises();
|
||||
|
||||
expect(overlayBackground["currentAddNewItemData"]).toBeNull();
|
||||
});
|
||||
|
||||
it("will open the add edit popout window after creating a new cipher", async () => {
|
||||
sendMockExtensionMessage(
|
||||
{
|
||||
|
@ -1146,6 +1174,7 @@ describe("OverlayBackground", () => {
|
|||
},
|
||||
sender,
|
||||
);
|
||||
jest.advanceTimersByTime(100);
|
||||
await flushPromises();
|
||||
|
||||
expect(cipherService.setAddEditCipherInfo).toHaveBeenCalled();
|
||||
|
@ -1154,6 +1183,8 @@ describe("OverlayBackground", () => {
|
|||
});
|
||||
|
||||
it("creates a new card cipher", async () => {
|
||||
overlayBackground["currentAddNewItemData"].addNewCipherType = CipherType.Card;
|
||||
|
||||
sendMockExtensionMessage(
|
||||
{
|
||||
command: "autofillOverlayAddNewVaultItem",
|
||||
|
@ -1169,6 +1200,7 @@ describe("OverlayBackground", () => {
|
|||
},
|
||||
sender,
|
||||
);
|
||||
jest.advanceTimersByTime(100);
|
||||
await flushPromises();
|
||||
|
||||
expect(cipherService.setAddEditCipherInfo).toHaveBeenCalled();
|
||||
|
@ -1177,6 +1209,10 @@ describe("OverlayBackground", () => {
|
|||
});
|
||||
|
||||
describe("creating a new identity cipher", () => {
|
||||
beforeEach(() => {
|
||||
overlayBackground["currentAddNewItemData"].addNewCipherType = CipherType.Identity;
|
||||
});
|
||||
|
||||
it("populates an identity cipher view and creates it", async () => {
|
||||
sendMockExtensionMessage(
|
||||
{
|
||||
|
@ -1203,6 +1239,7 @@ describe("OverlayBackground", () => {
|
|||
},
|
||||
sender,
|
||||
);
|
||||
jest.advanceTimersByTime(100);
|
||||
await flushPromises();
|
||||
|
||||
expect(cipherService.setAddEditCipherInfo).toHaveBeenCalled();
|
||||
|
@ -1223,6 +1260,7 @@ describe("OverlayBackground", () => {
|
|||
},
|
||||
sender,
|
||||
);
|
||||
jest.advanceTimersByTime(100);
|
||||
await flushPromises();
|
||||
|
||||
expect(cipherService.setAddEditCipherInfo).toHaveBeenCalled();
|
||||
|
@ -1241,6 +1279,7 @@ describe("OverlayBackground", () => {
|
|||
},
|
||||
sender,
|
||||
);
|
||||
jest.advanceTimersByTime(100);
|
||||
await flushPromises();
|
||||
|
||||
expect(cipherService.setAddEditCipherInfo).toHaveBeenCalled();
|
||||
|
@ -1259,11 +1298,173 @@ describe("OverlayBackground", () => {
|
|||
},
|
||||
sender,
|
||||
);
|
||||
jest.advanceTimersByTime(100);
|
||||
await flushPromises();
|
||||
|
||||
expect(cipherService.setAddEditCipherInfo).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe("pulling cipher data from multiple frames of a tab", () => {
|
||||
let subFrameSender: MockProxy<chrome.runtime.MessageSender>;
|
||||
const command = "autofillOverlayAddNewVaultItem";
|
||||
|
||||
beforeEach(() => {
|
||||
subFrameSender = mock<chrome.runtime.MessageSender>({ tab: sender.tab, frameId: 2 });
|
||||
});
|
||||
|
||||
it("combines the login cipher data from all frames", async () => {
|
||||
const buildLoginCipherViewSpy = jest.spyOn(
|
||||
overlayBackground as any,
|
||||
"buildLoginCipherView",
|
||||
);
|
||||
const addNewCipherType = CipherType.Login;
|
||||
const loginCipherData = {
|
||||
uri: "https://tacos.com",
|
||||
hostname: "",
|
||||
username: "username",
|
||||
password: "",
|
||||
};
|
||||
const subFrameLoginCipherData = {
|
||||
uri: "https://tacos.com",
|
||||
hostname: "tacos.com",
|
||||
username: "",
|
||||
password: "password",
|
||||
};
|
||||
|
||||
sendMockExtensionMessage({ command, addNewCipherType, login: loginCipherData }, sender);
|
||||
sendMockExtensionMessage(
|
||||
{ command, addNewCipherType, login: subFrameLoginCipherData },
|
||||
subFrameSender,
|
||||
);
|
||||
jest.advanceTimersByTime(100);
|
||||
await flushPromises();
|
||||
|
||||
expect(buildLoginCipherViewSpy).toHaveBeenCalledWith({
|
||||
uri: "https://tacos.com",
|
||||
hostname: "tacos.com",
|
||||
username: "username",
|
||||
password: "password",
|
||||
});
|
||||
});
|
||||
|
||||
it("combines the card cipher data from all frames", async () => {
|
||||
const buildCardCipherViewSpy = jest.spyOn(
|
||||
overlayBackground as any,
|
||||
"buildCardCipherView",
|
||||
);
|
||||
overlayBackground["currentAddNewItemData"].addNewCipherType = CipherType.Card;
|
||||
const addNewCipherType = CipherType.Card;
|
||||
const cardCipherData = {
|
||||
cardholderName: "cardholderName",
|
||||
number: "",
|
||||
expirationMonth: "",
|
||||
expirationYear: "",
|
||||
expirationDate: "12/25",
|
||||
cvv: "123",
|
||||
};
|
||||
const subFrameCardCipherData = {
|
||||
cardholderName: "",
|
||||
number: "4242424242424242",
|
||||
expirationMonth: "12",
|
||||
expirationYear: "2025",
|
||||
expirationDate: "",
|
||||
cvv: "",
|
||||
};
|
||||
|
||||
sendMockExtensionMessage({ command, addNewCipherType, card: cardCipherData }, sender);
|
||||
sendMockExtensionMessage(
|
||||
{ command, addNewCipherType, card: subFrameCardCipherData },
|
||||
subFrameSender,
|
||||
);
|
||||
jest.advanceTimersByTime(100);
|
||||
await flushPromises();
|
||||
|
||||
expect(buildCardCipherViewSpy).toHaveBeenCalledWith({
|
||||
cardholderName: "cardholderName",
|
||||
number: "4242424242424242",
|
||||
expirationMonth: "12",
|
||||
expirationYear: "2025",
|
||||
expirationDate: "12/25",
|
||||
cvv: "123",
|
||||
});
|
||||
});
|
||||
|
||||
it("combines the identity cipher data from all frames", async () => {
|
||||
const buildIdentityCipherViewSpy = jest.spyOn(
|
||||
overlayBackground as any,
|
||||
"buildIdentityCipherView",
|
||||
);
|
||||
overlayBackground["currentAddNewItemData"].addNewCipherType = CipherType.Identity;
|
||||
const addNewCipherType = CipherType.Identity;
|
||||
const identityCipherData = {
|
||||
title: "title",
|
||||
firstName: "firstName",
|
||||
middleName: "middleName",
|
||||
lastName: "",
|
||||
fullName: "",
|
||||
address1: "address1",
|
||||
address2: "address2",
|
||||
address3: "address3",
|
||||
city: "city",
|
||||
state: "state",
|
||||
postalCode: "postalCode",
|
||||
country: "country",
|
||||
company: "company",
|
||||
phone: "phone",
|
||||
email: "email",
|
||||
username: "username",
|
||||
};
|
||||
const subFrameIdentityCipherData = {
|
||||
title: "",
|
||||
firstName: "",
|
||||
middleName: "",
|
||||
lastName: "lastName",
|
||||
fullName: "fullName",
|
||||
address1: "",
|
||||
address2: "",
|
||||
address3: "",
|
||||
city: "",
|
||||
state: "",
|
||||
postalCode: "",
|
||||
country: "",
|
||||
company: "",
|
||||
phone: "",
|
||||
email: "",
|
||||
username: "",
|
||||
};
|
||||
|
||||
sendMockExtensionMessage(
|
||||
{ command, addNewCipherType, identity: identityCipherData },
|
||||
sender,
|
||||
);
|
||||
sendMockExtensionMessage(
|
||||
{ command, addNewCipherType, identity: subFrameIdentityCipherData },
|
||||
subFrameSender,
|
||||
);
|
||||
jest.advanceTimersByTime(100);
|
||||
await flushPromises();
|
||||
|
||||
expect(buildIdentityCipherViewSpy).toHaveBeenCalledWith({
|
||||
title: "title",
|
||||
firstName: "firstName",
|
||||
middleName: "middleName",
|
||||
lastName: "lastName",
|
||||
fullName: "fullName",
|
||||
address1: "address1",
|
||||
address2: "address2",
|
||||
address3: "address3",
|
||||
city: "city",
|
||||
state: "state",
|
||||
postalCode: "postalCode",
|
||||
country: "country",
|
||||
company: "company",
|
||||
phone: "phone",
|
||||
email: "email",
|
||||
username: "username",
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("checkIsInlineMenuCiphersPopulated message handler", () => {
|
||||
|
@ -1363,6 +1564,51 @@ describe("OverlayBackground", () => {
|
|||
showInlineMenuAccountCreation: true,
|
||||
});
|
||||
});
|
||||
|
||||
it("triggers an update of the inline menu ciphers when the new focused field's cipher type does not equal the previous focused field's cipher type", async () => {
|
||||
const updateOverlayCiphersSpy = jest.spyOn(overlayBackground, "updateOverlayCiphers");
|
||||
const tab = createChromeTabMock({ id: 2 });
|
||||
const sender = mock<chrome.runtime.MessageSender>({ tab, frameId: 100 });
|
||||
const focusedFieldData = createFocusedFieldDataMock({
|
||||
tabId: tab.id,
|
||||
frameId: sender.frameId,
|
||||
filledByCipherType: CipherType.Login,
|
||||
});
|
||||
sendMockExtensionMessage({ command: "updateFocusedFieldData", focusedFieldData }, sender);
|
||||
await flushPromises();
|
||||
|
||||
const newFocusedFieldData = createFocusedFieldDataMock({
|
||||
tabId: tab.id,
|
||||
frameId: sender.frameId,
|
||||
filledByCipherType: CipherType.Card,
|
||||
});
|
||||
sendMockExtensionMessage(
|
||||
{ command: "updateFocusedFieldData", focusedFieldData: newFocusedFieldData },
|
||||
sender,
|
||||
);
|
||||
await flushPromises();
|
||||
|
||||
expect(updateOverlayCiphersSpy).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe("updateIsFieldCurrentlyFocused message handler", () => {
|
||||
it("skips updating the isFiledCurrentlyFocused value when the focused field data is populated and the sender frame id does not equal the focused field's frame id", async () => {
|
||||
const focusedFieldData = createFocusedFieldDataMock();
|
||||
sendMockExtensionMessage(
|
||||
{ command: "updateFocusedFieldData", focusedFieldData },
|
||||
mock<chrome.runtime.MessageSender>({ tab: { id: 1 }, frameId: 10 }),
|
||||
);
|
||||
overlayBackground["isFieldCurrentlyFocused"] = true;
|
||||
|
||||
sendMockExtensionMessage(
|
||||
{ command: "updateIsFieldCurrentlyFocused", isFieldCurrentlyFocused: false },
|
||||
mock<chrome.runtime.MessageSender>({ tab: { id: 1 }, frameId: 20 }),
|
||||
);
|
||||
await flushPromises();
|
||||
|
||||
expect(overlayBackground["isFieldCurrentlyFocused"]).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe("updateIsFieldCurrentlyFocused message handler", () => {
|
||||
|
@ -1841,7 +2087,6 @@ describe("OverlayBackground", () => {
|
|||
overlayBackground["subFrameOffsetsForTab"][focusedFieldData.tabId] = new Map([
|
||||
[focusedFieldData.frameId, null],
|
||||
]);
|
||||
tabsSendMessageSpy.mockImplementation();
|
||||
jest.spyOn(overlayBackground as any, "updateInlineMenuPositionAfterRepositionEvent");
|
||||
|
||||
sendMockExtensionMessage(
|
||||
|
@ -2090,7 +2335,6 @@ describe("OverlayBackground", () => {
|
|||
describe("autofillInlineMenuButtonClicked message handler", () => {
|
||||
it("opens the unlock vault popout if the user auth status is not unlocked", async () => {
|
||||
activeAccountStatusMock$.next(AuthenticationStatus.Locked);
|
||||
tabsSendMessageSpy.mockImplementation();
|
||||
|
||||
sendPortMessage(buttonMessageConnectorSpy, {
|
||||
command: "autofillInlineMenuButtonClicked",
|
||||
|
@ -2291,7 +2535,6 @@ describe("OverlayBackground", () => {
|
|||
describe("unlockVault message handler", () => {
|
||||
it("opens the unlock vault popout", async () => {
|
||||
activeAccountStatusMock$.next(AuthenticationStatus.Locked);
|
||||
tabsSendMessageSpy.mockImplementation();
|
||||
|
||||
sendPortMessage(listMessageConnectorSpy, { command: "unlockVault", portKey });
|
||||
await flushPromises();
|
||||
|
@ -2443,11 +2686,10 @@ describe("OverlayBackground", () => {
|
|||
});
|
||||
await flushPromises();
|
||||
|
||||
expect(tabsSendMessageSpy).toHaveBeenCalledWith(
|
||||
sender.tab,
|
||||
{ command: "addNewVaultItemFromOverlay", addNewCipherType: CipherType.Login },
|
||||
{ frameId: sender.frameId },
|
||||
);
|
||||
expect(tabsSendMessageSpy).toHaveBeenCalledWith(sender.tab, {
|
||||
command: "addNewVaultItemFromOverlay",
|
||||
addNewCipherType: CipherType.Login,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
@ -42,6 +42,7 @@ import { generateRandomChars } from "../utils";
|
|||
import { LockedVaultPendingNotificationsData } from "./abstractions/notification.background";
|
||||
import {
|
||||
CloseInlineMenuMessage,
|
||||
CurrentAddNewItemData,
|
||||
FocusedFieldData,
|
||||
InlineMenuButtonPortMessageHandlers,
|
||||
InlineMenuCipherData,
|
||||
|
@ -83,6 +84,8 @@ export class OverlayBackground implements OverlayBackgroundInterface {
|
|||
private cancelUpdateInlineMenuPositionSubject = new Subject<void>();
|
||||
private repositionInlineMenuSubject = new Subject<chrome.runtime.MessageSender>();
|
||||
private rebuildSubFrameOffsetsSubject = new Subject<chrome.runtime.MessageSender>();
|
||||
private addNewVaultItemSubject = new Subject<CurrentAddNewItemData>();
|
||||
private currentAddNewItemData: CurrentAddNewItemData;
|
||||
private focusedFieldData: FocusedFieldData;
|
||||
private isFieldCurrentlyFocused: boolean = false;
|
||||
private isFieldCurrentlyFilling: boolean = false;
|
||||
|
@ -187,6 +190,14 @@ export class OverlayBackground implements OverlayBackgroundInterface {
|
|||
switchMap((sender) => this.rebuildSubFrameOffsets(sender)),
|
||||
)
|
||||
.subscribe();
|
||||
this.addNewVaultItemSubject
|
||||
.pipe(
|
||||
debounceTime(100),
|
||||
switchMap((addNewItemData) =>
|
||||
this.buildCipherAndOpenAddEditVaultItemPopout(addNewItemData),
|
||||
),
|
||||
)
|
||||
.subscribe();
|
||||
|
||||
// Debounce used to update inline menu position
|
||||
merge(
|
||||
|
@ -231,14 +242,14 @@ export class OverlayBackground implements OverlayBackgroundInterface {
|
|||
const authStatus = await firstValueFrom(this.authService.activeAccountStatus$);
|
||||
if (authStatus !== AuthenticationStatus.Unlocked) {
|
||||
if (this.focusedFieldData) {
|
||||
void this.closeInlineMenuAfterCiphersUpdate();
|
||||
this.closeInlineMenuAfterCiphersUpdate().catch((error) => this.logService.error(error));
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
const currentTab = await BrowserApi.getTabFromCurrentWindowId();
|
||||
if (this.focusedFieldData && currentTab?.id !== this.focusedFieldData.tabId) {
|
||||
void this.closeInlineMenuAfterCiphersUpdate();
|
||||
this.closeInlineMenuAfterCiphersUpdate().catch((error) => this.logService.error(error));
|
||||
}
|
||||
|
||||
this.inlineMenuCiphers = new Map();
|
||||
|
@ -319,7 +330,7 @@ export class OverlayBackground implements OverlayBackgroundInterface {
|
|||
private async getInlineMenuCipherData(): Promise<InlineMenuCipherData[]> {
|
||||
const showFavicons = await firstValueFrom(this.domainSettingsService.showFavicons$);
|
||||
const inlineMenuCiphersArray = Array.from(this.inlineMenuCiphers);
|
||||
let inlineMenuCipherData: InlineMenuCipherData[] = [];
|
||||
let inlineMenuCipherData: InlineMenuCipherData[];
|
||||
|
||||
if (this.showInlineMenuAccountCreation()) {
|
||||
inlineMenuCipherData = this.buildInlineMenuAccountCreationCiphers(
|
||||
|
@ -527,10 +538,14 @@ export class OverlayBackground implements OverlayBackgroundInterface {
|
|||
};
|
||||
|
||||
if (pageDetails.frameId !== 0 && pageDetails.details.fields.length) {
|
||||
void this.buildSubFrameOffsets(pageDetails.tab, pageDetails.frameId, pageDetails.details.url);
|
||||
void BrowserApi.tabSendMessage(pageDetails.tab, {
|
||||
this.buildSubFrameOffsets(
|
||||
pageDetails.tab,
|
||||
pageDetails.frameId,
|
||||
pageDetails.details.url,
|
||||
).catch((error) => this.logService.error(error));
|
||||
BrowserApi.tabSendMessage(pageDetails.tab, {
|
||||
command: "setupRebuildSubFrameOffsetsListeners",
|
||||
});
|
||||
}).catch((error) => this.logService.error(error));
|
||||
}
|
||||
|
||||
const pageDetailsMap = this.pageDetailsForTab[sender.tab.id];
|
||||
|
@ -620,11 +635,11 @@ export class OverlayBackground implements OverlayBackgroundInterface {
|
|||
|
||||
if (!subFrameOffset) {
|
||||
subFrameOffsetsForTab.set(frameId, null);
|
||||
void BrowserApi.tabSendMessage(
|
||||
BrowserApi.tabSendMessage(
|
||||
tab,
|
||||
{ command: "getSubFrameOffsetsFromWindowMessage", subFrameId: frameId },
|
||||
{ frameId },
|
||||
);
|
||||
).catch((error) => this.logService.error(error));
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -656,11 +671,11 @@ export class OverlayBackground implements OverlayBackgroundInterface {
|
|||
frameId,
|
||||
);
|
||||
|
||||
void BrowserApi.tabSendMessage(
|
||||
BrowserApi.tabSendMessage(
|
||||
tab,
|
||||
{ command: "destroyAutofillInlineMenuListeners" },
|
||||
{ frameId },
|
||||
);
|
||||
).catch((error) => this.logService.error(error));
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -696,13 +711,15 @@ export class OverlayBackground implements OverlayBackgroundInterface {
|
|||
}
|
||||
|
||||
if (!this.checkIsInlineMenuButtonVisible()) {
|
||||
void this.toggleInlineMenuHidden(
|
||||
this.toggleInlineMenuHidden(
|
||||
{ isInlineMenuHidden: false, setTransparentInlineMenu: true },
|
||||
sender,
|
||||
);
|
||||
).catch((error) => this.logService.error(error));
|
||||
}
|
||||
|
||||
void this.updateInlineMenuPosition({ overlayElement: AutofillOverlayElement.Button }, sender);
|
||||
this.updateInlineMenuPosition({ overlayElement: AutofillOverlayElement.Button }, sender).catch(
|
||||
(error) => this.logService.error(error),
|
||||
);
|
||||
|
||||
const mostRecentlyFocusedFieldHasValue = await BrowserApi.tabSendMessage(
|
||||
sender.tab,
|
||||
|
@ -722,7 +739,9 @@ export class OverlayBackground implements OverlayBackgroundInterface {
|
|||
return;
|
||||
}
|
||||
|
||||
void this.updateInlineMenuPosition({ overlayElement: AutofillOverlayElement.List }, sender);
|
||||
this.updateInlineMenuPosition({ overlayElement: AutofillOverlayElement.List }, sender).catch(
|
||||
(error) => this.logService.error(error),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -807,7 +826,9 @@ export class OverlayBackground implements OverlayBackgroundInterface {
|
|||
const command = "closeAutofillInlineMenu";
|
||||
const sendOptions = { frameId: 0 };
|
||||
if (forceCloseInlineMenu) {
|
||||
void BrowserApi.tabSendMessage(sender.tab, { command, overlayElement }, sendOptions);
|
||||
BrowserApi.tabSendMessage(sender.tab, { command, overlayElement }, sendOptions).catch(
|
||||
(error) => this.logService.error(error),
|
||||
);
|
||||
this.isInlineMenuButtonVisible = false;
|
||||
this.isInlineMenuListVisible = false;
|
||||
return;
|
||||
|
@ -818,11 +839,11 @@ export class OverlayBackground implements OverlayBackgroundInterface {
|
|||
}
|
||||
|
||||
if (this.isFieldCurrentlyFilling) {
|
||||
void BrowserApi.tabSendMessage(
|
||||
BrowserApi.tabSendMessage(
|
||||
sender.tab,
|
||||
{ command, overlayElement: AutofillOverlayElement.List },
|
||||
sendOptions,
|
||||
);
|
||||
).catch((error) => this.logService.error(error));
|
||||
this.isInlineMenuListVisible = false;
|
||||
return;
|
||||
}
|
||||
|
@ -840,7 +861,9 @@ export class OverlayBackground implements OverlayBackgroundInterface {
|
|||
this.isInlineMenuListVisible = false;
|
||||
}
|
||||
|
||||
void BrowserApi.tabSendMessage(sender.tab, { command, overlayElement }, sendOptions);
|
||||
BrowserApi.tabSendMessage(sender.tab, { command, overlayElement }, sendOptions).catch((error) =>
|
||||
this.logService.error(error),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -1092,11 +1115,11 @@ export class OverlayBackground implements OverlayBackgroundInterface {
|
|||
sender: chrome.runtime.MessageSender,
|
||||
) {
|
||||
if (this.focusedFieldData && !this.senderFrameHasFocusedField(sender)) {
|
||||
void BrowserApi.tabSendMessage(
|
||||
BrowserApi.tabSendMessage(
|
||||
sender.tab,
|
||||
{ command: "unsetMostRecentlyFocusedField" },
|
||||
{ frameId: this.focusedFieldData.frameId },
|
||||
);
|
||||
).catch((error) => this.logService.error(error));
|
||||
}
|
||||
|
||||
const previousFocusedFieldData = this.focusedFieldData;
|
||||
|
@ -1108,7 +1131,17 @@ export class OverlayBackground implements OverlayBackgroundInterface {
|
|||
!this.focusedFieldData.showInlineMenuAccountCreation;
|
||||
|
||||
if (accountCreationFieldBlurred || this.showInlineMenuAccountCreation()) {
|
||||
void this.updateIdentityCiphersOnLoginField(previousFocusedFieldData);
|
||||
this.updateIdentityCiphersOnLoginField(previousFocusedFieldData).catch((error) =>
|
||||
this.logService.error(error),
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
if (previousFocusedFieldData?.filledByCipherType !== focusedFieldData?.filledByCipherType) {
|
||||
const updateAllCipherTypes = focusedFieldData.filledByCipherType !== CipherType.Login;
|
||||
this.updateOverlayCiphers(updateAllCipherTypes).catch((error) =>
|
||||
this.logService.error(error),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1355,9 +1388,9 @@ export class OverlayBackground implements OverlayBackgroundInterface {
|
|||
return;
|
||||
}
|
||||
|
||||
void BrowserApi.tabSendMessageData(sender.tab, "redirectAutofillInlineMenuFocusOut", {
|
||||
BrowserApi.tabSendMessageData(sender.tab, "redirectAutofillInlineMenuFocusOut", {
|
||||
direction,
|
||||
});
|
||||
}).catch((error) => this.logService.error(error));
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -1375,13 +1408,11 @@ export class OverlayBackground implements OverlayBackgroundInterface {
|
|||
return;
|
||||
}
|
||||
|
||||
void BrowserApi.tabSendMessage(
|
||||
sender.tab,
|
||||
{ command: "addNewVaultItemFromOverlay", addNewCipherType },
|
||||
{
|
||||
frameId: this.focusedFieldData.frameId || 0,
|
||||
},
|
||||
);
|
||||
this.currentAddNewItemData = { addNewCipherType, sender };
|
||||
BrowserApi.tabSendMessage(sender.tab, {
|
||||
command: "addNewVaultItemFromOverlay",
|
||||
addNewCipherType,
|
||||
}).catch((error) => this.logService.error(error));
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -1398,18 +1429,154 @@ export class OverlayBackground implements OverlayBackgroundInterface {
|
|||
{ addNewCipherType, login, card, identity }: OverlayAddNewItemMessage,
|
||||
sender: chrome.runtime.MessageSender,
|
||||
) {
|
||||
if (!addNewCipherType) {
|
||||
if (
|
||||
!this.currentAddNewItemData ||
|
||||
sender.tab.id !== this.currentAddNewItemData.sender.tab.id ||
|
||||
!addNewCipherType ||
|
||||
this.currentAddNewItemData.addNewCipherType !== addNewCipherType
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (login && this.isAddingNewLogin()) {
|
||||
this.updateCurrentAddNewItemLogin(login);
|
||||
}
|
||||
|
||||
if (card && this.isAddingNewCard()) {
|
||||
this.updateCurrentAddNewItemCard(card);
|
||||
}
|
||||
|
||||
if (identity && this.isAddingNewIdentity()) {
|
||||
this.updateCurrentAddNewItemIdentity(identity);
|
||||
}
|
||||
|
||||
this.addNewVaultItemSubject.next(this.currentAddNewItemData);
|
||||
}
|
||||
|
||||
/**
|
||||
* Identifies if the current add new item data is for adding a new login.
|
||||
*/
|
||||
private isAddingNewLogin() {
|
||||
return this.currentAddNewItemData.addNewCipherType === CipherType.Login;
|
||||
}
|
||||
|
||||
/**
|
||||
* Identifies if the current add new item data is for adding a new card.
|
||||
*/
|
||||
private isAddingNewCard() {
|
||||
return this.currentAddNewItemData.addNewCipherType === CipherType.Card;
|
||||
}
|
||||
|
||||
/**
|
||||
* Identifies if the current add new item data is for adding a new identity.
|
||||
*/
|
||||
private isAddingNewIdentity() {
|
||||
return this.currentAddNewItemData.addNewCipherType === CipherType.Identity;
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the current add new item data with the provided login data. If the
|
||||
* login data is already present, the data will be merged with the existing data.
|
||||
*
|
||||
* @param login - The login data captured from the extension message
|
||||
*/
|
||||
private updateCurrentAddNewItemLogin(login: NewLoginCipherData) {
|
||||
if (!this.currentAddNewItemData.login) {
|
||||
this.currentAddNewItemData.login = login;
|
||||
return;
|
||||
}
|
||||
|
||||
const currentLoginData = this.currentAddNewItemData.login;
|
||||
this.currentAddNewItemData.login = {
|
||||
uri: login.uri || currentLoginData.uri,
|
||||
hostname: login.hostname || currentLoginData.hostname,
|
||||
username: login.username || currentLoginData.username,
|
||||
password: login.password || currentLoginData.password,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the current add new item data with the provided card data. If the
|
||||
* card data is already present, the data will be merged with the existing data.
|
||||
*
|
||||
* @param card - The card data captured from the extension message
|
||||
*/
|
||||
private updateCurrentAddNewItemCard(card: NewCardCipherData) {
|
||||
if (!this.currentAddNewItemData.card) {
|
||||
this.currentAddNewItemData.card = card;
|
||||
return;
|
||||
}
|
||||
|
||||
const currentCardData = this.currentAddNewItemData.card;
|
||||
this.currentAddNewItemData.card = {
|
||||
cardholderName: card.cardholderName || currentCardData.cardholderName,
|
||||
number: card.number || currentCardData.number,
|
||||
expirationMonth: card.expirationMonth || currentCardData.expirationMonth,
|
||||
expirationYear: card.expirationYear || currentCardData.expirationYear,
|
||||
expirationDate: card.expirationDate || currentCardData.expirationDate,
|
||||
cvv: card.cvv || currentCardData.cvv,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the current add new item data with the provided identity data. If the
|
||||
* identity data is already present, the data will be merged with the existing data.
|
||||
*
|
||||
* @param identity - The identity data captured from the extension message
|
||||
*/
|
||||
private updateCurrentAddNewItemIdentity(identity: NewIdentityCipherData) {
|
||||
if (!this.currentAddNewItemData.identity) {
|
||||
this.currentAddNewItemData.identity = identity;
|
||||
return;
|
||||
}
|
||||
|
||||
const currentIdentityData = this.currentAddNewItemData.identity;
|
||||
this.currentAddNewItemData.identity = {
|
||||
title: identity.title || currentIdentityData.title,
|
||||
firstName: identity.firstName || currentIdentityData.firstName,
|
||||
middleName: identity.middleName || currentIdentityData.middleName,
|
||||
lastName: identity.lastName || currentIdentityData.lastName,
|
||||
fullName: identity.fullName || currentIdentityData.fullName,
|
||||
address1: identity.address1 || currentIdentityData.address1,
|
||||
address2: identity.address2 || currentIdentityData.address2,
|
||||
address3: identity.address3 || currentIdentityData.address3,
|
||||
city: identity.city || currentIdentityData.city,
|
||||
state: identity.state || currentIdentityData.state,
|
||||
postalCode: identity.postalCode || currentIdentityData.postalCode,
|
||||
country: identity.country || currentIdentityData.country,
|
||||
company: identity.company || currentIdentityData.company,
|
||||
phone: identity.phone || currentIdentityData.phone,
|
||||
email: identity.email || currentIdentityData.email,
|
||||
username: identity.username || currentIdentityData.username,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles building a new cipher and opening the add/edit vault item popout.
|
||||
*
|
||||
* @param login - The login data captured from the extension message
|
||||
* @param card - The card data captured from the extension message
|
||||
* @param identity - The identity data captured from the extension message
|
||||
* @param sender - The sender of the extension message
|
||||
*/
|
||||
private async buildCipherAndOpenAddEditVaultItemPopout({
|
||||
login,
|
||||
card,
|
||||
identity,
|
||||
sender,
|
||||
}: CurrentAddNewItemData) {
|
||||
const cipherView: CipherView = this.buildNewVaultItemCipherView({
|
||||
addNewCipherType,
|
||||
login,
|
||||
card,
|
||||
identity,
|
||||
});
|
||||
|
||||
if (cipherView) {
|
||||
if (!cipherView) {
|
||||
this.currentAddNewItemData = null;
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
this.closeInlineMenu(sender);
|
||||
await this.cipherService.setAddEditCipherInfo({
|
||||
cipher: cipherView,
|
||||
|
@ -1418,32 +1585,30 @@ export class OverlayBackground implements OverlayBackgroundInterface {
|
|||
|
||||
await this.openAddEditVaultItemPopout(sender.tab, { cipherId: cipherView.id });
|
||||
await BrowserApi.sendMessage("inlineAutofillMenuRefreshAddEditCipher");
|
||||
} catch (error) {
|
||||
this.logService.error("Error building cipher and opening add/edit vault item popout", error);
|
||||
}
|
||||
|
||||
this.currentAddNewItemData = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
* @param identity - The identity data captured from the extension message
|
||||
*/
|
||||
private buildNewVaultItemCipherView({
|
||||
addNewCipherType,
|
||||
login,
|
||||
card,
|
||||
identity,
|
||||
}: OverlayAddNewItemMessage) {
|
||||
if (login && addNewCipherType === CipherType.Login) {
|
||||
private buildNewVaultItemCipherView({ login, card, identity }: OverlayAddNewItemMessage) {
|
||||
if (login && this.isAddingNewLogin()) {
|
||||
return this.buildLoginCipherView(login);
|
||||
}
|
||||
|
||||
if (card && addNewCipherType === CipherType.Card) {
|
||||
if (card && this.isAddingNewCard()) {
|
||||
return this.buildCardCipherView(card);
|
||||
}
|
||||
|
||||
if (identity && addNewCipherType === CipherType.Identity) {
|
||||
if (identity && this.isAddingNewIdentity()) {
|
||||
return this.buildIdentityCipherView(identity);
|
||||
}
|
||||
}
|
||||
|
@ -1708,7 +1873,9 @@ export class OverlayBackground implements OverlayBackgroundInterface {
|
|||
|
||||
this.resetFocusedFieldSubFrameOffsets(sender);
|
||||
this.cancelInlineMenuFadeInAndPositionUpdate();
|
||||
void this.toggleInlineMenuHidden({ isInlineMenuHidden: true }, sender);
|
||||
this.toggleInlineMenuHidden({ isInlineMenuHidden: true }, sender).catch((error) =>
|
||||
this.logService.error(error),
|
||||
);
|
||||
this.repositionInlineMenuSubject.next(sender);
|
||||
}
|
||||
|
||||
|
@ -1898,14 +2065,14 @@ export class OverlayBackground implements OverlayBackgroundInterface {
|
|||
filledByCipherType: this.focusedFieldData?.filledByCipherType,
|
||||
showInlineMenuAccountCreation: this.showInlineMenuAccountCreation(),
|
||||
});
|
||||
void this.updateInlineMenuPosition(
|
||||
this.updateInlineMenuPosition(
|
||||
{
|
||||
overlayElement: isInlineMenuListPort
|
||||
? AutofillOverlayElement.List
|
||||
: AutofillOverlayElement.Button,
|
||||
},
|
||||
port.sender,
|
||||
);
|
||||
).catch((error) => this.logService.error(error));
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
|
@ -1099,7 +1099,9 @@ describe("AutofillOverlayContentService", () => {
|
|||
selectFieldElement.dispatchEvent(new Event("focus"));
|
||||
await flushPromises();
|
||||
|
||||
expect(sendExtensionMessageSpy).toHaveBeenCalledWith("closeAutofillInlineMenu");
|
||||
expect(sendExtensionMessageSpy).toHaveBeenCalledWith("closeAutofillInlineMenu", {
|
||||
forceCloseInlineMenu: true,
|
||||
});
|
||||
});
|
||||
|
||||
it("updates the most recently focused field", async () => {
|
||||
|
|
|
@ -249,10 +249,6 @@ export class AutofillOverlayContentService implements AutofillOverlayContentServ
|
|||
* to the background script to add a new cipher.
|
||||
*/
|
||||
async addNewVaultItem({ addNewCipherType }: AutofillExtensionMessage) {
|
||||
if (!(await this.isInlineMenuListVisible())) {
|
||||
return;
|
||||
}
|
||||
|
||||
const command = "autofillOverlayAddNewVaultItem";
|
||||
|
||||
if (addNewCipherType === CipherType.Login) {
|
||||
|
@ -680,7 +676,9 @@ export class AutofillOverlayContentService implements AutofillOverlayContentServ
|
|||
}
|
||||
|
||||
if (elementIsSelectElement(formFieldElement)) {
|
||||
await this.sendExtensionMessage("closeAutofillInlineMenu");
|
||||
await this.sendExtensionMessage("closeAutofillInlineMenu", {
|
||||
forceCloseInlineMenu: true,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -763,7 +761,11 @@ export class AutofillOverlayContentService implements AutofillOverlayContentServ
|
|||
private async updateMostRecentlyFocusedField(
|
||||
formFieldElement: ElementWithOpId<FormFieldElement>,
|
||||
) {
|
||||
if (!formFieldElement || !elementIsFillableFormField(formFieldElement)) {
|
||||
if (
|
||||
!formFieldElement ||
|
||||
!elementIsFillableFormField(formFieldElement) ||
|
||||
elementIsSelectElement(formFieldElement)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue