Skip to content

Commit 2ccbbaa

Browse files
committed
🐛 Offscreen→SW 消息通道改用 postMessage 支持 Blob 传输
Offscreen 转发 GM API 请求到 ServiceWorker 时,原先走 ExtensionMessage (chrome.runtime, JSON 序列化),导致 Blob 等结构化数据丢失。 改为通过 postMessage 通道(结构化克隆)双向传输,所有 GM API 自动受益。
1 parent 8b4fd88 commit 2ccbbaa

3 files changed

Lines changed: 112 additions & 22 deletions

File tree

packages/message/window_message.ts

Lines changed: 101 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,12 @@
1-
import type { Message, MessageConnect, MessageSend, RuntimeMessageSender, TMessage } from "./types";
1+
import type {
2+
Message,
3+
MessageConnect,
4+
MessageSend,
5+
OnConnectCallback,
6+
OnMessageCallback,
7+
RuntimeMessageSender,
8+
TMessage,
9+
} from "./types";
210
import { uuidv4 } from "@App/pkg/utils/uuid";
311
import EventEmitter from "eventemitter3";
412

@@ -205,32 +213,46 @@ export class WindowMessageConnect implements MessageConnect {
205213
// service_worker和offscreen同时监听消息,会导致消息被两边同时接收,但是返回结果时会产生问题,导致报错
206214
// 不进行监听的话又无法从service_worker主动发送消息
207215
// 所以service_worker与offscreen使用ServiceWorker的方式进行通信
208-
export class ServiceWorkerMessageSend implements MessageSend {
216+
// 现在同时支持接收来自offscreen的请求(实现完整Message接口),使双向通道都走postMessage(结构化克隆,支持Blob)
217+
export class ServiceWorkerMessageSend implements Message {
209218
EE = new EventEmitter<string, any>();
210219

211220
private target: PostMessage | undefined = undefined;
212221

213-
constructor() {}
214-
215-
listened: boolean = false;
222+
constructor() {
223+
// 在构造函数中设置监听,确保能接收来自offscreen的请求
224+
self.addEventListener("message", (e: MessageEvent) => {
225+
this.messageHandle(e.data, e.source as PostMessage);
226+
});
227+
}
216228

217229
async init() {
218230
if (!this.target && self.clients) {
219-
if (!this.listened) {
220-
this.listened = true;
221-
self.addEventListener("message", (e) => {
222-
this.messageHandle(e.data);
223-
});
224-
}
225231
const list = await self.clients.matchAll({ includeUncontrolled: true, type: "window" });
226232
// 找到offscreen.html窗口
227233
this.target = list.find((client) => client.url == chrome.runtime.getURL("src/offscreen.html")) as PostMessage;
228234
}
229235
}
230236

231-
messageHandle(data: WindowMessageBody) {
237+
messageHandle(data: WindowMessageBody, source?: PostMessage) {
232238
// 处理消息
233-
if (data.type === "respMessage") {
239+
if (data.type === "sendMessage" && source) {
240+
// 接收到来自offscreen的请求消息
241+
this.EE.emit("message", data.data, (resp: any) => {
242+
if (!data.messageId) {
243+
return;
244+
}
245+
const body: WindowMessageBody = {
246+
messageId: data.messageId,
247+
type: "respMessage",
248+
data: resp,
249+
};
250+
source.postMessage(body);
251+
});
252+
} else if (data.type === "connect" && source) {
253+
// 接收到来自offscreen的连接请求
254+
this.EE.emit("connect", data.data, new WindowMessageConnect(data.messageId, this.EE, source));
255+
} else if (data.type === "respMessage") {
234256
// 接收到响应消息
235257
this.EE.emit(`response:${data.messageId}`, data);
236258
} else if (data.type === "disconnect") {
@@ -240,6 +262,14 @@ export class ServiceWorkerMessageSend implements MessageSend {
240262
}
241263
}
242264

265+
onMessage(callback: OnMessageCallback): void {
266+
this.EE.addListener("message", callback);
267+
}
268+
269+
onConnect(callback: OnConnectCallback): void {
270+
this.EE.addListener("connect", callback);
271+
}
272+
243273
async connect(data: TMessage): Promise<MessageConnect> {
244274
await this.init();
245275
const body: WindowMessageBody<TMessage> = {
@@ -271,3 +301,61 @@ export class ServiceWorkerMessageSend implements MessageSend {
271301
});
272302
}
273303
}
304+
305+
// Offscreen端通过navigator.serviceWorker.controller.postMessage向SW发送消息
306+
// 与ServiceWorkerMessageSend配对使用,实现Offscreen→SW的postMessage通道
307+
export class ServiceWorkerClientMessage implements MessageSend {
308+
EE = new EventEmitter<string, any>();
309+
310+
constructor() {
311+
navigator.serviceWorker.addEventListener("message", (e) => {
312+
this.messageHandle(e.data);
313+
});
314+
}
315+
316+
messageHandle(data: WindowMessageBody) {
317+
// 只处理响应类消息,请求类消息由WindowMessage处理
318+
if (data.type === "respMessage") {
319+
this.EE.emit(`response:${data.messageId}`, data);
320+
} else if (data.type === "disconnect") {
321+
this.EE.emit(`disconnect:${data.messageId}`);
322+
} else if (data.type === "connectMessage") {
323+
this.EE.emit(`connectMessage:${data.messageId}`, data.data);
324+
}
325+
}
326+
327+
private postToServiceWorker(message: any) {
328+
navigator.serviceWorker.controller!.postMessage(message);
329+
}
330+
331+
async connect(data: TMessage): Promise<MessageConnect> {
332+
const body: WindowMessageBody<TMessage> = {
333+
messageId: uuidv4(),
334+
type: "connect",
335+
data,
336+
};
337+
const target: PostMessage = {
338+
postMessage: (msg) => this.postToServiceWorker(msg),
339+
};
340+
this.postToServiceWorker(body);
341+
return new WindowMessageConnect(body.messageId, this.EE, target);
342+
}
343+
344+
sendMessage<T = any>(data: TMessage): Promise<T> {
345+
return new Promise((resolve: ((value: T) => void) | null) => {
346+
const messageId = uuidv4();
347+
const body: WindowMessageBody<TMessage> = {
348+
messageId,
349+
type: "sendMessage",
350+
data,
351+
};
352+
const eventId = `response:${messageId}`;
353+
this.EE.addListener(eventId, (body: WindowMessageBody<TMessage>) => {
354+
this.EE.removeAllListeners(eventId);
355+
resolve!(body.data as T);
356+
resolve = null;
357+
});
358+
this.postToServiceWorker(body);
359+
});
360+
}
361+
}

src/app/service/offscreen/index.ts

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,7 @@ import { forwardMessage, Server } from "@Packages/message/server";
22
import type { MessageSend } from "@Packages/message/types";
33
import { ScriptService } from "./script";
44
import { type Logger } from "@App/app/repo/logger";
5-
import { WindowMessage } from "@Packages/message/window_message";
6-
import { ServiceWorkerClient } from "../service_worker/client";
5+
import { ServiceWorkerClientMessage, WindowMessage } from "@Packages/message/window_message";
76
import { sendMessage } from "@Packages/message/client";
87
import GMApi from "./gm_api";
98
import { MessageQueue } from "@Packages/message/message_queue";
@@ -18,12 +17,13 @@ export class OffscreenManager {
1817

1918
private messageQueue = new MessageQueue();
2019

21-
private serviceWorker: ServiceWorkerClient;
20+
// 通过postMessage与SW通信,支持结构化克隆(Blob等)
21+
private swPostMessage: ServiceWorkerClientMessage;
2222

2323
constructor(private extMsgSender: MessageSend) {
2424
this.windowMessage = new WindowMessage(window, sandbox, true);
2525
this.windowServer = new Server("offscreen", this.windowMessage);
26-
this.serviceWorker = new ServiceWorkerClient(this.extMsgSender);
26+
this.swPostMessage = new ServiceWorkerClientMessage();
2727
}
2828

2929
logger(data: Logger) {
@@ -36,7 +36,7 @@ export class OffscreenManager {
3636

3737
preparationSandbox() {
3838
// 通知初始化好环境了
39-
this.serviceWorker.preparationOffscreen();
39+
sendMessage(this.swPostMessage, "serviceWorker/preparationOffscreen");
4040
}
4141

4242
sendMessageToServiceWorker(data: { action: string; data: any }) {
@@ -55,8 +55,8 @@ export class OffscreenManager {
5555
this.messageQueue
5656
);
5757
script.init();
58-
// 转发从sandbox来的gm api请求
59-
forwardMessage("serviceWorker", "runtime/gmApi", this.windowServer, this.extMsgSender);
58+
// 转发从sandbox来的gm api请求,通过postMessage通道传输(支持Blob等结构化克隆)
59+
forwardMessage("serviceWorker", "runtime/gmApi", this.windowServer, this.swPostMessage);
6060
// 转发valueUpdate与emitEvent
6161
forwardMessage("sandbox", "runtime/valueUpdate", this.windowServer, this.windowMessage);
6262
forwardMessage("sandbox", "runtime/emitEvent", this.windowServer, this.windowMessage);

src/service_worker.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -69,9 +69,11 @@ function main() {
6969
labels: { env: "service_worker" },
7070
});
7171
loggerCore.logger().debug("service worker start");
72-
const server = new Server("serviceWorker", message);
72+
const swMessage = new ServiceWorkerMessageSend();
73+
// 同时接收ExtensionMessage(chrome.runtime)和ServiceWorkerMessageSend(postMessage)的消息
74+
const server = new Server("serviceWorker", [message, swMessage]);
7375
const messageQueue = new MessageQueue();
74-
const manager = new ServiceWorkerManager(server, messageQueue, new ServiceWorkerMessageSend());
76+
const manager = new ServiceWorkerManager(server, messageQueue, swMessage);
7577
manager.initManager();
7678
// 初始化沙盒环境
7779
setupOffscreenDocument();

0 commit comments

Comments
 (0)