Skip to content

Commit a08c0b8

Browse files
committed
以类似broadcast机制重构通讯机制
1 parent 80d8fec commit a08c0b8

File tree

10 files changed

+387
-162
lines changed

10 files changed

+387
-162
lines changed

packages/message/custom_event_message.ts

Lines changed: 78 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,22 @@
11
import type { Message, MessageConnect, RuntimeMessageSender, TMessage } from "./types";
22
import { v4 as uuidv4 } from "uuid";
33
import { type PostMessage, type WindowMessageBody, WindowMessageConnect } from "./window_message";
4-
import LoggerCore from "@App/app/logger/core";
54
import EventEmitter from "eventemitter3";
65
import { DefinedFlags } from "@App/app/service/service_worker/runtime.consts";
76

87
// 避免页面载入后改动 EventTarget.prototype 的方法导致消息传递失败
9-
const pageDispatchEvent = performance.dispatchEvent.bind(performance);
10-
const pageAddEventListener = performance.addEventListener.bind(performance);
8+
export const pageDispatchEvent = performance.dispatchEvent.bind(performance);
9+
export const pageAddEventListener = performance.addEventListener.bind(performance);
10+
export const pageRemoveEventListener = performance.removeEventListener.bind(performance);
11+
const detailClone = typeof cloneInto === "function" ? cloneInto : null;
12+
export const pageDispatchCustomEvent = (eventType: string, detail: any) => {
13+
if (detailClone && detail) detail = detailClone(detail, document.defaultView);
14+
const ev = new CustomEventClone(eventType, {
15+
detail,
16+
cancelable: true,
17+
});
18+
return pageDispatchEvent(ev);
19+
};
1120

1221
// 避免页面载入后改动全域物件导致消息传递失败
1322
const MouseEventClone = MouseEvent;
@@ -30,28 +39,59 @@ export class CustomEventPostMessage implements PostMessage {
3039
}
3140
}
3241

