[PM-5189] Working through content script port improvement

This commit is contained in:
Cesar Gonzalez 2024-06-19 01:55:03 -05:00
parent 6802cc8957
commit da357f46b3
No known key found for this signature in database
GPG Key ID: 3381A5457F8CCECF
11 changed files with 88 additions and 29 deletions

View File

@ -73,13 +73,6 @@ export type OverlayBackgroundExtensionMessage = {
CloseInlineMenuMessage &
ToggleInlineMenuHiddenMessage;
export type OverlayPortMessage = {
[key: string]: any;
command: string;
direction?: string;
inlineMenuCipherId?: string;
};
export type InlineMenuCipherData = {
id: string;
name: string;
@ -101,7 +94,7 @@ export type BackgroundOnMessageHandlerParams = BackgroundMessageParam & Backgrou
export type OverlayBackgroundExtensionMessageHandlers = {
[key: string]: CallableFunction;
autofillOverlayElementClosed: ({ message, sender }: BackgroundOnMessageHandlerParams) => void;
autofillOverlayAddNewVaultItem: ({ message, sender }: BackgroundOnMessageHandlerParams) => void;
triggerAutofillOverlayReposition: ({ message, sender }: BackgroundOnMessageHandlerParams) => void;
checkIsInlineMenuCiphersPopulated: ({ sender }: BackgroundSenderParam) => void;
@ -137,6 +130,11 @@ export type OverlayBackgroundExtensionMessageHandlers = {
deletedCipher: () => void;
};
export type OverlayPortMessage = OverlayBackgroundExtensionMessage & {
direction?: string;
inlineMenuCipherId?: string;
};
export type PortMessageParam = {
message: OverlayPortMessage;
};
@ -145,6 +143,11 @@ export type PortConnectionParam = {
};
export type PortOnMessageHandlerParams = PortMessageParam & PortConnectionParam;
export type OverlayContentScriptPortMessageHandlers = {
[key: string]: CallableFunction;
autofillOverlayElementClosed: ({ message, port }: PortOnMessageHandlerParams) => void;
};
export type InlineMenuButtonPortMessageHandlers = {
[key: string]: CallableFunction;
triggerDelayedAutofillInlineMenuClosure: ({ port }: PortConnectionParam) => void;

View File

@ -50,6 +50,7 @@ import {
SubFrameOffsetsForTab,
CloseInlineMenuMessage,
ToggleInlineMenuHiddenMessage,
OverlayContentScriptPortMessageHandlers,
} from "./abstractions/overlay.background";
export class OverlayBackground implements OverlayBackgroundInterface {
@ -75,8 +76,6 @@ export class OverlayBackground implements OverlayBackgroundInterface {
private isFieldCurrentlyFilling: boolean = false;
private iconsServerUrl: string;
private readonly extensionMessageHandlers: OverlayBackgroundExtensionMessageHandlers = {
autofillOverlayElementClosed: ({ message, sender }) =>
this.overlayElementClosed(message, sender),
autofillOverlayAddNewVaultItem: ({ message, sender }) => this.addNewVaultItem(message, sender),
triggerAutofillOverlayReposition: ({ sender }) => this.triggerOverlayReposition(sender),
checkIsInlineMenuCiphersPopulated: ({ sender }) =>
@ -110,6 +109,9 @@ export class OverlayBackground implements OverlayBackgroundInterface {
editedCipher: () => this.updateInlineMenuCiphers(),
deletedCipher: () => this.updateInlineMenuCiphers(),
};
private readonly contentScriptPortMessageHandlers: OverlayContentScriptPortMessageHandlers = {
autofillOverlayElementClosed: ({ message, port }) => this.overlayElementClosed(message, port),
};
private readonly inlineMenuButtonPortMessageHandlers: InlineMenuButtonPortMessageHandlers = {
triggerDelayedAutofillInlineMenuClosure: ({ port }) => this.triggerDelayedInlineMenuClosure(),
autofillInlineMenuButtonClicked: ({ port }) => this.handleInlineMenuButtonClicked(port),
@ -612,13 +614,13 @@ export class OverlayBackground implements OverlayBackgroundInterface {
* the list and button ports and sets them to null.
*
* @param overlayElement - The overlay element that was closed, either the list or button
* @param sender - The sender of the port message
* @param port - The port that sent the message
*/
private overlayElementClosed(
{ overlayElement }: OverlayBackgroundExtensionMessage,
sender: chrome.runtime.MessageSender,
port: chrome.runtime.Port,
) {
if (sender.tab.id !== this.focusedFieldData?.tabId) {
if (port.sender.tab.id !== this.focusedFieldData?.tabId) {
this.expiredPorts.forEach((port) => port.disconnect());
this.expiredPorts = [];
return;
@ -1217,6 +1219,11 @@ export class OverlayBackground implements OverlayBackgroundInterface {
* @param port - The port that connected to the extension background
*/
private handlePortOnConnect = async (port: chrome.runtime.Port) => {
if (port.name === AutofillOverlayPort.ContentScript) {
port.onMessage.addListener(this.handleContentScriptPortMessage);
return;
}
const isInlineMenuListMessageConnector = port.name === AutofillOverlayPort.ListMessageConnector;
const isInlineMenuButtonMessageConnector =
port.name === AutofillOverlayPort.ButtonMessageConnector;
@ -1293,6 +1300,21 @@ export class OverlayBackground implements OverlayBackgroundInterface {
}
}
private handleContentScriptPortMessage = (
message: OverlayPortMessage,
port: chrome.runtime.Port,
) => {
if (port.name !== AutofillOverlayPort.ContentScript) {
return;
}
const handler: CallableFunction | undefined =
this.contentScriptPortMessageHandlers[message.command];
if (handler) {
handler({ message, port });
}
};
/**
* Handles messages sent to the overlay list or button ports.
*
@ -1300,7 +1322,7 @@ export class OverlayBackground implements OverlayBackgroundInterface {
* @param port - The port that sent the message
*/
private handleOverlayElementPortMessage = (
message: OverlayBackgroundExtensionMessage,
message: OverlayPortMessage,
port: chrome.runtime.Port,
) => {
const tabPortKey = this.portKeyForTab[port.sender.tab.id];

View File

@ -33,14 +33,14 @@ class AutofillInit implements AutofillInitInterface {
* CollectAutofillContentService and InsertAutofillContentService classes.
*
* @param autofillOverlayContentService - The autofill overlay content service, potentially undefined.
* @param inlineMenuElements - The inline menu elements, potentially undefined.
* @param autofillInlineMenuContentService - The inline menu elements, potentially undefined.
*/
constructor(
autofillOverlayContentService?: AutofillOverlayContentService,
inlineMenuElements?: AutofillInlineMenuContentService,
autofillInlineMenuContentService?: AutofillInlineMenuContentService,
) {
this.autofillOverlayContentService = autofillOverlayContentService;
this.autofillInlineMenuContentService = inlineMenuElements;
this.autofillInlineMenuContentService = autofillInlineMenuContentService;
this.domElementVisibilityService = new DomElementVisibilityService(
this.autofillInlineMenuContentService,
);

View File

@ -1,3 +1,4 @@
import { AutofillOverlayPort } from "../enums/autofill-overlay.enum";
import { AutofillInlineMenuContentService } from "../overlay/inline-menu/content/autofill-inline-menu-content.service";
import { AutofillOverlayContentService } from "../services/autofill-overlay-content.service";
import { InlineMenuFieldQualificationService } from "../services/inline-menu-field-qualification.service";
@ -7,17 +8,19 @@ import AutofillInit from "./autofill-init";
(function (windowContext) {
if (!windowContext.bitwardenAutofillInit) {
const overlayPort = chrome.runtime.connect({ name: AutofillOverlayPort.ContentScript });
const inlineMenuFieldQualificationService = new InlineMenuFieldQualificationService();
const autofillOverlayContentService = new AutofillOverlayContentService(
overlayPort,
inlineMenuFieldQualificationService,
);
let inlineMenuElements: AutofillInlineMenuContentService;
let autofillInlineMenuContentService: AutofillInlineMenuContentService;
if (globalThis.self === globalThis.top) {
inlineMenuElements = new AutofillInlineMenuContentService();
autofillInlineMenuContentService = new AutofillInlineMenuContentService(overlayPort);
}
windowContext.bitwardenAutofillInit = new AutofillInit(
autofillOverlayContentService,
inlineMenuElements,
autofillInlineMenuContentService,
);
setupAutofillInitDisconnectAction(windowContext);

View File

@ -4,6 +4,7 @@ export const AutofillOverlayElement = {
} as const;
export const AutofillOverlayPort = {
ContentScript: "autofill-overlay-content-script-port",
Button: "autofill-inline-menu-button-port",
ButtonMessageConnector: "autofill-inline-menu-button-message-connector",
List: "autofill-inline-menu-list-port",

View File

@ -1,14 +1,15 @@
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";
import { AutofillOverlayElement, AutofillOverlayPort } from "../../../enums/autofill-overlay.enum";
import { createMutationRecordMock, createPortSpyMock } from "../../../spec/autofill-mocks";
import { flushPromises, sendMockExtensionMessage } from "../../../spec/testing-utils";
import { ElementWithOpId } from "../../../types";
import { AutofillInlineMenuContentService } from "./autofill-inline-menu-content.service";
describe("AutofillInlineMenuContentService", () => {
let overlayPort: chrome.runtime.Port;
let autofillInlineMenuContentService: AutofillInlineMenuContentService;
let autofillInit: AutofillInit;
let sendExtensionMessageSpy: jest.SpyInstance;
@ -17,7 +18,8 @@ describe("AutofillInlineMenuContentService", () => {
beforeEach(() => {
globalThis.document.body.innerHTML = "";
autofillInlineMenuContentService = new AutofillInlineMenuContentService();
overlayPort = createPortSpyMock(AutofillOverlayPort.ContentScript);
autofillInlineMenuContentService = new AutofillInlineMenuContentService(overlayPort);
autofillInit = new AutofillInit(null, autofillInlineMenuContentService);
autofillInit.init();
observeBodyMutationsSpy = jest.spyOn(

View File

@ -41,7 +41,7 @@ export class AutofillInlineMenuContentService implements AutofillInlineMenuConte
checkIsAutofillInlineMenuListVisible: () => this.isInlineMenuListVisible(),
};
constructor() {
constructor(private port: chrome.runtime.Port) {
this.setupMutationObserver();
}
@ -115,7 +115,7 @@ export class AutofillInlineMenuContentService implements AutofillInlineMenuConte
if (this.buttonElement) {
this.buttonElement.remove();
this.isButtonVisible = false;
void this.sendExtensionMessage("autofillOverlayElementClosed", {
this.sendPortMessage("autofillOverlayElementClosed", {
overlayElement: AutofillOverlayElement.Button,
});
}
@ -128,7 +128,7 @@ export class AutofillInlineMenuContentService implements AutofillInlineMenuConte
if (this.listElement) {
this.listElement.remove();
this.isListVisible = false;
void this.sendExtensionMessage("autofillOverlayElementClosed", {
this.sendPortMessage("autofillOverlayElementClosed", {
overlayElement: AutofillOverlayElement.List,
});
}
@ -421,6 +421,16 @@ export class AutofillInlineMenuContentService implements AutofillInlineMenuConte
return false;
}
/**
* Sends a message through the port to the background script.
*
* @param command - The command to send through the port.
* @param message - The message to send through the port.
*/
private sendPortMessage(command: string, message: Omit<AutofillExtensionMessage, "command">) {
this.port.postMessage({ command, ...message });
}
/**
* Disconnects the mutation observers and removes the inline menu elements from the DOM.
*/

View File

@ -6,13 +6,14 @@ import { AutofillOverlayVisibility, EVENTS } from "@bitwarden/common/autofill/co
import AutofillInit from "../content/autofill-init";
import {
AutofillOverlayElement,
AutofillOverlayPort,
MAX_SUB_FRAME_DEPTH,
RedirectFocusDirection,
} from "../enums/autofill-overlay.enum";
import AutofillField from "../models/autofill-field";
import AutofillForm from "../models/autofill-form";
import AutofillPageDetails from "../models/autofill-page-details";
import { createAutofillFieldMock } from "../spec/autofill-mocks";
import { createAutofillFieldMock, createPortSpyMock } from "../spec/autofill-mocks";
import { flushPromises, postWindowMessage, sendMockExtensionMessage } from "../spec/testing-utils";
import { ElementWithOpId, FillableFormFieldElement, FormFieldElement } from "../types";
@ -25,6 +26,7 @@ const defaultDocumentVisibilityState = document.visibilityState;
describe("AutofillOverlayContentService", () => {
let autofillInit: AutofillInit;
let inlineMenuFieldQualificationService: InlineMenuFieldQualificationService;
let overlayPort: chrome.runtime.Port;
let autofillOverlayContentService: AutofillOverlayContentService;
let sendExtensionMessageSpy: jest.SpyInstance;
const sendResponseSpy = jest.fn();
@ -39,8 +41,10 @@ describe("AutofillOverlayContentService", () => {
});
beforeEach(() => {
overlayPort = createPortSpyMock(AutofillOverlayPort.ContentScript);
inlineMenuFieldQualificationService = new InlineMenuFieldQualificationService();
autofillOverlayContentService = new AutofillOverlayContentService(
overlayPort,
inlineMenuFieldQualificationService,
);
autofillInit = new AutofillInit(autofillOverlayContentService);

View File

@ -79,7 +79,10 @@ export class AutofillOverlayContentService implements AutofillOverlayContentServ
destroyAutofillInlineMenuListeners: () => this.destroy(),
};
constructor(private inlineMenuFieldQualificationService: InlineMenuFieldQualificationService) {}
constructor(
private port: chrome.runtime.Port,
private inlineMenuFieldQualificationService: InlineMenuFieldQualificationService,
) {}
/**
* Initializes the autofill overlay content service by setting up the mutation observers.

View File

@ -1,8 +1,13 @@
import { mock } from "jest-mock-extended";
import { AutofillOverlayPort } from "../enums/autofill-overlay.enum";
import AutofillField from "../models/autofill-field";
import AutofillForm from "../models/autofill-form";
import { createAutofillFieldMock, createAutofillFormMock } from "../spec/autofill-mocks";
import {
createAutofillFieldMock,
createAutofillFormMock,
createPortSpyMock,
} from "../spec/autofill-mocks";
import { mockQuerySelectorAllDefinedCall } from "../spec/testing-utils";
import {
ElementWithOpId,
@ -28,9 +33,11 @@ const mockLoginForm = `
const waitForIdleCallback = () => new Promise((resolve) => globalThis.requestIdleCallback(resolve));
describe("CollectAutofillContentService", () => {
const overlayPort = createPortSpyMock(AutofillOverlayPort.ContentScript);
const domElementVisibilityService = new DomElementVisibilityService();
const inlineMenuFieldQualificationService = mock<InlineMenuFieldQualificationService>();
const autofillOverlayContentService = new AutofillOverlayContentService(
overlayPort,
inlineMenuFieldQualificationService,
);
let collectAutofillContentService: CollectAutofillContentService;

View File

@ -2,7 +2,9 @@ import { mock } from "jest-mock-extended";
import { EVENTS } from "@bitwarden/common/autofill/constants";
import { AutofillOverlayPort } from "../enums/autofill-overlay.enum";
import AutofillScript, { FillScript, FillScriptActions } from "../models/autofill-script";
import { createPortSpyMock } from "../spec/autofill-mocks";
import { mockQuerySelectorAllDefinedCall } from "../spec/testing-utils";
import { FillableFormFieldElement, FormElementWithAttribute, FormFieldElement } from "../types";
@ -67,9 +69,11 @@ function setMockWindowLocation({
}
describe("InsertAutofillContentService", () => {
const overlayPort = createPortSpyMock(AutofillOverlayPort.ContentScript);
const inlineMenuFieldQualificationService = mock<InlineMenuFieldQualificationService>();
const domElementVisibilityService = new DomElementVisibilityService();
const autofillOverlayContentService = new AutofillOverlayContentService(
overlayPort,
inlineMenuFieldQualificationService,
);
const collectAutofillContentService = new CollectAutofillContentService(