Skip to content
18 changes: 18 additions & 0 deletions packages/message/common.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
// 避免页面载入后改动全域物件导致消息传递失败
export const MouseEventClone = MouseEvent;
export const CustomEventClone = CustomEvent;
const performanceClone = performance;

// 避免页面载入后改动 EventTarget.prototype 的方法导致消息传递失败
export const pageDispatchEvent = performance.dispatchEvent.bind(performance);
export const pageAddEventListener = performance.addEventListener.bind(performance);
export const pageRemoveEventListener = performance.removeEventListener.bind(performance);
const detailClone = typeof cloneInto === "function" ? cloneInto : null;
export const pageDispatchCustomEvent = (eventType: string, detail: any) => {
if (detailClone && detail) detail = detailClone(detail, performanceClone);
const ev = new CustomEventClone(eventType, {
detail,
cancelable: true,
});
return pageDispatchEvent(ev);
};
124 changes: 74 additions & 50 deletions packages/message/custom_event_message.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,16 @@
import type { Message, MessageConnect, RuntimeMessageSender, TMessage } from "./types";
import { v4 as uuidv4 } from "uuid";
import { type PostMessage, type WindowMessageBody, WindowMessageConnect } from "./window_message";
import LoggerCore from "@App/app/logger/core";
import EventEmitter from "eventemitter3";
import { DefinedFlags } from "@App/app/service/service_worker/runtime.consts";

// 避免页面载入后改动 EventTarget.prototype 的方法导致消息传递失败
const pageDispatchEvent = performance.dispatchEvent.bind(performance);
const pageAddEventListener = performance.addEventListener.bind(performance);

// 避免页面载入后改动全域物件导致消息传递失败
const MouseEventClone = MouseEvent;
const CustomEventClone = CustomEvent;
import {
pageDispatchEvent,
pageAddEventListener,
pageRemoveEventListener,
pageDispatchCustomEvent,
MouseEventClone,
CustomEventClone,
} from "@Packages/message/common";

// 避免页面载入后改动 Map.prototype 导致消息传递失败
const relatedTargetMap = new Map<number, EventTarget>();
Expand All @@ -30,28 +29,59 @@ export class CustomEventPostMessage implements PostMessage {
}
}

export type PageMessaging = {
et: string;
bindEmitter?: () => void;
waitReady?: Promise<void>;
waitReadyResolve?: () => any;
onReady?: (callback: () => any) => any;
};

export const createPageMessaging = (et: string) => {
const pageMessaging = { et } as PageMessaging;
pageMessaging.waitReady = new Promise<void>((resolve) => {
pageMessaging.waitReadyResolve = resolve;
});
pageMessaging.onReady = (callback: () => any) => {
if (pageMessaging.et) {
callback();
} else {
pageMessaging.waitReady!.then(callback);
}
};
return pageMessaging;
};