42+
export type PageMessaging = {
43+
et: string;
44+
bindEmitter?: () => void;
45+
waitReady?: Promise<void>;
46+
waitReadyResolve?: () => any;
47+
onReady?: (callback: () => any) => any;
48+
};
49+
50+
export const createPageMessaging = (et: string) => {
51+
const pageMessaging = { et } as PageMessaging;
52+
pageMessaging.waitReady = new Promise<void>((resolve) => {
53+
pageMessaging.waitReadyResolve = resolve;
54+
});
55+
pageMessaging.onReady = (callback: () => any) => {
56+
if (pageMessaging.et) {
57+
callback();
58+
} else {
59+
pageMessaging.waitReady!.then(callback);
60+
}
61+
};
62+
return pageMessaging;
63+
};
64+
3365
// 使用CustomEvent来进行通讯, 可以在content与inject中传递一些dom对象
3466
export class CustomEventMessage implements Message {
3567
EE = new EventEmitter<string, any>();
3668
readonly receiveFlag: string;
3769
readonly sendFlag: string;
70+
readonly pageMessagingHandler: (event: Event) => any;
3871

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

4275
constructor(
43-
messageFlag: string,
76+
private pageMessaging: PageMessaging,
4477
protected readonly isContent: boolean
4578
) {
46-
this.receiveFlag = `evt${messageFlag}${isContent ? DefinedFlags.contentFlag : DefinedFlags.injectFlag}${DefinedFlags.domEvent}`;
47-
this.sendFlag = `evt${messageFlag}${isContent ? DefinedFlags.injectFlag : DefinedFlags.contentFlag}${DefinedFlags.domEvent}`;
48-
pageAddEventListener(this.receiveFlag, (event) => {
79+
this.receiveFlag = `${isContent ? DefinedFlags.contentFlag : DefinedFlags.injectFlag}${DefinedFlags.domEvent}`;
80+
this.sendFlag = `${isContent ? DefinedFlags.injectFlag : DefinedFlags.contentFlag}${DefinedFlags.domEvent}`;
81+
this.pageMessagingHandler = (event: Event) => {
4982
if (event instanceof MouseEventClone && event.movementX && event.relatedTarget) {
50-
relatedTargetMap.set(event.movementX, event.relatedTarget!);
83+
relatedTargetMap.set(event.movementX, event.relatedTarget);
5184
} else if (event instanceof CustomEventClone) {
5285
this.messageHandle(event.detail, new CustomEventPostMessage(this));
5386
}
54-
});
87+
};
88+
}
89+
90+
bindEmitter() {
91+
if (!this.pageMessaging.et) throw new Error("bindEmitter() failed");
92+
const receiveFlag = `evt_${this.pageMessaging.et}_${this.receiveFlag}`;
93+
pageRemoveEventListener(receiveFlag, this.pageMessagingHandler); // 避免重覆
94+
pageAddEventListener(receiveFlag, this.pageMessagingHandler);
5595
}
5696

5797
messageHandle(data: WindowMessageBody, target: PostMessage) {
@@ -95,56 +135,49 @@ export class CustomEventMessage implements Message {
95135

96136
connect(data: TMessage): Promise<MessageConnect> {
97137
return new Promise((resolve) => {
98-
const body: WindowMessageBody<TMessage> = {
99-
messageId: uuidv4(),
100-
type: "connect",
101-
data,
102-
};
103-
this.nativeSend(body);
104-
// EventEmitter3 采用同步事件设计,callback会被马上执行而不像传统javascript架构以下一个macrotask 执行
105-
resolve(new WindowMessageConnect(body.messageId, this.EE, new CustomEventPostMessage(this)));
138+
this.pageMessaging.onReady!(() => {
139+
const body: WindowMessageBody<TMessage> = {
140+
messageId: uuidv4(),
141+
type: "connect",
142+
data,
143+
};
144+
this.nativeSend(body);
145+
// EventEmitter3 采用同步事件设计,callback会被马上执行而不像传统javascript架构以下一个macrotask 执行
146+
resolve(new WindowMessageConnect(body.messageId, this.EE, new CustomEventPostMessage(this)));
147+
});
106148
});
107149
}
108150

109151
nativeSend(detail: any) {
110-
if (typeof cloneInto !== "undefined") {
111-
try {
112-
LoggerCore.logger().info("nativeSend");
113-
detail = cloneInto(detail, document.defaultView);
114-
} catch (e) {
115-
console.log(e);
116-
LoggerCore.logger().info("error data");
117-
}
118-
}
119-
120-
const ev = new CustomEventClone(this.sendFlag, {
121-
detail,
122-
});
123-
pageDispatchEvent(ev);
152+
if (!this.pageMessaging.et) throw new Error("inject.js is not ready or destroyed.");
153+
pageDispatchCustomEvent(`evt_${this.pageMessaging.et}_${this.sendFlag}`, detail);
124154
}
125155

126156
sendMessage<T = any>(data: TMessage): Promise<T> {
127157
return new Promise((resolve: ((value: T) => void) | null) => {
128-
const messageId = uuidv4();
129-
const body: WindowMessageBody<TMessage> = {
130-
messageId,
131-
type: "sendMessage",
132-
data,
133-
};
134-
const eventId = `response:${messageId}`;
135-
this.EE.addListener(eventId, (body: WindowMessageBody<TMessage>) => {
136-
this.EE.removeAllListeners(eventId);
137-
resolve!(body.data as T);
138-
resolve = null; // 设为 null 提醒JS引擎可以GC
158+
this.pageMessaging.onReady!(() => {
159+
const messageId = uuidv4();
160+
const body: WindowMessageBody<TMessage> = {
161+
messageId,
162+
type: "sendMessage",
163+
data,
164+
};
165+
const eventId = `response:${messageId}`;
166+
this.EE.addListener(eventId, (body: WindowMessageBody<TMessage>) => {
167+
this.EE.removeAllListeners(eventId);
168+
resolve!(body.data as T);
169+
resolve = null; // 设为 null 提醒JS引擎可以GC
170+
});
171+
this.nativeSend(body);
139172
});
140-
this.nativeSend(body);
141173
});
142174
}
143175

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

166199
sendRelatedTarget(target: EventTarget): number {
200+
if (!this.pageMessaging.et) throw new Error("inject.js is not ready or destroyed.");
167201
// 特殊处理relatedTarget,返回id进行关联
168202
// 先将relatedTarget转换成id发送过去
169203
const id = (relateId = relateId === maxInteger ? 1 : relateId + 1);
170204
// 可以使用此种方式交互element
171-
const ev = new MouseEventClone(this.sendFlag, {
205+
const ev = new MouseEventClone(`evt_${this.pageMessaging.et}_${this.sendFlag}`, {
172206
movementX: id,
173207
relatedTarget: target,
174208
});

packages/message/server.test.ts

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
import { describe, expect, it, beforeEach, vi, afterEach } from "vitest";
22
import { GetSenderType, SenderConnect, SenderRuntime, Server, type IGetSender } from "./server";
3-
import { CustomEventMessage } from "./custom_event_message";
3+
import { createPageMessaging, CustomEventMessage } from "./custom_event_message";
44
import type { MessageConnect, RuntimeMessageSender } from "./types";
55
import { DefinedFlags } from "@App/app/service/service_worker/runtime.consts";
6+
import { uuidv4 } from "@App/pkg/utils/uuid";
67

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

1415
const setupGlobal = () => {
15-
const flags = "-test.server";
16+
const testFlag = uuidv4();
17+
const testPageMessaging = createPageMessaging(testFlag);
1618
// 创建 content 和 inject 之间的消息通道
17-
contentMessage = new CustomEventMessage(flags, true); // content 端
18-
injectMessage = new CustomEventMessage(flags, false); // inject 端
19+
contentMessage = new CustomEventMessage(testPageMessaging, true); // content 端
20+
injectMessage = new CustomEventMessage(testPageMessaging, false); // inject 端
21+
contentMessage.bindEmitter();
22+
injectMessage.bindEmitter();
1923

2024
// 服务端使用 content 消息
2125
server = new Server("api", contentMessage);
@@ -33,7 +37,7 @@ const setupGlobal = () => {
3337
vi.fn().mockImplementation((event: Event) => {
3438
if (event instanceof CustomEvent) {
3539
const eventType = event.type;
36-
if (eventType.includes("-test.server")) {
40+
if (eventType.includes(testFlag)) {
3741
let targetEventType: string;
3842
let messageThis: CustomEventMessage;
3943
let messageThat: CustomEventMessage;
@@ -43,11 +47,13 @@ const setupGlobal = () => {
4347
targetEventType = eventType.replace(DefinedFlags.contentFlag, DefinedFlags.injectFlag);
4448
messageThis = contentMessage;
4549
messageThat = injectMessage;
50+
console.log(12377, contentMessage, injectMessage);
4651
} else if (eventType.includes(DefinedFlags.injectFlag)) {
4752
// content -> inject
4853
targetEventType = eventType.replace(DefinedFlags.injectFlag, DefinedFlags.contentFlag);
4954
messageThis = injectMessage;
5055
messageThat = contentMessage;
56+
console.log(12378, contentMessage, injectMessage);
5157
} else {
5258
throw new Error("test mock failed");
5359
}

rspack.config.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ export default defineConfig({
3434
offscreen: `${src}/offscreen.ts`,
3535
sandbox: `${src}/sandbox.ts`,
3636
content: `${src}/content.ts`,
37+
scripting: `${src}/scripting.ts`,
3738
inject: `${src}/inject.ts`,
3839
popup: `${src}/pages/popup/main.tsx`,
3940
install: `${src}/pages/install/main.tsx`,

src/app/service/content/content.ts

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { Client, sendMessage } from "@Packages/message/client";
1+
import { Client } from "@Packages/message/client";
22
import { type CustomEventMessage } from "@Packages/message/custom_event_message";
33
import { forwardMessage, type Server } from "@Packages/message/server";
44
import type { MessageSend } from "@Packages/message/types";
@@ -16,7 +16,7 @@ export default class ContentRuntime {
1616

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

3131
init() {
32-
this.extServer.on("runtime/emitEvent", (data) => {
33-
// 转发给inject和scriptExecutor
34-
this.scriptExecutor.emitEvent(data);
35-
return sendMessage(this.senderToInject, "inject/runtime/emitEvent", data);
36-
});
37-
this.extServer.on("runtime/valueUpdate", (data) => {
38-
// 转发给inject和scriptExecutor
39-
this.scriptExecutor.valueUpdate(data);
40-
return sendMessage(this.senderToInject, "inject/runtime/valueUpdate", data);
41-
});
32+
// this.extServer.on("runtime/emitEvent", (data) => {
33+
// // 转发给inject和scriptExecutor
34+
// this.scriptExecutor.emitEvent(data);
35+
// return sendMessage(this.senderToInject, "inject/runtime/emitEvent", data);
36+
// });
37+
// this.extServer.on("runtime/valueUpdate", (data) => {
38+
// // 转发给inject和scriptExecutor
39+
// this.scriptExecutor.valueUpdate(data);
40+
// return sendMessage(this.senderToInject, "inject/runtime/valueUpdate", data);
41+
// });
4242
this.server.on("logger", (data: Logger) => {
4343
LoggerCore.logger().log(data.level, data.message, data.label);
4444
});
@@ -127,8 +127,8 @@ export default class ContentRuntime {
127127
);
128128
}
129129

130-
pageLoad(messageFlag: string, envInfo: GMInfoEnv) {
131-
this.scriptExecutor.checkEarlyStartScript("content", messageFlag, envInfo);
130+
pageLoad(envInfo: GMInfoEnv) {
131+
this.scriptExecutor.checkEarlyStartScript("content", MessageFlag, envInfo);
132132
const client = new RuntimeClient(this.senderToExt);
133133
// 向service_worker请求脚本列表及环境信息
134134
client.pageLoad().then((o) => {

src/app/service/content/utils.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -151,7 +151,8 @@ export function compilePreInjectScript(
151151
return `window['${flag}'] = function(){${autoDeleteMountCode}${scriptCode}};
152152
{
153153
let o = { cancelable: true, detail: { scriptFlag: '${flag}', scriptInfo: (${scriptInfoJSON}) } },
154-
f = () => performance.dispatchEvent(new CustomEvent('${evScriptLoad}', o)),
154+
c = typeof cloneInto === "function" ? cloneInto(o, document.defaultView) : o,
155+
f = () => performance.dispatchEvent(new CustomEvent('${evScriptLoad}', c)),
155156
needWait = f();
156157
if (needWait) performance.addEventListener('${evEnvLoad}', f, { once: true });
157158
}

0 commit comments

Comments
 (0)