mirror of
https://github.com/bitwarden/browser.git
synced 2025-01-09 19:28:06 +01:00
[PM-5189] Working through further issues on positioning of inline menu
This commit is contained in:
parent
582cdf17dd
commit
4114b538e5
@ -65,6 +65,7 @@ export type OverlayBackgroundExtensionMessage = {
|
||||
details?: AutofillPageDetails;
|
||||
isFieldCurrentlyFocused?: boolean;
|
||||
isFieldCurrentlyFilling?: boolean;
|
||||
isInlineMenuElementVisible?: boolean;
|
||||
subFrameData?: SubFrameOffsetData;
|
||||
focusedFieldData?: FocusedFieldData;
|
||||
styles?: Partial<CSSStyleDeclaration>;
|
||||
@ -119,9 +120,12 @@ export type OverlayBackgroundExtensionMessageHandlers = {
|
||||
message,
|
||||
sender,
|
||||
}: BackgroundOnMessageHandlerParams) => Promise<void>;
|
||||
toggleAutofillInlineMenuHidden: ({ message, sender }: BackgroundOnMessageHandlerParams) => void;
|
||||
checkIsAutofillInlineMenuButtonVisible: ({ sender }: BackgroundSenderParam) => void;
|
||||
checkIsAutofillInlineMenuListVisible: ({ sender }: BackgroundSenderParam) => void;
|
||||
updateAutofillInlineMenuElementIsVisibleStatus: ({
|
||||
message,
|
||||
sender,
|
||||
}: BackgroundOnMessageHandlerParams) => void;
|
||||
checkIsAutofillInlineMenuButtonVisible: () => void;
|
||||
checkIsAutofillInlineMenuListVisible: () => void;
|
||||
getCurrentTabFrameId: ({ sender }: BackgroundSenderParam) => number;
|
||||
updateSubFrameData: ({ message, sender }: BackgroundOnMessageHandlerParams) => void;
|
||||
triggerSubFrameFocusInRebuild: ({ sender }: BackgroundSenderParam) => void;
|
||||
|
@ -519,9 +519,10 @@ describe("OverlayBackground", () => {
|
||||
await flushPromises();
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
beforeEach(async () => {
|
||||
sender = mock<chrome.runtime.MessageSender>({ tab, frameId: middleFrameId });
|
||||
jest.useFakeTimers();
|
||||
await initOverlayElementPorts();
|
||||
});
|
||||
|
||||
it("skips updating the position of either inline menu element if a field is not currently focused", async () => {
|
||||
@ -559,11 +560,10 @@ describe("OverlayBackground", () => {
|
||||
sendMockExtensionMessage({ command: "triggerAutofillOverlayReposition" }, sender);
|
||||
await flushUpdateInlineMenuPromises();
|
||||
|
||||
expect(tabsSendMessageSpy).toHaveBeenCalledWith(
|
||||
sender.tab,
|
||||
{ command: "toggleAutofillInlineMenuHidden", isInlineMenuHidden: true },
|
||||
{ frameId: 0 },
|
||||
);
|
||||
expect(buttonPortSpy.postMessage).toHaveBeenCalledWith({
|
||||
command: "toggleAutofillInlineMenuHidden",
|
||||
styles: { display: "none" },
|
||||
});
|
||||
expect(tabsSendMessageSpy).toHaveBeenCalledWith(
|
||||
sender.tab,
|
||||
{
|
||||
@ -1312,73 +1312,35 @@ describe("OverlayBackground", () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe("toggleAutofillInlineMenuHidden message handler", () => {
|
||||
beforeEach(async () => {
|
||||
await initOverlayElementPorts();
|
||||
});
|
||||
|
||||
it("returns early if the sender tab is not equal to the focused field tab", async () => {
|
||||
describe("checkIsAutofillInlineMenuButtonVisible message handler", () => {
|
||||
it("returns true when the inline menu button is visible", async () => {
|
||||
overlayBackground["isInlineMenuButtonVisible"] = true;
|
||||
const sender = mock<chrome.runtime.MessageSender>({ tab: { id: 1 } });
|
||||
const focusedFieldData = createFocusedFieldDataMock({ tabId: 2 });
|
||||
sendMockExtensionMessage({ command: "updateFocusedFieldData", focusedFieldData });
|
||||
|
||||
sendMockExtensionMessage({ command: "toggleAutofillInlineMenuHidden" }, sender);
|
||||
|
||||
expect(tabsSendMessageSpy).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("posts a message to the overlay button and list which hides the menu", async () => {
|
||||
const message = {
|
||||
command: "toggleAutofillInlineMenuHidden",
|
||||
isInlineMenuHidden: true,
|
||||
setTransparentInlineMenu: false,
|
||||
};
|
||||
|
||||
sendMockExtensionMessage(message);
|
||||
sendMockExtensionMessage(
|
||||
{ command: "checkIsAutofillInlineMenuButtonVisible" },
|
||||
sender,
|
||||
sendResponse,
|
||||
);
|
||||
await flushPromises();
|
||||
|
||||
expect(buttonPortSpy.postMessage).toHaveBeenCalledWith({
|
||||
command: "toggleAutofillInlineMenuHidden",
|
||||
styles: {
|
||||
display: "none",
|
||||
opacity: "1",
|
||||
},
|
||||
});
|
||||
expect(listPortSpy.postMessage).toHaveBeenCalledWith({
|
||||
command: "toggleAutofillInlineMenuHidden",
|
||||
styles: {
|
||||
display: "none",
|
||||
opacity: "1",
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("checkIsAutofillInlineMenuButtonVisible", () => {
|
||||
it("sends a message to the top frame of the tab to identify if the inline menu button is visible", () => {
|
||||
const sender = mock<chrome.runtime.MessageSender>({ tab: { id: 1 } });
|
||||
|
||||
sendMockExtensionMessage({ command: "checkIsAutofillInlineMenuButtonVisible" }, sender);
|
||||
|
||||
expect(tabsSendMessageSpy).toHaveBeenCalledWith(
|
||||
sender.tab,
|
||||
{ command: "checkIsAutofillInlineMenuButtonVisible" },
|
||||
{ frameId: 0 },
|
||||
);
|
||||
expect(sendResponse).toHaveBeenCalledWith(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe("checkIsAutofillInlineMenuListVisible message handler", () => {
|
||||
it("sends a message to the top frame of the tab to identify if the inline menu list is visible", () => {
|
||||
it("returns true when the inline menu list is visible", async () => {
|
||||
overlayBackground["isInlineMenuListVisible"] = true;
|
||||
const sender = mock<chrome.runtime.MessageSender>({ tab: { id: 1 } });
|
||||
|
||||
sendMockExtensionMessage({ command: "checkIsAutofillInlineMenuListVisible" }, sender);
|
||||
|
||||
expect(tabsSendMessageSpy).toHaveBeenCalledWith(
|
||||
sender.tab,
|
||||
sendMockExtensionMessage(
|
||||
{ command: "checkIsAutofillInlineMenuListVisible" },
|
||||
{ frameId: 0 },
|
||||
sender,
|
||||
sendResponse,
|
||||
);
|
||||
await flushPromises();
|
||||
|
||||
expect(sendResponse).toHaveBeenCalledWith(true);
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -74,6 +74,8 @@ export class OverlayBackground implements OverlayBackgroundInterface {
|
||||
private focusedFieldData: FocusedFieldData;
|
||||
private isFieldCurrentlyFocused: boolean = false;
|
||||
private isFieldCurrentlyFilling: boolean = false;
|
||||
private isInlineMenuButtonVisible: boolean = false;
|
||||
private isInlineMenuListVisible: boolean = false;
|
||||
private iconsServerUrl: string;
|
||||
private readonly extensionMessageHandlers: OverlayBackgroundExtensionMessageHandlers = {
|
||||
autofillOverlayElementClosed: ({ message, sender }) =>
|
||||
@ -94,11 +96,10 @@ export class OverlayBackground implements OverlayBackgroundInterface {
|
||||
focusAutofillInlineMenuList: () => this.focusInlineMenuList(),
|
||||
updateAutofillInlineMenuPosition: ({ message, sender }) =>
|
||||
this.updateInlineMenuPosition(message, sender),
|
||||
toggleAutofillInlineMenuHidden: ({ message, sender }) =>
|
||||
this.toggleInlineMenuHidden(message, sender),
|
||||
checkIsAutofillInlineMenuButtonVisible: ({ sender }) =>
|
||||
this.checkIsInlineMenuButtonVisible(sender),
|
||||
checkIsAutofillInlineMenuListVisible: ({ sender }) => this.checkIsInlineMenuListVisible(sender),
|
||||
updateAutofillInlineMenuElementIsVisibleStatus: ({ message, sender }) =>
|
||||
this.updateInlineMenuElementIsVisibleStatus(message, sender),
|
||||
checkIsAutofillInlineMenuButtonVisible: () => this.checkIsInlineMenuButtonVisible(),
|
||||
checkIsAutofillInlineMenuListVisible: () => this.checkIsInlineMenuListVisible(),
|
||||
getCurrentTabFrameId: ({ sender }) => this.getSenderFrameId(sender),
|
||||
updateSubFrameData: ({ message, sender }) => this.updateSubFrameData(message, sender),
|
||||
triggerSubFrameFocusInRebuild: ({ sender }) => this.triggerSubFrameFocusInRebuild(sender),
|
||||
@ -453,7 +454,7 @@ export class OverlayBackground implements OverlayBackgroundInterface {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!(await this.checkIsInlineMenuButtonVisible(sender))) {
|
||||
if (!this.checkIsInlineMenuButtonVisible()) {
|
||||
void this.toggleInlineMenuHidden(
|
||||
{ isInlineMenuHidden: false, setTransparentInlineMenu: true },
|
||||
sender,
|
||||
@ -519,7 +520,7 @@ export class OverlayBackground implements OverlayBackgroundInterface {
|
||||
* if it is open, otherwise it will check the inline menu button.
|
||||
*/
|
||||
private checkInlineMenuFocused(sender: chrome.runtime.MessageSender) {
|
||||
if (sender.tab.id !== this.focusedFieldData?.tabId) {
|
||||
if (!this.senderTabHasFocusedField(sender)) {
|
||||
return;
|
||||
}
|
||||
|
||||
@ -561,6 +562,8 @@ export class OverlayBackground implements OverlayBackgroundInterface {
|
||||
const sendOptions = { frameId: 0 };
|
||||
if (forceCloseInlineMenu) {
|
||||
void BrowserApi.tabSendMessage(sender.tab, { command, overlayElement }, sendOptions);
|
||||
this.isInlineMenuButtonVisible = false;
|
||||
this.isInlineMenuListVisible = false;
|
||||
return;
|
||||
}
|
||||
|
||||
@ -574,9 +577,18 @@ export class OverlayBackground implements OverlayBackgroundInterface {
|
||||
{ command, overlayElement: AutofillOverlayElement.List },
|
||||
sendOptions,
|
||||
);
|
||||
this.isInlineMenuListVisible = false;
|
||||
return;
|
||||
}
|
||||
|
||||
if (overlayElement === AutofillOverlayElement.Button) {
|
||||
this.isInlineMenuButtonVisible = false;
|
||||
}
|
||||
|
||||
if (overlayElement === AutofillOverlayElement.List) {
|
||||
this.isInlineMenuListVisible = false;
|
||||
}
|
||||
|
||||
void BrowserApi.tabSendMessage(sender.tab, { command, overlayElement }, sendOptions);
|
||||
}
|
||||
|
||||
@ -619,21 +631,24 @@ export class OverlayBackground implements OverlayBackgroundInterface {
|
||||
{ overlayElement }: OverlayBackgroundExtensionMessage,
|
||||
sender: chrome.runtime.MessageSender,
|
||||
) {
|
||||
if (sender.tab.id !== this.focusedFieldData?.tabId) {
|
||||
if (!this.senderTabHasFocusedField(sender)) {
|
||||
this.expiredPorts.forEach((port) => port.disconnect());
|
||||
this.expiredPorts = [];
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (overlayElement === AutofillOverlayElement.Button) {
|
||||
this.inlineMenuButtonPort?.disconnect();
|
||||
this.inlineMenuButtonPort = null;
|
||||
this.isInlineMenuButtonVisible = false;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
this.inlineMenuListPort?.disconnect();
|
||||
this.inlineMenuListPort = null;
|
||||
this.isInlineMenuListVisible = false;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -647,7 +662,7 @@ export class OverlayBackground implements OverlayBackgroundInterface {
|
||||
{ overlayElement }: { overlayElement?: string },
|
||||
sender: chrome.runtime.MessageSender,
|
||||
) {
|
||||
if (!overlayElement || sender.tab.id !== this.focusedFieldData?.tabId) {
|
||||
if (!overlayElement || !this.senderTabHasFocusedField(sender)) {
|
||||
return;
|
||||
}
|
||||
|
||||
@ -686,6 +701,32 @@ export class OverlayBackground implements OverlayBackgroundInterface {
|
||||
this.startInlineMenuFadeIn();
|
||||
}
|
||||
|
||||
/**
|
||||
* Triggers an update of the inline menu's visibility after the top level frame
|
||||
* appends the element to the DOM.
|
||||
*
|
||||
* @param message - The message received from the content script
|
||||
* @param sender - The sender of the port message
|
||||
*/
|
||||
private updateInlineMenuElementIsVisibleStatus(
|
||||
message: OverlayBackgroundExtensionMessage,
|
||||
sender: chrome.runtime.MessageSender,
|
||||
) {
|
||||
if (!this.senderTabHasFocusedField(sender)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const { overlayElement, isInlineMenuElementVisible } = message;
|
||||
if (overlayElement === AutofillOverlayElement.Button) {
|
||||
this.isInlineMenuButtonVisible = isInlineMenuElementVisible;
|
||||
return;
|
||||
}
|
||||
|
||||
if (overlayElement === AutofillOverlayElement.List) {
|
||||
this.isInlineMenuListVisible = isInlineMenuElementVisible;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles updating the opacity of both the inline menu button and list.
|
||||
* This is used to simultaneously fade in the inline menu elements.
|
||||
@ -797,7 +838,7 @@ export class OverlayBackground implements OverlayBackgroundInterface {
|
||||
{ isInlineMenuHidden, setTransparentInlineMenu }: ToggleInlineMenuHiddenMessage,
|
||||
sender: chrome.runtime.MessageSender,
|
||||
) {
|
||||
if (sender.tab.id !== this.focusedFieldData?.tabId) {
|
||||
if (!this.senderTabHasFocusedField(sender)) {
|
||||
return;
|
||||
}
|
||||
|
||||
@ -810,15 +851,16 @@ export class OverlayBackground implements OverlayBackgroundInterface {
|
||||
styles = { ...styles, opacity };
|
||||
}
|
||||
|
||||
await BrowserApi.tabSendMessage(
|
||||
sender.tab,
|
||||
{ command: "toggleAutofillInlineMenuHidden", isInlineMenuHidden },
|
||||
{ frameId: 0 },
|
||||
);
|
||||
|
||||
const portMessage = { command: "toggleAutofillInlineMenuHidden", styles };
|
||||
this.inlineMenuButtonPort?.postMessage(portMessage);
|
||||
this.inlineMenuListPort?.postMessage(portMessage);
|
||||
if (this.inlineMenuButtonPort) {
|
||||
this.isInlineMenuButtonVisible = !isInlineMenuHidden;
|
||||
this.inlineMenuButtonPort.postMessage(portMessage);
|
||||
}
|
||||
|
||||
if (this.inlineMenuListPort) {
|
||||
this.isInlineMenuListVisible = !isInlineMenuHidden;
|
||||
this.inlineMenuListPort.postMessage(portMessage);
|
||||
}
|
||||
|
||||
if (setTransparentInlineMenu) {
|
||||
this.startInlineMenuFadeIn();
|
||||
@ -1010,7 +1052,7 @@ export class OverlayBackground implements OverlayBackgroundInterface {
|
||||
* @param sender - The sender of the port message
|
||||
*/
|
||||
private getNewVaultItemDetails({ sender }: chrome.runtime.Port) {
|
||||
if (sender.tab.id !== this.focusedFieldData.tabId) {
|
||||
if (!this.senderTabHasFocusedField(sender)) {
|
||||
return;
|
||||
}
|
||||
|
||||
@ -1095,33 +1137,17 @@ export class OverlayBackground implements OverlayBackgroundInterface {
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a message to the top level frame of the sender to check if the inline menu button is visible.
|
||||
*
|
||||
* @param sender - The sender of the message
|
||||
* Returns the visibility status of the inline menu button.
|
||||
*/
|
||||
private async checkIsInlineMenuButtonVisible(
|
||||
sender: chrome.runtime.MessageSender,
|
||||
): Promise<boolean> {
|
||||
return await BrowserApi.tabSendMessage(
|
||||
sender.tab,
|
||||
{ command: "checkIsAutofillInlineMenuButtonVisible" },
|
||||
{ frameId: 0 },
|
||||
);
|
||||
private checkIsInlineMenuButtonVisible(): boolean {
|
||||
return this.isInlineMenuButtonVisible;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a message to the top level frame of the sender to check if the inline menu list is visible.
|
||||
*
|
||||
* @param sender - The sender of the message
|
||||
* Returns the visibility status of the inline menu list.
|
||||
*/
|
||||
private async checkIsInlineMenuListVisible(
|
||||
sender: chrome.runtime.MessageSender,
|
||||
): Promise<boolean> {
|
||||
return await BrowserApi.tabSendMessage(
|
||||
sender.tab,
|
||||
{ command: "checkIsAutofillInlineMenuListVisible" },
|
||||
{ frameId: 0 },
|
||||
);
|
||||
private checkIsInlineMenuListVisible(): boolean {
|
||||
return this.isInlineMenuListVisible;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -1132,7 +1158,7 @@ export class OverlayBackground implements OverlayBackgroundInterface {
|
||||
* @param sender - The sender of the message
|
||||
*/
|
||||
private checkIsInlineMenuCiphersPopulated(sender: chrome.runtime.MessageSender) {
|
||||
return sender.tab.id === this.focusedFieldData.tabId && this.inlineMenuCiphers.size > 0;
|
||||
return this.senderTabHasFocusedField(sender) && this.inlineMenuCiphers.size > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -1166,7 +1192,7 @@ export class OverlayBackground implements OverlayBackgroundInterface {
|
||||
* @param sender - The sender of the message
|
||||
*/
|
||||
private checkShouldRepositionInlineMenu(sender: chrome.runtime.MessageSender): boolean {
|
||||
if (!this.focusedFieldData || sender.tab.id !== this.focusedFieldData.tabId) {
|
||||
if (!this.focusedFieldData || !this.senderTabHasFocusedField(sender)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -1186,6 +1212,15 @@ export class OverlayBackground implements OverlayBackgroundInterface {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Identifies if the sender tab is the same as the focused field's tab.
|
||||
*
|
||||
* @param sender - The sender of the message
|
||||
*/
|
||||
private senderTabHasFocusedField(sender: chrome.runtime.MessageSender) {
|
||||
return sender.tab.id === this.focusedFieldData?.tabId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Triggers when a scroll or resize event occurs within a tab. Will reposition the inline menu
|
||||
* if the focused field is within the viewport.
|
||||
@ -1448,10 +1483,12 @@ export class OverlayBackground implements OverlayBackgroundInterface {
|
||||
private handlePortOnDisconnect = (port: chrome.runtime.Port) => {
|
||||
if (port.name === AutofillOverlayPort.List) {
|
||||
this.inlineMenuListPort = null;
|
||||
this.isInlineMenuListVisible = false;
|
||||
}
|
||||
|
||||
if (port.name === AutofillOverlayPort.Button) {
|
||||
this.inlineMenuButtonPort = null;
|
||||
this.isInlineMenuButtonVisible = false;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
@ -1,5 +1,6 @@
|
||||
import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status";
|
||||
|
||||
import { AutofillOverlayElementType } from "../../enums/autofill-overlay.enum";
|
||||
import AutofillScript from "../../models/autofill-script";
|
||||
|
||||
export type AutofillExtensionMessage = {
|
||||
@ -13,7 +14,7 @@ export type AutofillExtensionMessage = {
|
||||
pageDetailsUrl?: string;
|
||||
ciphers?: any;
|
||||
isInlineMenuHidden?: boolean;
|
||||
overlayElement?: string;
|
||||
overlayElement?: AutofillOverlayElementType;
|
||||
isFocusingFieldElement?: boolean;
|
||||
authStatus?: AuthenticationStatus;
|
||||
isOpeningFullInlineMenu?: boolean;
|
||||
|
@ -3,6 +3,9 @@ export const AutofillOverlayElement = {
|
||||
List: "autofill-inline-menu-list",
|
||||
} as const;
|
||||
|
||||
export type AutofillOverlayElementType =
|
||||
(typeof AutofillOverlayElement)[keyof typeof AutofillOverlayElement];
|
||||
|
||||
export const AutofillOverlayPort = {
|
||||
Button: "autofill-inline-menu-button-port",
|
||||
ButtonMessageConnector: "autofill-inline-menu-button-message-connector",
|
||||
|
@ -4,9 +4,6 @@ export type InlineMenuExtensionMessageHandlers = {
|
||||
[key: string]: CallableFunction;
|
||||
closeAutofillInlineMenu: ({ message }: AutofillExtensionMessageParam) => void;
|
||||
appendAutofillInlineMenuToDom: ({ message }: AutofillExtensionMessageParam) => Promise<void>;
|
||||
toggleAutofillInlineMenuHidden: ({ message }: AutofillExtensionMessageParam) => void;
|
||||
checkIsAutofillInlineMenuButtonVisible: () => boolean;
|
||||
checkIsAutofillInlineMenuListVisible: () => boolean;
|
||||
};
|
||||
|
||||
export interface AutofillInlineMenuContentService {
|
||||
|
@ -1,5 +1,3 @@
|
||||
import { mock } from "jest-mock-extended";
|
||||
|
||||
import AutofillInit from "../../../content/autofill-init";
|
||||
import { AutofillOverlayElement } from "../../../enums/autofill-overlay.enum";
|
||||
import { createMutationRecordMock } from "../../../spec/autofill-mocks";
|
||||
@ -13,7 +11,6 @@ describe("AutofillInlineMenuContentService", () => {
|
||||
let autofillInit: AutofillInit;
|
||||
let sendExtensionMessageSpy: jest.SpyInstance;
|
||||
let observeBodyMutationsSpy: jest.SpyInstance;
|
||||
const sendResponseSpy = jest.fn();
|
||||
|
||||
beforeEach(() => {
|
||||
globalThis.document.body.innerHTML = "";
|
||||
@ -141,76 +138,6 @@ describe("AutofillInlineMenuContentService", () => {
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("toggleAutofillInlineMenuHidden message handler", () => {
|
||||
it("sets the inline elements as hidden if the elements do not exist", () => {
|
||||
sendMockExtensionMessage({
|
||||
command: "toggleAutofillInlineMenuHidden",
|
||||
isInlineMenuHidden: false,
|
||||
});
|
||||
|
||||
expect(autofillInlineMenuContentService["isButtonVisible"]).toBe(false);
|
||||
expect(autofillInlineMenuContentService["isListVisible"]).toBe(false);
|
||||
});
|
||||
|
||||
it("sets the inline elements as visible", () => {
|
||||
autofillInlineMenuContentService["buttonElement"] = document.createElement("div");
|
||||
autofillInlineMenuContentService["listElement"] = document.createElement("div");
|
||||
|
||||
sendMockExtensionMessage({
|
||||
command: "toggleAutofillInlineMenuHidden",
|
||||
isInlineMenuHidden: false,
|
||||
});
|
||||
|
||||
expect(autofillInlineMenuContentService["isButtonVisible"]).toBe(true);
|
||||
expect(autofillInlineMenuContentService["isListVisible"]).toBe(true);
|
||||
});
|
||||
|
||||
it("sets the inline elements as hidden", () => {
|
||||
autofillInlineMenuContentService["buttonElement"] = document.createElement("div");
|
||||
autofillInlineMenuContentService["listElement"] = document.createElement("div");
|
||||
autofillInlineMenuContentService["isButtonVisible"] = true;
|
||||
autofillInlineMenuContentService["isListVisible"] = true;
|
||||
|
||||
sendMockExtensionMessage({
|
||||
command: "toggleAutofillInlineMenuHidden",
|
||||
isInlineMenuHidden: true,
|
||||
});
|
||||
|
||||
expect(autofillInlineMenuContentService["isButtonVisible"]).toBe(false);
|
||||
expect(autofillInlineMenuContentService["isListVisible"]).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe("checkIsAutofillInlineMenuButtonVisible message handler", () => {
|
||||
it("returns true if the inline menu button is visible", async () => {
|
||||
autofillInlineMenuContentService["isButtonVisible"] = true;
|
||||
|
||||
sendMockExtensionMessage(
|
||||
{ command: "checkIsAutofillInlineMenuButtonVisible" },
|
||||
mock<chrome.runtime.MessageSender>(),
|
||||
sendResponseSpy,
|
||||
);
|
||||
await flushPromises();
|
||||
|
||||
expect(sendResponseSpy).toHaveBeenCalledWith(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe("checkIsAutofillInlineMenuListVisible message handler", () => {
|
||||
it("returns true if the inline menu list is visible", async () => {
|
||||
autofillInlineMenuContentService["isListVisible"] = true;
|
||||
|
||||
sendMockExtensionMessage(
|
||||
{ command: "checkIsAutofillInlineMenuListVisible" },
|
||||
mock<chrome.runtime.MessageSender>(),
|
||||
sendResponseSpy,
|
||||
);
|
||||
await flushPromises();
|
||||
|
||||
expect(sendResponseSpy).toHaveBeenCalledWith(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("handleInlineMenuElementMutationObserverUpdate", () => {
|
||||
@ -295,6 +222,7 @@ describe("AutofillInlineMenuContentService", () => {
|
||||
describe("handleBodyElementMutationObserverUpdate", () => {
|
||||
let buttonElement: HTMLElement;
|
||||
let listElement: HTMLElement;
|
||||
let isInlineMenuListVisibleSpy: jest.SpyInstance;
|
||||
|
||||
beforeEach(() => {
|
||||
document.body.innerHTML = `
|
||||
@ -305,7 +233,9 @@ describe("AutofillInlineMenuContentService", () => {
|
||||
listElement = document.querySelector(".overlay-list") as HTMLElement;
|
||||
autofillInlineMenuContentService["buttonElement"] = buttonElement;
|
||||
autofillInlineMenuContentService["listElement"] = listElement;
|
||||
autofillInlineMenuContentService["isListVisible"] = true;
|
||||
isInlineMenuListVisibleSpy = jest
|
||||
.spyOn(autofillInlineMenuContentService as any, "isInlineMenuListVisible")
|
||||
.mockResolvedValue(true);
|
||||
jest.spyOn(globalThis.document.body, "insertBefore");
|
||||
jest
|
||||
.spyOn(
|
||||
@ -315,16 +245,16 @@ describe("AutofillInlineMenuContentService", () => {
|
||||
.mockReturnValue(false);
|
||||
});
|
||||
|
||||
it("skips handling the mutation if the overlay elements are not present in the DOM", () => {
|
||||
it("skips handling the mutation if the overlay elements are not present in the DOM", async () => {
|
||||
autofillInlineMenuContentService["buttonElement"] = undefined;
|
||||
autofillInlineMenuContentService["listElement"] = undefined;
|
||||
|
||||
autofillInlineMenuContentService["handleBodyElementMutationObserverUpdate"]();
|
||||
await autofillInlineMenuContentService["handleBodyElementMutationObserverUpdate"]();
|
||||
|
||||
expect(globalThis.document.body.insertBefore).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("skips handling the mutation if excessive mutations are being triggered", () => {
|
||||
it("skips handling the mutation if excessive mutations are being triggered", async () => {
|
||||
jest
|
||||
.spyOn(
|
||||
autofillInlineMenuContentService as any,
|
||||
@ -332,31 +262,31 @@ describe("AutofillInlineMenuContentService", () => {
|
||||
)
|
||||
.mockReturnValue(true);
|
||||
|
||||
autofillInlineMenuContentService["handleBodyElementMutationObserverUpdate"]();
|
||||
await autofillInlineMenuContentService["handleBodyElementMutationObserverUpdate"]();
|
||||
|
||||
expect(globalThis.document.body.insertBefore).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("skips re-arranging the DOM elements if the last child of the body is the overlay list and the second to last child of the body is the overlay button", () => {
|
||||
autofillInlineMenuContentService["handleBodyElementMutationObserverUpdate"]();
|
||||
it("skips re-arranging the DOM elements if the last child of the body is the overlay list and the second to last child of the body is the overlay button", async () => {
|
||||
await autofillInlineMenuContentService["handleBodyElementMutationObserverUpdate"]();
|
||||
|
||||
expect(globalThis.document.body.insertBefore).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("skips re-arranging the DOM elements if the last child is the overlay button and the overlay list is not visible", () => {
|
||||
it("skips re-arranging the DOM elements if the last child is the overlay button and the overlay list is not visible", async () => {
|
||||
listElement.remove();
|
||||
autofillInlineMenuContentService["isListVisible"] = false;
|
||||
isInlineMenuListVisibleSpy.mockResolvedValue(false);
|
||||
|
||||
autofillInlineMenuContentService["handleBodyElementMutationObserverUpdate"]();
|
||||
await autofillInlineMenuContentService["handleBodyElementMutationObserverUpdate"]();
|
||||
|
||||
expect(globalThis.document.body.insertBefore).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("positions the overlay button before the overlay list if an element has inserted itself after the button element", () => {
|
||||
it("positions the overlay button before the overlay list if an element has inserted itself after the button element", async () => {
|
||||
const injectedElement = document.createElement("div");
|
||||
document.body.insertBefore(injectedElement, listElement);
|
||||
|
||||
autofillInlineMenuContentService["handleBodyElementMutationObserverUpdate"]();
|
||||
await autofillInlineMenuContentService["handleBodyElementMutationObserverUpdate"]();
|
||||
|
||||
expect(globalThis.document.body.insertBefore).toHaveBeenCalledWith(
|
||||
buttonElement,
|
||||
@ -364,10 +294,10 @@ describe("AutofillInlineMenuContentService", () => {
|
||||
);
|
||||
});
|
||||
|
||||
it("positions the overlay button before the overlay list if the elements have inserted in incorrect order", () => {
|
||||
it("positions the overlay button before the overlay list if the elements have inserted in incorrect order", async () => {
|
||||
document.body.appendChild(buttonElement);
|
||||
|
||||
autofillInlineMenuContentService["handleBodyElementMutationObserverUpdate"]();
|
||||
await autofillInlineMenuContentService["handleBodyElementMutationObserverUpdate"]();
|
||||
|
||||
expect(globalThis.document.body.insertBefore).toHaveBeenCalledWith(
|
||||
buttonElement,
|
||||
@ -375,11 +305,11 @@ describe("AutofillInlineMenuContentService", () => {
|
||||
);
|
||||
});
|
||||
|
||||
it("positions the last child before the overlay button if it is not the overlay list", () => {
|
||||
it("positions the last child before the overlay button if it is not the overlay list", async () => {
|
||||
const injectedElement = document.createElement("div");
|
||||
document.body.appendChild(injectedElement);
|
||||
|
||||
autofillInlineMenuContentService["handleBodyElementMutationObserverUpdate"]();
|
||||
await autofillInlineMenuContentService["handleBodyElementMutationObserverUpdate"]();
|
||||
|
||||
expect(globalThis.document.body.insertBefore).toHaveBeenCalledWith(
|
||||
injectedElement,
|
||||
|
@ -1,5 +1,8 @@
|
||||
import { AutofillExtensionMessage } from "../../../content/abstractions/autofill-init";
|
||||
import { AutofillOverlayElement } from "../../../enums/autofill-overlay.enum";
|
||||
import {
|
||||
AutofillOverlayElement,
|
||||
AutofillOverlayElementType,
|
||||
} from "../../../enums/autofill-overlay.enum";
|
||||
import {
|
||||
sendExtensionMessage,
|
||||
generateRandomCustomElementName,
|
||||
@ -21,8 +24,6 @@ export class AutofillInlineMenuContentService implements AutofillInlineMenuConte
|
||||
globalThis.navigator.userAgent.indexOf(" Gecko/") !== -1;
|
||||
private buttonElement: HTMLElement;
|
||||
private listElement: HTMLElement;
|
||||
private isButtonVisible = false;
|
||||
private isListVisible = false;
|
||||
private inlineMenuElementsMutationObserver: MutationObserver;
|
||||
private bodyElementMutationObserver: MutationObserver;
|
||||
private mutationObserverIterations = 0;
|
||||
@ -36,9 +37,6 @@ export class AutofillInlineMenuContentService implements AutofillInlineMenuConte
|
||||
private readonly extensionMessageHandlers: InlineMenuExtensionMessageHandlers = {
|
||||
closeAutofillInlineMenu: ({ message }) => this.closeInlineMenu(message),
|
||||
appendAutofillInlineMenuToDom: ({ message }) => this.appendInlineMenuElements(message),
|
||||
toggleAutofillInlineMenuHidden: ({ message }) => this.toggleInlineMenuHidden(message),
|
||||
checkIsAutofillInlineMenuButtonVisible: () => this.isInlineMenuButtonVisible(),
|
||||
checkIsAutofillInlineMenuListVisible: () => this.isInlineMenuListVisible(),
|
||||
};
|
||||
|
||||
constructor() {
|
||||
@ -62,28 +60,23 @@ export class AutofillInlineMenuContentService implements AutofillInlineMenuConte
|
||||
}
|
||||
|
||||
/**
|
||||
* Identifies if the inline menu button is currently visible.
|
||||
* Checks if the inline menu button is visible at the top frame.
|
||||
*/
|
||||
private isInlineMenuButtonVisible() {
|
||||
return this.isButtonVisible;
|
||||
private async isInlineMenuButtonVisible() {
|
||||
return (
|
||||
!!this.buttonElement &&
|
||||
(await this.sendExtensionMessage("checkIsAutofillInlineMenuButtonVisible")) === true
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Identifies if the inline menu list is currently visible.
|
||||
* Checks if the inline menu list if visible at the top frame.
|
||||
*/
|
||||
private isInlineMenuListVisible() {
|
||||
return this.isListVisible;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a message that facilitates hiding the inline menu elements.
|
||||
*
|
||||
* @param message - The message that contains the visibility state of the inline menu elements.
|
||||
*/
|
||||
private toggleInlineMenuHidden(message: AutofillExtensionMessage) {
|
||||
const { isInlineMenuHidden } = message;
|
||||
this.isButtonVisible = !!this.buttonElement && !isInlineMenuHidden;
|
||||
this.isListVisible = !!this.listElement && !isInlineMenuHidden;
|
||||
private async isInlineMenuListVisible() {
|
||||
return (
|
||||
!!this.listElement &&
|
||||
(await this.sendExtensionMessage("checkIsAutofillInlineMenuListVisible")) === true
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -114,7 +107,6 @@ export class AutofillInlineMenuContentService implements AutofillInlineMenuConte
|
||||
private closeInlineMenuButton() {
|
||||
if (this.buttonElement) {
|
||||
this.buttonElement.remove();
|
||||
this.isButtonVisible = false;
|
||||
void this.sendExtensionMessage("autofillOverlayElementClosed", {
|
||||
overlayElement: AutofillOverlayElement.Button,
|
||||
});
|
||||
@ -127,7 +119,6 @@ export class AutofillInlineMenuContentService implements AutofillInlineMenuConte
|
||||
private closeInlineMenuList() {
|
||||
if (this.listElement) {
|
||||
this.listElement.remove();
|
||||
this.isListVisible = false;
|
||||
void this.sendExtensionMessage("autofillOverlayElementClosed", {
|
||||
overlayElement: AutofillOverlayElement.List,
|
||||
});
|
||||
@ -154,9 +145,9 @@ export class AutofillInlineMenuContentService implements AutofillInlineMenuConte
|
||||
this.updateCustomElementDefaultStyles(this.buttonElement);
|
||||
}
|
||||
|
||||
if (!this.isButtonVisible) {
|
||||
if (!(await this.isInlineMenuButtonVisible())) {
|
||||
this.appendInlineMenuElementToBody(this.buttonElement);
|
||||
this.isButtonVisible = true;
|
||||
this.updateInlineMenuElementIsVisibleStatus(AutofillOverlayElement.Button, true);
|
||||
}
|
||||
}
|
||||
|
||||
@ -169,12 +160,28 @@ export class AutofillInlineMenuContentService implements AutofillInlineMenuConte
|
||||
this.updateCustomElementDefaultStyles(this.listElement);
|
||||
}
|
||||
|
||||
if (!this.isListVisible) {
|
||||
if (!(await this.isInlineMenuListVisible())) {
|
||||
this.appendInlineMenuElementToBody(this.listElement);
|
||||
this.isListVisible = true;
|
||||
this.updateInlineMenuElementIsVisibleStatus(AutofillOverlayElement.List, true);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the visibility status of the inline menu element within the background script.
|
||||
*
|
||||
* @param overlayElement - The inline menu element to update the visibility status for.
|
||||
* @param isInlineMenuElementVisible - The visibility status to update the inline menu element to.
|
||||
*/
|
||||
private updateInlineMenuElementIsVisibleStatus(
|
||||
overlayElement: AutofillOverlayElementType,
|
||||
isInlineMenuElementVisible: boolean,
|
||||
) {
|
||||
void this.sendExtensionMessage("updateAutofillInlineMenuElementIsVisibleStatus", {
|
||||
overlayElement,
|
||||
isInlineMenuElementVisible,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Appends the inline menu element to the body element. This method will also
|
||||
* observe the body element to ensure that the inline menu element is not
|
||||
@ -360,7 +367,7 @@ export class AutofillInlineMenuContentService implements AutofillInlineMenuConte
|
||||
* ensure that the inline menu elements are always present at the bottom of the
|
||||
* body element.
|
||||
*/
|
||||
private handleBodyElementMutationObserverUpdate = () => {
|
||||
private handleBodyElementMutationObserverUpdate = async () => {
|
||||
if (
|
||||
(!this.buttonElement && !this.listElement) ||
|
||||
this.isTriggeringExcessiveMutationObserverIterations()
|
||||
@ -377,14 +384,14 @@ export class AutofillInlineMenuContentService implements AutofillInlineMenuConte
|
||||
if (
|
||||
!lastChild ||
|
||||
(lastChildIsInlineMenuList && secondToLastChildIsInlineMenuButton) ||
|
||||
(lastChildIsInlineMenuButton && !this.isListVisible)
|
||||
(lastChildIsInlineMenuButton && !(await this.isInlineMenuListVisible()))
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (
|
||||
(lastChildIsInlineMenuList && !secondToLastChildIsInlineMenuButton) ||
|
||||
(lastChildIsInlineMenuButton && this.isListVisible)
|
||||
(lastChildIsInlineMenuButton && (await this.isInlineMenuListVisible()))
|
||||
) {
|
||||
globalThis.document.body.insertBefore(this.buttonElement, this.listElement);
|
||||
return;
|
||||
|
Loading…
Reference in New Issue
Block a user