// 使用CustomEvent来进行通讯, 可以在content与inject中传递一些dom对象
export class CustomEventMessage implements Message {
EE = new EventEmitter<string, any>();
readonly receiveFlag: string;
readonly sendFlag: string;
readonly pageMessagingHandler: (event: Event) => any;

// 关联dom目标
relatedTarget: Map<number, EventTarget> = new Map();

constructor(
messageFlag: string,
private pageMessaging: PageMessaging,
protected readonly isContent: boolean
) {
this.receiveFlag = `evt${messageFlag}${isContent ? DefinedFlags.contentFlag : DefinedFlags.injectFlag}${DefinedFlags.domEvent}`;
this.sendFlag = `evt${messageFlag}${isContent ? DefinedFlags.injectFlag : DefinedFlags.contentFlag}${DefinedFlags.domEvent}`;
pageAddEventListener(this.receiveFlag, (event) => {
this.receiveFlag = `${isContent ? DefinedFlags.contentFlag : DefinedFlags.injectFlag}${DefinedFlags.domEvent}`;
this.sendFlag = `${isContent ? DefinedFlags.injectFlag : DefinedFlags.contentFlag}${DefinedFlags.domEvent}`;
this.pageMessagingHandler = (event: Event) => {
if (event instanceof MouseEventClone && event.movementX && event.relatedTarget) {
relatedTargetMap.set(event.movementX, event.relatedTarget!);
relatedTargetMap.set(event.movementX, event.relatedTarget);
} else if (event instanceof CustomEventClone) {
this.messageHandle(event.detail, new CustomEventPostMessage(this));
}
});
};
}

bindEmitter() {
if (!this.pageMessaging.et) throw new Error("bindEmitter() failed");
const receiveFlag = `evt_${this.pageMessaging.et}_${this.receiveFlag}`;
pageRemoveEventListener(receiveFlag, this.pageMessagingHandler); // 避免重覆
pageAddEventListener(receiveFlag, this.pageMessagingHandler);
}

messageHandle(data: WindowMessageBody, target: PostMessage) {
Expand Down Expand Up @@ -95,56 +125,49 @@ export class CustomEventMessage implements Message {

connect(data: TMessage): Promise<MessageConnect> {
return new Promise((resolve) => {
const body: WindowMessageBody<TMessage> = {
messageId: uuidv4(),
type: "connect",
data,
};
this.nativeSend(body);
// EventEmitter3 采用同步事件设计,callback会被马上执行而不像传统javascript架构以下一个macrotask 执行
resolve(new WindowMessageConnect(body.messageId, this.EE, new CustomEventPostMessage(this)));
this.pageMessaging.onReady!(() => {
const body: WindowMessageBody<TMessage> = {
messageId: uuidv4(),
type: "connect",
data,
};
this.nativeSend(body);
// EventEmitter3 采用同步事件设计,callback会被马上执行而不像传统javascript架构以下一个macrotask 执行
resolve(new WindowMessageConnect(body.messageId, this.EE, new CustomEventPostMessage(this)));
});
});
}

nativeSend(detail: any) {
if (typeof cloneInto !== "undefined") {
try {
LoggerCore.logger().info("nativeSend");
detail = cloneInto(detail, document.defaultView);
} catch (e) {
console.log(e);
LoggerCore.logger().info("error data");
}
}

const ev = new CustomEventClone(this.sendFlag, {
detail,
});
pageDispatchEvent(ev);
if (!this.pageMessaging.et) throw new Error("inject.js is not ready or destroyed.");
pageDispatchCustomEvent(`evt_${this.pageMessaging.et}_${this.sendFlag}`, detail);
}

sendMessage<T = any>(data: TMessage): Promise<T> {
return new Promise((resolve: ((value: T) => void) | null) => {
const messageId = uuidv4();
const body: WindowMessageBody<TMessage> = {
messageId,
type: "sendMessage",
data,
};
const eventId = `response:${messageId}`;
this.EE.addListener(eventId, (body: WindowMessageBody<TMessage>) => {
this.EE.removeAllListeners(eventId);
resolve!(body.data as T);
resolve = null; // 设为 null 提醒JS引擎可以GC
this.pageMessaging.onReady!(() => {
const messageId = uuidv4();
const body: WindowMessageBody<TMessage> = {
messageId,
type: "sendMessage",
data,
};
const eventId = `response:${messageId}`;
this.EE.addListener(eventId, (body: WindowMessageBody<TMessage>) => {
this.EE.removeAllListeners(eventId);
resolve!(body.data as T);
resolve = null; // 设为 null 提醒JS引擎可以GC
});
this.nativeSend(body);
});
this.nativeSend(body);
});
}

// 同步发送消息
// 与content页的消息通讯实际是同步,此方法不需要经过background
// 但是请注意中间不要有promise
syncSendMessage(data: TMessage): TMessage {
if (!this.pageMessaging.et) throw new Error("inject.js is not ready or destroyed.");
const messageId = uuidv4();
const body: WindowMessageBody<TMessage> = {
messageId,
Expand All @@ -164,11 +187,12 @@ export class CustomEventMessage implements Message {
}

sendRelatedTarget(target: EventTarget): number {
if (!this.pageMessaging.et) throw new Error("inject.js is not ready or destroyed.");
// 特殊处理relatedTarget,返回id进行关联
// 先将relatedTarget转换成id发送过去
const id = (relateId = relateId === maxInteger ? 1 : relateId + 1);
// 可以使用此种方式交互element
const ev = new MouseEventClone(this.sendFlag, {
const ev = new MouseEventClone(`evt_${this.pageMessaging.et}_${this.sendFlag}`, {
movementX: id,
relatedTarget: target,
});
Expand Down
14 changes: 9 additions & 5 deletions packages/message/server.test.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import { describe, expect, it, beforeEach, vi, afterEach } from "vitest";
import { GetSenderType, SenderConnect, SenderRuntime, Server, type IGetSender } from "./server";
import { CustomEventMessage } from "./custom_event_message";
import { createPageMessaging, CustomEventMessage } from "./custom_event_message";
import type { MessageConnect, RuntimeMessageSender } from "./types";
import { DefinedFlags } from "@App/app/service/service_worker/runtime.consts";
import { uuidv4 } from "@App/pkg/utils/uuid";

let contentMessage: CustomEventMessage;
let injectMessage: CustomEventMessage;
Expand All @@ -12,10 +13,13 @@ let client: CustomEventMessage;
const nextTick = () => Promise.resolve().then(() => {});

const setupGlobal = () => {
const flags = "-test.server";
const testFlag = uuidv4();
const testPageMessaging = createPageMessaging(testFlag);
// 创建 content 和 inject 之间的消息通道
contentMessage = new CustomEventMessage(flags, true); // content 端
injectMessage = new CustomEventMessage(flags, false); // inject 端
contentMessage = new CustomEventMessage(testPageMessaging, true); // content 端
injectMessage = new CustomEventMessage(testPageMessaging, false); // inject 端
contentMessage.bindEmitter();
injectMessage.bindEmitter();

// 服务端使用 content 消息
server = new Server("api", contentMessage);
Expand All @@ -33,7 +37,7 @@ const setupGlobal = () => {
vi.fn().mockImplementation((event: Event) => {
if (event instanceof CustomEvent) {
const eventType = event.type;
if (eventType.includes("-test.server")) {
if (eventType.includes(testFlag)) {
let targetEventType: string;
let messageThis: CustomEventMessage;
let messageThat: CustomEventMessage;
Expand Down
1 change: 1 addition & 0 deletions rspack.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ export default defineConfig({
offscreen: `${src}/offscreen.ts`,
sandbox: `${src}/sandbox.ts`,
content: `${src}/content.ts`,
scripting: `${src}/scripting.ts`,
inject: `${src}/inject.ts`,
popup: `${src}/pages/popup/main.tsx`,
install: `${src}/pages/install/main.tsx`,
Expand Down
28 changes: 14 additions & 14 deletions src/app/service/content/content.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Client, sendMessage } from "@Packages/message/client";
import { Client } from "@Packages/message/client";
import { type CustomEventMessage } from "@Packages/message/custom_event_message";
import { forwardMessage, type Server } from "@Packages/message/server";
import type { MessageSend } from "@Packages/message/types";
Expand All @@ -16,7 +16,7 @@ export default class ContentRuntime {

constructor(
// 监听来自service_worker的消息
private readonly extServer: Server,
private readonly extServer: null,
// 监听来自inject的消息
private readonly server: Server,
// 发送给扩展service_worker的通信接口
Expand All @@ -29,16 +29,16 @@ export default class ContentRuntime {
) {}

init() {
this.extServer.on("runtime/emitEvent", (data) => {
// 转发给inject和scriptExecutor
this.scriptExecutor.emitEvent(data);
return sendMessage(this.senderToInject, "inject/runtime/emitEvent", data);
});
this.extServer.on("runtime/valueUpdate", (data) => {
// 转发给inject和scriptExecutor
this.scriptExecutor.valueUpdate(data);
return sendMessage(this.senderToInject, "inject/runtime/valueUpdate", data);
});
// this.extServer.on("runtime/emitEvent", (data) => {
// // 转发给inject和scriptExecutor
// this.scriptExecutor.emitEvent(data);
// return sendMessage(this.senderToInject, "inject/runtime/emitEvent", data);
// });
// this.extServer.on("runtime/valueUpdate", (data) => {
// // 转发给inject和scriptExecutor
// this.scriptExecutor.valueUpdate(data);
// return sendMessage(this.senderToInject, "inject/runtime/valueUpdate", data);
// });
this.server.on("logger", (data: Logger) => {
LoggerCore.logger().log(data.level, data.message, data.label);
});
Expand Down Expand Up @@ -127,8 +127,8 @@ export default class ContentRuntime {
);
}

pageLoad(messageFlag: string, envInfo: GMInfoEnv) {
this.scriptExecutor.checkEarlyStartScript("content", messageFlag, envInfo);
pageLoad(envInfo: GMInfoEnv) {
this.scriptExecutor.checkEarlyStartScript("content", MessageFlag, envInfo);
const client = new RuntimeClient(this.senderToExt);
// 向service_worker请求脚本列表及环境信息
client.pageLoad().then((o) => {
Expand Down
3 changes: 2 additions & 1 deletion src/app/service/content/script_executor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import type { GMInfoEnv, ScriptFunc, ValueUpdateDataEncoded } from "./types";
import { addStyleSheet, definePropertyListener } from "./utils";
import type { ScriptLoadInfo, TScriptInfo } from "@App/app/repo/scripts";
import { DefinedFlags } from "../service_worker/runtime.consts";
import { pageDispatchEvent } from "@Packages/message/common";
import { isUrlExcluded } from "@App/pkg/utils/match";

export type ExecScriptEntry = {
Expand Down Expand Up @@ -120,7 +121,7 @@ export class ScriptExecutor {
// 通知 环境 加载完成
// 适用于此「通知环境加载完成」代码执行前的脚本加载
const ev = new CustomEvent(envLoadCompleteEvtName);
performance.dispatchEvent(ev);
pageDispatchEvent(ev);
}

execEarlyScript(flag: string, scriptInfo: TScriptInfo, envInfo: GMInfoEnv) {
Expand Down
3 changes: 2 additions & 1 deletion src/app/service/content/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -151,7 +151,8 @@ export function compilePreInjectScript(
return `window['${flag}'] = function(){${autoDeleteMountCode}${scriptCode}};
{
let o = { cancelable: true, detail: { scriptFlag: '${flag}', scriptInfo: (${scriptInfoJSON}) } },
f = () => performance.dispatchEvent(new CustomEvent('${evScriptLoad}', o)),
c = typeof cloneInto === "function" ? cloneInto(o, performance) : o,
f = () => performance.dispatchEvent(new CustomEvent('${evScriptLoad}', c)),
needWait = f();
if (needWait) performance.addEventListener('${evEnvLoad}', f, { once: true });
}
Expand Down
Loading
Loading