mirror of
https://github.com/bitwarden/browser
synced 2025-12-18 09:13:33 +00:00
[PM-5189] Working through jest tests for the AutofillOverlayContentService
This commit is contained in:
@@ -34,6 +34,6 @@ export interface AutofillOverlayContentService {
|
|||||||
autofillFieldElement: ElementWithOpId<FormFieldElement>,
|
autofillFieldElement: ElementWithOpId<FormFieldElement>,
|
||||||
autofillFieldData: AutofillField,
|
autofillFieldData: AutofillField,
|
||||||
): Promise<void>;
|
): Promise<void>;
|
||||||
blurMostRecentlyFocusedField(isRemovingInlineMenu?: boolean): void;
|
blurMostRecentlyFocusedField(isClosingInlineMenu?: boolean): void;
|
||||||
destroy(): void;
|
destroy(): void;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -749,131 +749,6 @@ describe("AutofillOverlayContentService", () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("openAutofillInlineMenu", () => {
|
|
||||||
let autofillFieldElement: ElementWithOpId<FormFieldElement>;
|
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
document.body.innerHTML = `
|
|
||||||
<form id="validFormId">
|
|
||||||
<input type="text" id="username-field" placeholder="username" />
|
|
||||||
<input type="password" id="password-field" placeholder="password" />
|
|
||||||
</form>
|
|
||||||
`;
|
|
||||||
|
|
||||||
autofillFieldElement = document.getElementById(
|
|
||||||
"username-field",
|
|
||||||
) as ElementWithOpId<FormFieldElement>;
|
|
||||||
autofillFieldElement.opid = "op-1";
|
|
||||||
autofillOverlayContentService["mostRecentlyFocusedField"] = autofillFieldElement;
|
|
||||||
});
|
|
||||||
|
|
||||||
it("skips opening the overlay if a field has not been recently focused", () => {
|
|
||||||
autofillOverlayContentService["mostRecentlyFocusedField"] = undefined;
|
|
||||||
|
|
||||||
autofillOverlayContentService["openAutofillInlineMenu"]();
|
|
||||||
|
|
||||||
expect(sendExtensionMessageSpy).not.toHaveBeenCalled();
|
|
||||||
});
|
|
||||||
|
|
||||||
it("focuses the most recent overlay field if the field is not focused", () => {
|
|
||||||
jest.spyOn(autofillFieldElement, "getRootNode").mockReturnValue(document);
|
|
||||||
Object.defineProperty(document, "activeElement", {
|
|
||||||
value: document.createElement("div"),
|
|
||||||
writable: true,
|
|
||||||
});
|
|
||||||
const focusMostRecentOverlayFieldSpy = jest.spyOn(
|
|
||||||
autofillOverlayContentService as any,
|
|
||||||
"focusMostRecentlyFocusedField",
|
|
||||||
);
|
|
||||||
|
|
||||||
autofillOverlayContentService["openAutofillInlineMenu"]({ isFocusingFieldElement: true });
|
|
||||||
|
|
||||||
expect(focusMostRecentOverlayFieldSpy).toHaveBeenCalled();
|
|
||||||
});
|
|
||||||
|
|
||||||
it("skips focusing the most recent overlay field if the field is already focused", () => {
|
|
||||||
jest.spyOn(autofillFieldElement, "getRootNode").mockReturnValue(document);
|
|
||||||
Object.defineProperty(document, "activeElement", {
|
|
||||||
value: autofillFieldElement,
|
|
||||||
writable: true,
|
|
||||||
});
|
|
||||||
const focusMostRecentOverlayFieldSpy = jest.spyOn(
|
|
||||||
autofillOverlayContentService as any,
|
|
||||||
"focusMostRecentlyFocusedField",
|
|
||||||
);
|
|
||||||
|
|
||||||
autofillOverlayContentService["openAutofillInlineMenu"]({ isFocusingFieldElement: true });
|
|
||||||
|
|
||||||
expect(focusMostRecentOverlayFieldSpy).not.toHaveBeenCalled();
|
|
||||||
});
|
|
||||||
|
|
||||||
it("stores the user's auth status", () => {
|
|
||||||
autofillOverlayContentService["authStatus"] = undefined;
|
|
||||||
|
|
||||||
autofillOverlayContentService["openAutofillInlineMenu"]({
|
|
||||||
authStatus: AuthenticationStatus.Unlocked,
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(autofillOverlayContentService["authStatus"]).toEqual(AuthenticationStatus.Unlocked);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("opens both autofill overlay elements", () => {
|
|
||||||
autofillOverlayContentService["mostRecentlyFocusedField"] = autofillFieldElement;
|
|
||||||
|
|
||||||
autofillOverlayContentService["openAutofillInlineMenu"]();
|
|
||||||
|
|
||||||
expect(sendExtensionMessageSpy).toHaveBeenCalledWith("updateAutofillInlineMenuPosition", {
|
|
||||||
overlayElement: AutofillOverlayElement.Button,
|
|
||||||
});
|
|
||||||
expect(sendExtensionMessageSpy).toHaveBeenCalledWith("updateAutofillInlineMenuPosition", {
|
|
||||||
overlayElement: AutofillOverlayElement.List,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it("opens the autofill overlay button only if overlay visibility is set for onButtonClick", () => {
|
|
||||||
autofillOverlayContentService["inlineMenuVisibility"] =
|
|
||||||
AutofillOverlayVisibility.OnButtonClick;
|
|
||||||
|
|
||||||
autofillOverlayContentService["openAutofillInlineMenu"]({
|
|
||||||
isOpeningFullAutofillInlineMenu: false,
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(sendExtensionMessageSpy).toHaveBeenCalledWith("updateAutofillInlineMenuPosition", {
|
|
||||||
overlayElement: AutofillOverlayElement.Button,
|
|
||||||
});
|
|
||||||
expect(sendExtensionMessageSpy).not.toHaveBeenCalledWith("updateAutofillInlineMenuPosition", {
|
|
||||||
overlayElement: AutofillOverlayElement.List,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it("overrides the onButtonClick visibility setting to open both overlay elements", () => {
|
|
||||||
autofillOverlayContentService["inlineMenuVisibility"] =
|
|
||||||
AutofillOverlayVisibility.OnButtonClick;
|
|
||||||
|
|
||||||
autofillOverlayContentService["openAutofillInlineMenu"]({
|
|
||||||
isOpeningFullAutofillInlineMenu: true,
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(sendExtensionMessageSpy).toHaveBeenCalledWith("updateAutofillInlineMenuPosition", {
|
|
||||||
overlayElement: AutofillOverlayElement.Button,
|
|
||||||
});
|
|
||||||
expect(sendExtensionMessageSpy).toHaveBeenCalledWith("updateAutofillInlineMenuPosition", {
|
|
||||||
overlayElement: AutofillOverlayElement.List,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it("sends an extension message requesting an re-collection of page details if they need to update", () => {
|
|
||||||
jest.spyOn(autofillOverlayContentService as any, "sendExtensionMessage");
|
|
||||||
autofillOverlayContentService.pageDetailsUpdateRequired = true;
|
|
||||||
|
|
||||||
autofillOverlayContentService["openAutofillInlineMenu"]();
|
|
||||||
|
|
||||||
expect(sendExtensionMessageSpy).toHaveBeenCalledWith("bgCollectPageDetails", {
|
|
||||||
sender: "autofillOverlayContentService",
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("focusMostRecentlyFocusedField", () => {
|
describe("focusMostRecentlyFocusedField", () => {
|
||||||
it("focuses the most recently focused overlay field", () => {
|
it("focuses the most recently focused overlay field", () => {
|
||||||
const mostRecentlyFocusedField = document.createElement(
|
const mostRecentlyFocusedField = document.createElement(
|
||||||
@@ -888,199 +763,6 @@ describe("AutofillOverlayContentService", () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("blurMostRecentlyFocusedField", () => {
|
|
||||||
it("removes focus from the most recently focused overlay field", () => {
|
|
||||||
const mostRecentlyFocusedField = document.createElement(
|
|
||||||
"input",
|
|
||||||
) as ElementWithOpId<HTMLInputElement>;
|
|
||||||
autofillOverlayContentService["mostRecentlyFocusedField"] = mostRecentlyFocusedField;
|
|
||||||
jest.spyOn(mostRecentlyFocusedField, "blur");
|
|
||||||
|
|
||||||
autofillOverlayContentService["blurMostRecentlyFocusedField"]();
|
|
||||||
|
|
||||||
expect(mostRecentlyFocusedField.blur).toHaveBeenCalled();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("addNewVaultItem", () => {
|
|
||||||
it("skips sending the message if the overlay list is not visible", async () => {
|
|
||||||
jest
|
|
||||||
.spyOn(autofillOverlayContentService as any, "isInlineMenuListVisible")
|
|
||||||
.mockResolvedValue(false);
|
|
||||||
|
|
||||||
await autofillOverlayContentService.addNewVaultItem();
|
|
||||||
|
|
||||||
expect(sendExtensionMessageSpy).not.toHaveBeenCalled();
|
|
||||||
});
|
|
||||||
|
|
||||||
it("sends a message that facilitates adding a new vault item with empty fields", async () => {
|
|
||||||
jest
|
|
||||||
.spyOn(autofillOverlayContentService as any, "isInlineMenuListVisible")
|
|
||||||
.mockResolvedValue(true);
|
|
||||||
|
|
||||||
await autofillOverlayContentService.addNewVaultItem();
|
|
||||||
|
|
||||||
expect(sendExtensionMessageSpy).toHaveBeenCalledWith("autofillOverlayAddNewVaultItem", {
|
|
||||||
login: {
|
|
||||||
username: "",
|
|
||||||
password: "",
|
|
||||||
uri: "http://localhost/",
|
|
||||||
hostname: "localhost",
|
|
||||||
},
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it("sends a message that facilitates adding a new vault item with data from user filled fields", async () => {
|
|
||||||
document.body.innerHTML = `
|
|
||||||
<form id="validFormId">
|
|
||||||
<input type="text" id="username-field" placeholder="username" />
|
|
||||||
<input type="password" id="password-field" placeholder="password" />
|
|
||||||
</form>
|
|
||||||
`;
|
|
||||||
const usernameField = document.getElementById(
|
|
||||||
"username-field",
|
|
||||||
) as ElementWithOpId<HTMLInputElement>;
|
|
||||||
const passwordField = document.getElementById(
|
|
||||||
"password-field",
|
|
||||||
) as ElementWithOpId<HTMLInputElement>;
|
|
||||||
usernameField.value = "test-username";
|
|
||||||
passwordField.value = "test-password";
|
|
||||||
jest
|
|
||||||
.spyOn(autofillOverlayContentService as any, "isInlineMenuListVisible")
|
|
||||||
.mockResolvedValue(true);
|
|
||||||
autofillOverlayContentService["userFilledFields"] = {
|
|
||||||
username: usernameField,
|
|
||||||
password: passwordField,
|
|
||||||
};
|
|
||||||
|
|
||||||
await autofillOverlayContentService.addNewVaultItem();
|
|
||||||
|
|
||||||
expect(sendExtensionMessageSpy).toHaveBeenCalledWith("autofillOverlayAddNewVaultItem", {
|
|
||||||
login: {
|
|
||||||
username: "test-username",
|
|
||||||
password: "test-password",
|
|
||||||
uri: "http://localhost/",
|
|
||||||
hostname: "localhost",
|
|
||||||
},
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("redirectInlineMenuFocusOut", () => {
|
|
||||||
let autofillFieldElement: ElementWithOpId<FormFieldElement>;
|
|
||||||
let autofillFieldFocusSpy: jest.SpyInstance;
|
|
||||||
let findTabsSpy: jest.SpyInstance;
|
|
||||||
let previousFocusableElement: HTMLElement;
|
|
||||||
let nextFocusableElement: HTMLElement;
|
|
||||||
let isInlineMenuListVisibleSpy: jest.SpyInstance;
|
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
document.body.innerHTML = `
|
|
||||||
<div class="previous-focusable-element" tabindex="0"></div>
|
|
||||||
<form id="validFormId">
|
|
||||||
<input type="text" id="username-field" placeholder="username" />
|
|
||||||
<input type="password" id="password-field" placeholder="password" />
|
|
||||||
</form>
|
|
||||||
<div class="next-focusable-element" tabindex="0"></div>
|
|
||||||
`;
|
|
||||||
autofillFieldElement = document.getElementById(
|
|
||||||
"username-field",
|
|
||||||
) as ElementWithOpId<FormFieldElement>;
|
|
||||||
autofillFieldElement.opid = "op-1";
|
|
||||||
previousFocusableElement = document.querySelector(
|
|
||||||
".previous-focusable-element",
|
|
||||||
) as HTMLElement;
|
|
||||||
nextFocusableElement = document.querySelector(".next-focusable-element") as HTMLElement;
|
|
||||||
autofillFieldFocusSpy = jest.spyOn(autofillFieldElement, "focus");
|
|
||||||
findTabsSpy = jest.spyOn(autofillOverlayContentService as any, "findTabs");
|
|
||||||
isInlineMenuListVisibleSpy = jest
|
|
||||||
.spyOn(autofillOverlayContentService as any, "isInlineMenuListVisible")
|
|
||||||
.mockResolvedValue(true);
|
|
||||||
autofillOverlayContentService["mostRecentlyFocusedField"] = autofillFieldElement;
|
|
||||||
autofillOverlayContentService["focusableElements"] = [
|
|
||||||
previousFocusableElement,
|
|
||||||
autofillFieldElement,
|
|
||||||
nextFocusableElement,
|
|
||||||
];
|
|
||||||
});
|
|
||||||
|
|
||||||
it("skips focusing an element if the overlay is not visible", async () => {
|
|
||||||
isInlineMenuListVisibleSpy.mockResolvedValue(false);
|
|
||||||
|
|
||||||
await autofillOverlayContentService["redirectInlineMenuFocusOut"](
|
|
||||||
RedirectFocusDirection.Next,
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(findTabsSpy).not.toHaveBeenCalled();
|
|
||||||
});
|
|
||||||
|
|
||||||
it("skips focusing an element if no recently focused field exists", async () => {
|
|
||||||
autofillOverlayContentService["mostRecentlyFocusedField"] = undefined;
|
|
||||||
|
|
||||||
await autofillOverlayContentService["redirectInlineMenuFocusOut"](
|
|
||||||
RedirectFocusDirection.Next,
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(findTabsSpy).not.toHaveBeenCalled();
|
|
||||||
});
|
|
||||||
|
|
||||||
it("focuses the most recently focused field if the focus direction is `Current`", async () => {
|
|
||||||
await autofillOverlayContentService["redirectInlineMenuFocusOut"](
|
|
||||||
RedirectFocusDirection.Current,
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(findTabsSpy).not.toHaveBeenCalled();
|
|
||||||
expect(autofillFieldFocusSpy).toHaveBeenCalled();
|
|
||||||
});
|
|
||||||
|
|
||||||
it("removes the overlay if the focus direction is `Current`", async () => {
|
|
||||||
jest.useFakeTimers();
|
|
||||||
await autofillOverlayContentService["redirectInlineMenuFocusOut"](
|
|
||||||
RedirectFocusDirection.Current,
|
|
||||||
);
|
|
||||||
jest.advanceTimersByTime(150);
|
|
||||||
|
|
||||||
expect(sendExtensionMessageSpy).toHaveBeenCalledWith("closeAutofillInlineMenu");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("finds all focusable tabs if the focusable elements array is not populated", async () => {
|
|
||||||
autofillOverlayContentService["focusableElements"] = [];
|
|
||||||
findTabsSpy.mockReturnValue([
|
|
||||||
previousFocusableElement,
|
|
||||||
autofillFieldElement,
|
|
||||||
nextFocusableElement,
|
|
||||||
]);
|
|
||||||
|
|
||||||
await autofillOverlayContentService["redirectInlineMenuFocusOut"](
|
|
||||||
RedirectFocusDirection.Next,
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(findTabsSpy).toHaveBeenCalledWith(globalThis.document.body, { getShadowRoot: true });
|
|
||||||
});
|
|
||||||
|
|
||||||
it("focuses the previous focusable element if the focus direction is `Previous`", async () => {
|
|
||||||
jest.spyOn(previousFocusableElement, "focus");
|
|
||||||
|
|
||||||
await autofillOverlayContentService["redirectInlineMenuFocusOut"](
|
|
||||||
RedirectFocusDirection.Previous,
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(autofillFieldFocusSpy).not.toHaveBeenCalled();
|
|
||||||
expect(previousFocusableElement.focus).toHaveBeenCalled();
|
|
||||||
});
|
|
||||||
|
|
||||||
it("focuses the next focusable element if the focus direction is `Next`", async () => {
|
|
||||||
jest.spyOn(nextFocusableElement, "focus");
|
|
||||||
|
|
||||||
await autofillOverlayContentService["redirectInlineMenuFocusOut"](
|
|
||||||
RedirectFocusDirection.Next,
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(autofillFieldFocusSpy).not.toHaveBeenCalled();
|
|
||||||
expect(nextFocusableElement.focus).toHaveBeenCalled();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("handleOverlayRepositionEvent", () => {
|
describe("handleOverlayRepositionEvent", () => {
|
||||||
let checkShouldRepositionInlineMenuSpy: jest.SpyInstance;
|
let checkShouldRepositionInlineMenuSpy: jest.SpyInstance;
|
||||||
|
|
||||||
@@ -1319,12 +1001,120 @@ describe("AutofillOverlayContentService", () => {
|
|||||||
|
|
||||||
describe("extension onMessage handlers", () => {
|
describe("extension onMessage handlers", () => {
|
||||||
describe("openAutofillInlineMenu message handler", () => {
|
describe("openAutofillInlineMenu message handler", () => {
|
||||||
it("sends a message to the background to trigger an update in the inline menu's position", async () => {
|
let autofillFieldElement: ElementWithOpId<FormFieldElement>;
|
||||||
autofillOverlayContentService["mostRecentlyFocusedField"] =
|
|
||||||
mock<ElementWithOpId<FormFieldElement>>();
|
beforeEach(() => {
|
||||||
|
document.body.innerHTML = `
|
||||||
|
<form id="validFormId">
|
||||||
|
<input type="text" id="username-field" placeholder="username" />
|
||||||
|
<input type="password" id="password-field" placeholder="password" />
|
||||||
|
</form>
|
||||||
|
`;
|
||||||
|
|
||||||
|
autofillFieldElement = document.getElementById(
|
||||||
|
"username-field",
|
||||||
|
) as ElementWithOpId<FormFieldElement>;
|
||||||
|
autofillFieldElement.opid = "op-1";
|
||||||
|
autofillOverlayContentService["mostRecentlyFocusedField"] = autofillFieldElement;
|
||||||
|
});
|
||||||
|
|
||||||
|
it("skips opening the overlay if a field has not been recently focused", () => {
|
||||||
|
autofillOverlayContentService["mostRecentlyFocusedField"] = undefined;
|
||||||
|
|
||||||
|
sendMockExtensionMessage({ command: "openAutofillInlineMenu" });
|
||||||
|
|
||||||
|
expect(sendExtensionMessageSpy).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("focuses the most recent overlay field if the field is not focused", () => {
|
||||||
|
jest.spyOn(autofillFieldElement, "getRootNode").mockReturnValue(document);
|
||||||
|
Object.defineProperty(document, "activeElement", {
|
||||||
|
value: document.createElement("div"),
|
||||||
|
writable: true,
|
||||||
|
});
|
||||||
|
const focusMostRecentOverlayFieldSpy = jest.spyOn(
|
||||||
|
autofillOverlayContentService as any,
|
||||||
|
"focusMostRecentlyFocusedField",
|
||||||
|
);
|
||||||
|
|
||||||
sendMockExtensionMessage({
|
sendMockExtensionMessage({
|
||||||
command: "openAutofillInlineMenu",
|
command: "openAutofillInlineMenu",
|
||||||
|
isFocusingFieldElement: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(focusMostRecentOverlayFieldSpy).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("skips focusing the most recent overlay field if the field is already focused", () => {
|
||||||
|
jest.spyOn(autofillFieldElement, "getRootNode").mockReturnValue(document);
|
||||||
|
Object.defineProperty(document, "activeElement", {
|
||||||
|
value: autofillFieldElement,
|
||||||
|
writable: true,
|
||||||
|
});
|
||||||
|
const focusMostRecentOverlayFieldSpy = jest.spyOn(
|
||||||
|
autofillOverlayContentService as any,
|
||||||
|
"focusMostRecentlyFocusedField",
|
||||||
|
);
|
||||||
|
|
||||||
|
sendMockExtensionMessage({
|
||||||
|
command: "openAutofillInlineMenu",
|
||||||
|
isFocusingFieldElement: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(focusMostRecentOverlayFieldSpy).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("stores the user's auth status", () => {
|
||||||
|
autofillOverlayContentService["authStatus"] = undefined;
|
||||||
|
|
||||||
|
sendMockExtensionMessage({
|
||||||
|
command: "openAutofillInlineMenu",
|
||||||
|
authStatus: AuthenticationStatus.Unlocked,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(autofillOverlayContentService["authStatus"]).toEqual(AuthenticationStatus.Unlocked);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("opens both autofill overlay elements", () => {
|
||||||
|
autofillOverlayContentService["mostRecentlyFocusedField"] = autofillFieldElement;
|
||||||
|
|
||||||
|
sendMockExtensionMessage({ command: "openAutofillInlineMenu" });
|
||||||
|
|
||||||
|
expect(sendExtensionMessageSpy).toHaveBeenCalledWith("updateAutofillInlineMenuPosition", {
|
||||||
|
overlayElement: AutofillOverlayElement.Button,
|
||||||
|
});
|
||||||
|
expect(sendExtensionMessageSpy).toHaveBeenCalledWith("updateAutofillInlineMenuPosition", {
|
||||||
|
overlayElement: AutofillOverlayElement.List,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("opens the autofill overlay button only if overlay visibility is set for onButtonClick", () => {
|
||||||
|
autofillOverlayContentService["inlineMenuVisibility"] =
|
||||||
|
AutofillOverlayVisibility.OnButtonClick;
|
||||||
|
|
||||||
|
sendMockExtensionMessage({
|
||||||
|
command: "openAutofillInlineMenu",
|
||||||
|
isOpeningFullAutofillInlineMenu: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(sendExtensionMessageSpy).toHaveBeenCalledWith("updateAutofillInlineMenuPosition", {
|
||||||
|
overlayElement: AutofillOverlayElement.Button,
|
||||||
|
});
|
||||||
|
expect(sendExtensionMessageSpy).not.toHaveBeenCalledWith(
|
||||||
|
"updateAutofillInlineMenuPosition",
|
||||||
|
{
|
||||||
|
overlayElement: AutofillOverlayElement.List,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("overrides the onButtonClick visibility setting to open both overlay elements", () => {
|
||||||
|
autofillOverlayContentService["inlineMenuVisibility"] =
|
||||||
|
AutofillOverlayVisibility.OnButtonClick;
|
||||||
|
|
||||||
|
sendMockExtensionMessage({
|
||||||
|
command: "openAutofillInlineMenu",
|
||||||
|
isOpeningFullAutofillInlineMenu: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(sendExtensionMessageSpy).toHaveBeenCalledWith("updateAutofillInlineMenuPosition", {
|
expect(sendExtensionMessageSpy).toHaveBeenCalledWith("updateAutofillInlineMenuPosition", {
|
||||||
@@ -1334,17 +1124,38 @@ describe("AutofillOverlayContentService", () => {
|
|||||||
overlayElement: AutofillOverlayElement.List,
|
overlayElement: AutofillOverlayElement.List,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("sends an extension message requesting an re-collection of page details if they need to update", () => {
|
||||||
|
jest.spyOn(autofillOverlayContentService as any, "sendExtensionMessage");
|
||||||
|
autofillOverlayContentService.pageDetailsUpdateRequired = true;
|
||||||
|
|
||||||
|
autofillOverlayContentService["openAutofillInlineMenu"]();
|
||||||
|
sendMockExtensionMessage({ command: "openAutofillInlineMenu" });
|
||||||
|
|
||||||
|
expect(sendExtensionMessageSpy).toHaveBeenCalledWith("bgCollectPageDetails", {
|
||||||
|
sender: "autofillOverlayContentService",
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("addNewVaultItemFromOverlay message handler", () => {
|
describe("addNewVaultItemFromOverlay message handler", () => {
|
||||||
it("sends an extension message with the cipher login details to add to the user's vault", async () => {
|
it("skips sending the message if the overlay list is not visible", async () => {
|
||||||
|
jest
|
||||||
|
.spyOn(autofillOverlayContentService as any, "isInlineMenuListVisible")
|
||||||
|
.mockResolvedValue(false);
|
||||||
|
|
||||||
|
sendMockExtensionMessage({ command: "addNewVaultItemFromOverlay" });
|
||||||
|
await flushPromises();
|
||||||
|
|
||||||
|
expect(sendExtensionMessageSpy).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("sends a message that facilitates adding a new vault item with empty fields", async () => {
|
||||||
jest
|
jest
|
||||||
.spyOn(autofillOverlayContentService as any, "isInlineMenuListVisible")
|
.spyOn(autofillOverlayContentService as any, "isInlineMenuListVisible")
|
||||||
.mockResolvedValue(true);
|
.mockResolvedValue(true);
|
||||||
|
|
||||||
sendMockExtensionMessage({
|
sendMockExtensionMessage({ command: "addNewVaultItemFromOverlay" });
|
||||||
command: "addNewVaultItemFromOverlay",
|
|
||||||
});
|
|
||||||
await flushPromises();
|
await flushPromises();
|
||||||
|
|
||||||
expect(sendExtensionMessageSpy).toHaveBeenCalledWith("autofillOverlayAddNewVaultItem", {
|
expect(sendExtensionMessageSpy).toHaveBeenCalledWith("autofillOverlayAddNewVaultItem", {
|
||||||
@@ -1356,6 +1167,219 @@ describe("AutofillOverlayContentService", () => {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("sends a message that facilitates adding a new vault item with data from user filled fields", async () => {
|
||||||
|
document.body.innerHTML = `
|
||||||
|
<form id="validFormId">
|
||||||
|
<input type="text" id="username-field" placeholder="username" />
|
||||||
|
<input type="password" id="password-field" placeholder="password" />
|
||||||
|
</form>
|
||||||
|
`;
|
||||||
|
const usernameField = document.getElementById(
|
||||||
|
"username-field",
|
||||||
|
) as ElementWithOpId<HTMLInputElement>;
|
||||||
|
const passwordField = document.getElementById(
|
||||||
|
"password-field",
|
||||||
|
) as ElementWithOpId<HTMLInputElement>;
|
||||||
|
usernameField.value = "test-username";
|
||||||
|
passwordField.value = "test-password";
|
||||||
|
jest
|
||||||
|
.spyOn(autofillOverlayContentService as any, "isInlineMenuListVisible")
|
||||||
|
.mockResolvedValue(true);
|
||||||
|
autofillOverlayContentService["userFilledFields"] = {
|
||||||
|
username: usernameField,
|
||||||
|
password: passwordField,
|
||||||
|
};
|
||||||
|
|
||||||
|
sendMockExtensionMessage({ command: "addNewVaultItemFromOverlay" });
|
||||||
|
await flushPromises();
|
||||||
|
|
||||||
|
expect(sendExtensionMessageSpy).toHaveBeenCalledWith("autofillOverlayAddNewVaultItem", {
|
||||||
|
login: {
|
||||||
|
username: "test-username",
|
||||||
|
password: "test-password",
|
||||||
|
uri: "http://localhost/",
|
||||||
|
hostname: "localhost",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("unsetMostRecentlyFocusedField message handler", () => {
|
||||||
|
it("will reset the mostRecentlyFocusedField value to a null value", () => {
|
||||||
|
autofillOverlayContentService["mostRecentlyFocusedField"] =
|
||||||
|
mock<ElementWithOpId<FormFieldElement>>();
|
||||||
|
|
||||||
|
sendMockExtensionMessage({
|
||||||
|
command: "unsetMostRecentlyFocusedField",
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(autofillOverlayContentService["mostRecentlyFocusedField"]).toBeNull();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("messages that trigger a blur of the most recently focused field", () => {
|
||||||
|
const messages = [
|
||||||
|
"blurMostRecentlyFocusedField",
|
||||||
|
"bgUnlockPopoutOpened",
|
||||||
|
"bgVaultItemRepromptPopoutOpened",
|
||||||
|
];
|
||||||
|
|
||||||
|
messages.forEach((message, index) => {
|
||||||
|
const isClosingInlineMenu = index >= 1;
|
||||||
|
it(`will blur the most recently focused field${isClosingInlineMenu ? " and close the inline menu" : ""} when a ${message} message is received`, () => {
|
||||||
|
autofillOverlayContentService["mostRecentlyFocusedField"] =
|
||||||
|
mock<ElementWithOpId<FormFieldElement>>();
|
||||||
|
|
||||||
|
sendMockExtensionMessage({ command: message });
|
||||||
|
|
||||||
|
expect(autofillOverlayContentService["mostRecentlyFocusedField"].blur).toHaveBeenCalled();
|
||||||
|
|
||||||
|
if (isClosingInlineMenu) {
|
||||||
|
expect(sendExtensionMessageSpy).toHaveBeenCalledWith("closeAutofillInlineMenu");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("redirectAutofillInlineMenuFocusOut message handler", () => {
|
||||||
|
let autofillFieldElement: ElementWithOpId<FormFieldElement>;
|
||||||
|
let autofillFieldFocusSpy: jest.SpyInstance;
|
||||||
|
let findTabsSpy: jest.SpyInstance;
|
||||||
|
let previousFocusableElement: HTMLElement;
|
||||||
|
let nextFocusableElement: HTMLElement;
|
||||||
|
let isInlineMenuListVisibleSpy: jest.SpyInstance;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
document.body.innerHTML = `
|
||||||
|
<div class="previous-focusable-element" tabindex="0"></div>
|
||||||
|
<form id="validFormId">
|
||||||
|
<input type="text" id="username-field" placeholder="username" />
|
||||||
|
<input type="password" id="password-field" placeholder="password" />
|
||||||
|
</form>
|
||||||
|
<div class="next-focusable-element" tabindex="0"></div>
|
||||||
|
`;
|
||||||
|
autofillFieldElement = document.getElementById(
|
||||||
|
"username-field",
|
||||||
|
) as ElementWithOpId<FormFieldElement>;
|
||||||
|
autofillFieldElement.opid = "op-1";
|
||||||
|
previousFocusableElement = document.querySelector(
|
||||||
|
".previous-focusable-element",
|
||||||
|
) as HTMLElement;
|
||||||
|
nextFocusableElement = document.querySelector(".next-focusable-element") as HTMLElement;
|
||||||
|
autofillFieldFocusSpy = jest.spyOn(autofillFieldElement, "focus");
|
||||||
|
findTabsSpy = jest.spyOn(autofillOverlayContentService as any, "findTabs");
|
||||||
|
isInlineMenuListVisibleSpy = jest
|
||||||
|
.spyOn(autofillOverlayContentService as any, "isInlineMenuListVisible")
|
||||||
|
.mockResolvedValue(true);
|
||||||
|
autofillOverlayContentService["mostRecentlyFocusedField"] = autofillFieldElement;
|
||||||
|
autofillOverlayContentService["focusableElements"] = [
|
||||||
|
previousFocusableElement,
|
||||||
|
autofillFieldElement,
|
||||||
|
nextFocusableElement,
|
||||||
|
];
|
||||||
|
});
|
||||||
|
|
||||||
|
it("skips focusing an element if the overlay is not visible", async () => {
|
||||||
|
isInlineMenuListVisibleSpy.mockResolvedValue(false);
|
||||||
|
|
||||||
|
sendMockExtensionMessage({
|
||||||
|
command: "redirectAutofillInlineMenuFocusOut",
|
||||||
|
data: { direction: RedirectFocusDirection.Next },
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(findTabsSpy).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("skips focusing an element if no recently focused field exists", async () => {
|
||||||
|
autofillOverlayContentService["mostRecentlyFocusedField"] = undefined;
|
||||||
|
|
||||||
|
sendMockExtensionMessage({
|
||||||
|
command: "redirectAutofillInlineMenuFocusOut",
|
||||||
|
data: { direction: RedirectFocusDirection.Next },
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(findTabsSpy).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("focuses the most recently focused field if the focus direction is `Current`", async () => {
|
||||||
|
sendMockExtensionMessage({
|
||||||
|
command: "redirectAutofillInlineMenuFocusOut",
|
||||||
|
data: { direction: RedirectFocusDirection.Current },
|
||||||
|
});
|
||||||
|
await flushPromises();
|
||||||
|
|
||||||
|
expect(findTabsSpy).not.toHaveBeenCalled();
|
||||||
|
expect(autofillFieldFocusSpy).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("removes the overlay if the focus direction is `Current`", async () => {
|
||||||
|
jest.useFakeTimers();
|
||||||
|
sendMockExtensionMessage({
|
||||||
|
command: "redirectAutofillInlineMenuFocusOut",
|
||||||
|
data: { direction: RedirectFocusDirection.Current },
|
||||||
|
});
|
||||||
|
await flushPromises();
|
||||||
|
jest.advanceTimersByTime(150);
|
||||||
|
|
||||||
|
expect(sendExtensionMessageSpy).toHaveBeenCalledWith("closeAutofillInlineMenu");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("finds all focusable tabs if the focusable elements array is not populated", async () => {
|
||||||
|
autofillOverlayContentService["focusableElements"] = [];
|
||||||
|
findTabsSpy.mockReturnValue([
|
||||||
|
previousFocusableElement,
|
||||||
|
autofillFieldElement,
|
||||||
|
nextFocusableElement,
|
||||||
|
]);
|
||||||
|
|
||||||
|
sendMockExtensionMessage({
|
||||||
|
command: "redirectAutofillInlineMenuFocusOut",
|
||||||
|
data: { direction: RedirectFocusDirection.Next },
|
||||||
|
});
|
||||||
|
await flushPromises();
|
||||||
|
|
||||||
|
expect(findTabsSpy).toHaveBeenCalledWith(globalThis.document.body, { getShadowRoot: true });
|
||||||
|
});
|
||||||
|
|
||||||
|
it("focuses the previous focusable element if the focus direction is `Previous`", async () => {
|
||||||
|
jest.spyOn(previousFocusableElement, "focus");
|
||||||
|
|
||||||
|
sendMockExtensionMessage({
|
||||||
|
command: "redirectAutofillInlineMenuFocusOut",
|
||||||
|
data: { direction: RedirectFocusDirection.Previous },
|
||||||
|
});
|
||||||
|
await flushPromises();
|
||||||
|
|
||||||
|
expect(autofillFieldFocusSpy).not.toHaveBeenCalled();
|
||||||
|
expect(previousFocusableElement.focus).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("focuses the next focusable element if the focus direction is `Next`", async () => {
|
||||||
|
jest.spyOn(nextFocusableElement, "focus");
|
||||||
|
|
||||||
|
sendMockExtensionMessage({
|
||||||
|
command: "redirectAutofillInlineMenuFocusOut",
|
||||||
|
data: { direction: RedirectFocusDirection.Next },
|
||||||
|
});
|
||||||
|
await flushPromises();
|
||||||
|
|
||||||
|
expect(autofillFieldFocusSpy).not.toHaveBeenCalled();
|
||||||
|
expect(nextFocusableElement.focus).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("updateAutofillInlineMenuVisibility message handler", () => {
|
||||||
|
it("updates the inlineMenuVisibility property", () => {
|
||||||
|
sendMockExtensionMessage({
|
||||||
|
command: "updateAutofillInlineMenuVisibility",
|
||||||
|
data: { inlineMenuVisibility: AutofillOverlayVisibility.OnButtonClick },
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(autofillOverlayContentService["inlineMenuVisibility"]).toEqual(
|
||||||
|
AutofillOverlayVisibility.OnButtonClick,
|
||||||
|
);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -157,14 +157,17 @@ export class AutofillOverlayContentService implements AutofillOverlayContentServ
|
|||||||
/**
|
/**
|
||||||
* Removes focus from the most recently focused field element.
|
* Removes focus from the most recently focused field element.
|
||||||
*/
|
*/
|
||||||
blurMostRecentlyFocusedField(isRemovingInlineMenu: boolean = false) {
|
blurMostRecentlyFocusedField(isClosingInlineMenu: boolean = false) {
|
||||||
this.mostRecentlyFocusedField?.blur();
|
this.mostRecentlyFocusedField?.blur();
|
||||||
|
|
||||||
if (isRemovingInlineMenu) {
|
if (isClosingInlineMenu) {
|
||||||
void sendExtensionMessage("closeAutofillInlineMenu");
|
void this.sendExtensionMessage("closeAutofillInlineMenu");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the most recently focused field within the current frame to a `null` value.
|
||||||
|
*/
|
||||||
unsetMostRecentlyFocusedField() {
|
unsetMostRecentlyFocusedField() {
|
||||||
this.mostRecentlyFocusedField = null;
|
this.mostRecentlyFocusedField = null;
|
||||||
}
|
}
|
||||||
@@ -694,6 +697,14 @@ export class AutofillOverlayContentService implements AutofillOverlayContentServ
|
|||||||
return !isLoginCipherField;
|
return !isLoginCipherField;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validates whether a field is considered to be "hidden" based on the field's attributes.
|
||||||
|
* If the field is hidden, a fallback listener will be set up to ensure that the
|
||||||
|
* field will have the inline menu set up on it when it becomes visible.
|
||||||
|
*
|
||||||
|
* @param formFieldElement - The form field element that triggered the focus event.
|
||||||
|
* @param autofillFieldData - Autofill field data captured from the form field element.
|
||||||
|
*/
|
||||||
private isHiddenField(
|
private isHiddenField(
|
||||||
formFieldElement: ElementWithOpId<FormFieldElement>,
|
formFieldElement: ElementWithOpId<FormFieldElement>,
|
||||||
autofillFieldData: AutofillField,
|
autofillFieldData: AutofillField,
|
||||||
@@ -708,6 +719,13 @@ export class AutofillOverlayContentService implements AutofillOverlayContentServ
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets up a fallback listener that will facilitate setting up the
|
||||||
|
* inline menu on the field when it becomes visible and focused.
|
||||||
|
*
|
||||||
|
* @param formFieldElement - The form field element that triggered the focus event.
|
||||||
|
* @param autofillFieldData - Autofill field data captured from the form field element.
|
||||||
|
*/
|
||||||
private setupHiddenFieldFallbackListener(
|
private setupHiddenFieldFallbackListener(
|
||||||
formFieldElement: ElementWithOpId<FormFieldElement>,
|
formFieldElement: ElementWithOpId<FormFieldElement>,
|
||||||
autofillFieldData: AutofillField,
|
autofillFieldData: AutofillField,
|
||||||
@@ -716,11 +734,23 @@ export class AutofillOverlayContentService implements AutofillOverlayContentServ
|
|||||||
formFieldElement.addEventListener(EVENTS.FOCUS, this.handleHiddenFieldFocusEvent);
|
formFieldElement.addEventListener(EVENTS.FOCUS, this.handleHiddenFieldFocusEvent);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes the fallback listener that facilitates setting up the inline
|
||||||
|
* menu on the field when it becomes visible and focused.
|
||||||
|
*
|
||||||
|
* @param formFieldElement - The form field element that triggered the focus event.
|
||||||
|
*/
|
||||||
private removeHiddenFieldFallbackListener(formFieldElement: ElementWithOpId<FormFieldElement>) {
|
private removeHiddenFieldFallbackListener(formFieldElement: ElementWithOpId<FormFieldElement>) {
|
||||||
formFieldElement.removeEventListener(EVENTS.FOCUS, this.handleHiddenFieldFocusEvent);
|
formFieldElement.removeEventListener(EVENTS.FOCUS, this.handleHiddenFieldFocusEvent);
|
||||||
this.hiddenFormFieldElements.delete(formFieldElement);
|
this.hiddenFormFieldElements.delete(formFieldElement);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles the focus event on a hidden field. When
|
||||||
|
* triggered, the inline menu is set up on the field.
|
||||||
|
*
|
||||||
|
* @param event - The focus event.
|
||||||
|
*/
|
||||||
private handleHiddenFieldFocusEvent = (event: FocusEvent) => {
|
private handleHiddenFieldFocusEvent = (event: FocusEvent) => {
|
||||||
const formFieldElement = event.target as ElementWithOpId<FormFieldElement>;
|
const formFieldElement = event.target as ElementWithOpId<FormFieldElement>;
|
||||||
const autofillFieldData = this.hiddenFormFieldElements.get(formFieldElement);
|
const autofillFieldData = this.hiddenFormFieldElements.get(formFieldElement);
|
||||||
@@ -766,8 +796,6 @@ export class AutofillOverlayContentService implements AutofillOverlayContentServ
|
|||||||
globalThis.removeEventListener(EVENTS.RESIZE, this.handleOverlayRepositionEvent);
|
globalThis.removeEventListener(EVENTS.RESIZE, this.handleOverlayRepositionEvent);
|
||||||
}
|
}
|
||||||
|
|
||||||
private overlayRepositionTimeout: number | NodeJS.Timeout;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handles the resize or scroll events that enact
|
* Handles the resize or scroll events that enact
|
||||||
* repositioning of existing overlay elements.
|
* repositioning of existing overlay elements.
|
||||||
@@ -783,6 +811,9 @@ export class AutofillOverlayContentService implements AutofillOverlayContentServ
|
|||||||
this.userInteractionEventTimeout = setTimeout(this.triggerOverlayRepositionUpdates, 750);
|
this.userInteractionEventTimeout = setTimeout(this.triggerOverlayRepositionUpdates, 750);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Triggers a rebuild of a sub frame's offsets within the tab.
|
||||||
|
*/
|
||||||
private rebuildSubFrameOffsets() {
|
private rebuildSubFrameOffsets() {
|
||||||
this.clearRecalculateSubFrameOffsetsTimeout();
|
this.clearRecalculateSubFrameOffsetsTimeout();
|
||||||
this.recalculateSubFrameOffsetsTimeout = setTimeout(
|
this.recalculateSubFrameOffsetsTimeout = setTimeout(
|
||||||
@@ -840,12 +871,19 @@ export class AutofillOverlayContentService implements AutofillOverlayContentServ
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clears the timeout that facilitates recalculating the sub frame offsets.
|
||||||
|
*/
|
||||||
private clearRecalculateSubFrameOffsetsTimeout() {
|
private clearRecalculateSubFrameOffsetsTimeout() {
|
||||||
if (this.recalculateSubFrameOffsetsTimeout) {
|
if (this.recalculateSubFrameOffsetsTimeout) {
|
||||||
clearTimeout(this.recalculateSubFrameOffsetsTimeout);
|
clearTimeout(this.recalculateSubFrameOffsetsTimeout);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if the focused field is present within the bounds of the viewport.
|
||||||
|
* If not present, the inline menu will be closed.
|
||||||
|
*/
|
||||||
private isFocusedFieldWithinViewportBounds() {
|
private isFocusedFieldWithinViewportBounds() {
|
||||||
const focusedFieldRectsTop = this.focusedFieldData?.focusedFieldRects?.top;
|
const focusedFieldRectsTop = this.focusedFieldData?.focusedFieldRects?.top;
|
||||||
return (
|
return (
|
||||||
@@ -855,6 +893,11 @@ export class AutofillOverlayContentService implements AutofillOverlayContentServ
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a value that indicates if we should hide the inline menu list due to a filled field.
|
||||||
|
*
|
||||||
|
* @param formFieldElement - The form field element that triggered the focus event.
|
||||||
|
*/
|
||||||
private async hideAutofillInlineMenuListOnFilledField(
|
private async hideAutofillInlineMenuListOnFilledField(
|
||||||
formFieldElement?: FillableFormFieldElement,
|
formFieldElement?: FillableFormFieldElement,
|
||||||
): Promise<boolean> {
|
): Promise<boolean> {
|
||||||
@@ -864,6 +907,9 @@ export class AutofillOverlayContentService implements AutofillOverlayContentServ
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indicates whether the most recently focused field has a value.
|
||||||
|
*/
|
||||||
private mostRecentlyFocusedFieldHasValue() {
|
private mostRecentlyFocusedFieldHasValue() {
|
||||||
return Boolean((this.mostRecentlyFocusedField as FillableFormFieldElement)?.value);
|
return Boolean((this.mostRecentlyFocusedField as FillableFormFieldElement)?.value);
|
||||||
}
|
}
|
||||||
@@ -889,7 +935,7 @@ export class AutofillOverlayContentService implements AutofillOverlayContentServ
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.mostRecentlyFocusedField = null;
|
this.unsetMostRecentlyFocusedField();
|
||||||
void this.sendExtensionMessage("closeAutofillInlineMenu", {
|
void this.sendExtensionMessage("closeAutofillInlineMenu", {
|
||||||
forceCloseAutofillInlineMenu: true,
|
forceCloseAutofillInlineMenu: true,
|
||||||
});
|
});
|
||||||
@@ -909,6 +955,12 @@ export class AutofillOverlayContentService implements AutofillOverlayContentServ
|
|||||||
return documentRoot?.activeElement;
|
return documentRoot?.activeElement;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Queries all iframe elements within the document and returns the
|
||||||
|
* sub frame offsets for each iframe element.
|
||||||
|
*
|
||||||
|
* @param message - The message object from the extension.
|
||||||
|
*/
|
||||||
private async getSubFrameOffsets(
|
private async getSubFrameOffsets(
|
||||||
message: AutofillExtensionMessage,
|
message: AutofillExtensionMessage,
|
||||||
): Promise<SubFrameOffsetData | null> {
|
): Promise<SubFrameOffsetData | null> {
|
||||||
@@ -930,6 +982,14 @@ export class AutofillOverlayContentService implements AutofillOverlayContentServ
|
|||||||
return this.calculateSubFrameOffsets(iframeElement, subFrameUrl);
|
return this.calculateSubFrameOffsets(iframeElement, subFrameUrl);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculates the bounding rect for the queried frame and returns the
|
||||||
|
* offset data for the sub frame.
|
||||||
|
*
|
||||||
|
* @param iframeElement - The iframe element to calculate the sub frame offsets for.
|
||||||
|
* @param subFrameUrl - The URL of the sub frame.
|
||||||
|
* @param frameId - The frame ID of the sub frame.
|
||||||
|
*/
|
||||||
private calculateSubFrameOffsets(
|
private calculateSubFrameOffsets(
|
||||||
iframeElement: HTMLIFrameElement,
|
iframeElement: HTMLIFrameElement,
|
||||||
subFrameUrl?: string,
|
subFrameUrl?: string,
|
||||||
@@ -950,6 +1010,11 @@ export class AutofillOverlayContentService implements AutofillOverlayContentServ
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Posts a message to the parent frame to calculate the sub frame offset of the current frame.
|
||||||
|
*
|
||||||
|
* @param message - The message object from the extension.
|
||||||
|
*/
|
||||||
private getSubFrameOffsetsFromWindowMessage(message: any) {
|
private getSubFrameOffsetsFromWindowMessage(message: any) {
|
||||||
globalThis.parent.postMessage(
|
globalThis.parent.postMessage(
|
||||||
{
|
{
|
||||||
@@ -966,14 +1031,24 @@ export class AutofillOverlayContentService implements AutofillOverlayContentServ
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles window messages that are sent to the current frame. Will trigger a
|
||||||
|
* calculation of the sub frame offsets through the parent frame.
|
||||||
|
*
|
||||||
|
* @param event - The message event.
|
||||||
|
*/
|
||||||
private handleWindowMessageEvent = (event: MessageEvent) => {
|
private handleWindowMessageEvent = (event: MessageEvent) => {
|
||||||
if (event.data?.command !== "calculateSubFramePositioning") {
|
if (event.data?.command === "calculateSubFramePositioning") {
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
void this.calculateSubFramePositioning(event);
|
void this.calculateSubFramePositioning(event);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculates the sub frame positioning for the current frame
|
||||||
|
* through all parent frames until the top frame is reached.
|
||||||
|
*
|
||||||
|
* @param event - The message event.
|
||||||
|
*/
|
||||||
private calculateSubFramePositioning = async (event: MessageEvent) => {
|
private calculateSubFramePositioning = async (event: MessageEvent) => {
|
||||||
const subFrameData = event.data.subFrameData;
|
const subFrameData = event.data.subFrameData;
|
||||||
let subFrameOffsets: SubFrameOffsetData;
|
let subFrameOffsets: SubFrameOffsetData;
|
||||||
@@ -1006,30 +1081,49 @@ export class AutofillOverlayContentService implements AutofillOverlayContentServ
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates the local reference to the inline menu visibility setting.
|
||||||
|
*
|
||||||
|
* @param data - The data object from the extension message.
|
||||||
|
*/
|
||||||
private updateAutofillInlineMenuVisibility({ data }: AutofillExtensionMessage) {
|
private updateAutofillInlineMenuVisibility({ data }: AutofillExtensionMessage) {
|
||||||
if (isNaN(data?.inlineMenuVisibility)) {
|
if (!isNaN(data?.inlineMenuVisibility)) {
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.inlineMenuVisibility = data.inlineMenuVisibility;
|
this.inlineMenuVisibility = data.inlineMenuVisibility;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if a field is currently filling within an frame in the tab.
|
||||||
|
*/
|
||||||
private async isFieldCurrentlyFilling() {
|
private async isFieldCurrentlyFilling() {
|
||||||
return (await this.sendExtensionMessage("checkIsFieldCurrentlyFilling")) === true;
|
return (await this.sendExtensionMessage("checkIsFieldCurrentlyFilling")) === true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if the inline menu button is visible at the top frame.
|
||||||
|
*/
|
||||||
private async isInlineMenuButtonVisible() {
|
private async isInlineMenuButtonVisible() {
|
||||||
return (await this.sendExtensionMessage("checkIsAutofillInlineMenuButtonVisible")) === true;
|
return (await this.sendExtensionMessage("checkIsAutofillInlineMenuButtonVisible")) === true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if the inline menu list if visible at the top frame.
|
||||||
|
*/
|
||||||
private async isInlineMenuListVisible() {
|
private async isInlineMenuListVisible() {
|
||||||
return (await this.sendExtensionMessage("checkIsAutofillInlineMenuListVisible")) === true;
|
return (await this.sendExtensionMessage("checkIsAutofillInlineMenuListVisible")) === true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if the current tab contains ciphers that can be used to populate the inline menu.
|
||||||
|
*/
|
||||||
private async isInlineMenuCiphersPopulated() {
|
private async isInlineMenuCiphersPopulated() {
|
||||||
return (await this.sendExtensionMessage("checkIsInlineMenuCiphersPopulated")) === true;
|
return (await this.sendExtensionMessage("checkIsInlineMenuCiphersPopulated")) === true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Triggers a validation to ensure that the inline menu is repositioned only when the
|
||||||
|
* current frame contains the focused field at any given depth level.
|
||||||
|
*/
|
||||||
private async checkShouldRepositionInlineMenu() {
|
private async checkShouldRepositionInlineMenu() {
|
||||||
return (await this.sendExtensionMessage("checkShouldRepositionInlineMenu")) === true;
|
return (await this.sendExtensionMessage("checkShouldRepositionInlineMenu")) === true;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user