Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 @@ -34,6 +34,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 { TScriptInfo } from "@App/app/repo/scripts";
import { DefinedFlags } from "../service_worker/runtime.consts";
import { pageDispatchEvent } from "@Packages/message/common";

export type ExecScriptEntry = {
scriptLoadInfo: TScriptInfo;
Expand Down Expand Up @@ -101,7 +102,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