196 lines
6.8 KiB
JavaScript
196 lines
6.8 KiB
JavaScript
const { scaffoldModule } = require("./scaffold-module");
|
||
|
||
function normalizeWorkerSeed(seed = {}) {
|
||
const workerSeed = seed.worker && typeof seed.worker === "object" ? seed.worker : seed;
|
||
return {
|
||
scriptBaseUrl: workerSeed.scriptBaseUrl || "https://example.invalid/assets/",
|
||
inlineScripts: Object.assign({}, workerSeed.inlineScripts || {}),
|
||
sharedWorkerName: workerSeed.sharedWorkerName || "env-shared-worker",
|
||
broadcastChannels: Array.isArray(workerSeed.broadcastChannels)
|
||
? workerSeed.broadcastChannels.slice()
|
||
: ["env-sync"],
|
||
};
|
||
}
|
||
|
||
function renderWorkerPatch(seed) {
|
||
const serializedSeed = JSON.stringify(seed, null, 2);
|
||
return `(
|
||
function installWorkerModule(globalObject) {
|
||
const seed = ${serializedSeed};
|
||
const protector = globalObject.__envNativeProtector || null;
|
||
const sharedChannels = Object.create(null);
|
||
|
||
function applyEventTarget(target) {
|
||
target._listeners = Object.create(null);
|
||
target.addEventListener = function addEventListener(type, listener) {
|
||
if (!this._listeners[type]) this._listeners[type] = [];
|
||
this._listeners[type].push(listener);
|
||
};
|
||
target.removeEventListener = function removeEventListener(type, listener) {
|
||
const list = this._listeners[type] || [];
|
||
this._listeners[type] = list.filter(function filterItem(item) {
|
||
return item !== listener;
|
||
});
|
||
};
|
||
target.dispatchEvent = function dispatchEvent(event) {
|
||
const list = (this._listeners[event.type] || []).slice();
|
||
for (const listener of list) listener.call(this, event);
|
||
const handler = this["on" + event.type];
|
||
if (typeof handler === "function") handler.call(this, event);
|
||
return true;
|
||
};
|
||
}
|
||
|
||
function createMessageEvent(data) {
|
||
return { type: "message", data: data };
|
||
}
|
||
|
||
function MessagePort() {
|
||
applyEventTarget(this);
|
||
this._entangledPort = null;
|
||
this._started = false;
|
||
}
|
||
|
||
MessagePort.prototype.postMessage = function postMessage(data) {
|
||
if (this._entangledPort) this._entangledPort.dispatchEvent(createMessageEvent(data));
|
||
};
|
||
MessagePort.prototype.start = function start() {
|
||
this._started = true;
|
||
};
|
||
MessagePort.prototype.close = function close() {
|
||
this._entangledPort = null;
|
||
this._started = false;
|
||
};
|
||
|
||
function createMessageChannel() {
|
||
const port1 = new MessagePort();
|
||
const port2 = new MessagePort();
|
||
port1._entangledPort = port2;
|
||
port2._entangledPort = port1;
|
||
return { port1: port1, port2: port2 };
|
||
}
|
||
|
||
function Worker(scriptUrl) {
|
||
applyEventTarget(this);
|
||
this.scriptURL = scriptUrl || "";
|
||
this.baseUrl = seed.scriptBaseUrl;
|
||
this.inlineSource = seed.inlineScripts[scriptUrl] || "";
|
||
}
|
||
|
||
Worker.prototype.postMessage = function postMessage(data) {
|
||
this.dispatchEvent(createMessageEvent(data));
|
||
};
|
||
Worker.prototype.terminate = function terminate() {
|
||
this._terminated = true;
|
||
};
|
||
|
||
function SharedWorker(scriptUrl, name) {
|
||
applyEventTarget(this);
|
||
this.scriptURL = scriptUrl || "";
|
||
this.name = name || seed.sharedWorkerName;
|
||
const channel = createMessageChannel();
|
||
this.port = channel.port1;
|
||
this._workerPort = channel.port2;
|
||
}
|
||
|
||
function BroadcastChannel(name) {
|
||
applyEventTarget(this);
|
||
this.name = name || "";
|
||
if (!sharedChannels[this.name]) sharedChannels[this.name] = [];
|
||
sharedChannels[this.name].push(this);
|
||
}
|
||
|
||
BroadcastChannel.prototype.postMessage = function postMessage(data) {
|
||
const peers = sharedChannels[this.name] || [];
|
||
for (const peer of peers) {
|
||
if (peer !== this) peer.dispatchEvent(createMessageEvent(data));
|
||
}
|
||
};
|
||
BroadcastChannel.prototype.close = function close() {
|
||
const peers = sharedChannels[this.name] || [];
|
||
sharedChannels[this.name] = peers.filter(function filterPeer(item) {
|
||
return item !== this;
|
||
}, this);
|
||
};
|
||
|
||
if (protector && protector.repairFunctionMeta) {
|
||
protector.repairFunctionMeta(MessagePort, { name: "MessagePort", length: 0 });
|
||
protector.repairFunctionMeta(Worker, { name: "Worker", length: 1 });
|
||
protector.repairFunctionMeta(SharedWorker, { name: "SharedWorker", length: 2 });
|
||
protector.repairFunctionMeta(BroadcastChannel, { name: "BroadcastChannel", length: 1 });
|
||
}
|
||
if (protector && protector.protectFunction) {
|
||
protector.protectFunction(MessagePort, "MessagePort");
|
||
protector.protectFunction(Worker, "Worker");
|
||
protector.protectFunction(SharedWorker, "SharedWorker");
|
||
protector.protectFunction(BroadcastChannel, "BroadcastChannel");
|
||
}
|
||
|
||
Object.defineProperty(globalObject, "MessagePort", {
|
||
configurable: true,
|
||
enumerable: false,
|
||
writable: true,
|
||
value: MessagePort
|
||
});
|
||
Object.defineProperty(globalObject, "MessageChannel", {
|
||
configurable: true,
|
||
enumerable: false,
|
||
writable: true,
|
||
value: function MessageChannel() {
|
||
return createMessageChannel();
|
||
}
|
||
});
|
||
Object.defineProperty(globalObject, "Worker", {
|
||
configurable: true,
|
||
enumerable: false,
|
||
writable: true,
|
||
value: Worker
|
||
});
|
||
Object.defineProperty(globalObject, "SharedWorker", {
|
||
configurable: true,
|
||
enumerable: false,
|
||
writable: true,
|
||
value: SharedWorker
|
||
});
|
||
Object.defineProperty(globalObject, "BroadcastChannel", {
|
||
configurable: true,
|
||
enumerable: false,
|
||
writable: true,
|
||
value: BroadcastChannel
|
||
});
|
||
|
||
return { Worker: Worker, SharedWorker: SharedWorker, BroadcastChannel: BroadcastChannel };
|
||
})(typeof globalThis !== "undefined" ? globalThis : window);
|
||
`;
|
||
}
|
||
|
||
function buildWorkerModule(seed = {}) {
|
||
const module = scaffoldModule("worker-module");
|
||
const normalizedSeed = normalizeWorkerSeed(seed);
|
||
|
||
module.patchPlan = [
|
||
"补出 Worker/SharedWorker/MessagePort/MessageChannel/BroadcastChannel 构造器。",
|
||
"用最小事件系统模拟 postMessage/onmessage 通路。",
|
||
"把 worker 脚本路径、inline source、广播频道名称外置为种子,而不是写死在补丁里。",
|
||
];
|
||
module.patchCode = renderWorkerPatch(normalizedSeed);
|
||
module.runtimeState = { worker: normalizedSeed };
|
||
module.validation = [
|
||
"检查 typeof Worker/SharedWorker/BroadcastChannel === 'function'。",
|
||
"执行 new Worker('/worker.js').postMessage({ping:1}),确认 message 事件能被触发。",
|
||
"执行 new SharedWorker('/shared.js').port.postMessage('x'),确认端口存在。",
|
||
"执行两个同名 BroadcastChannel 互发消息,确认消息广播通路正常。",
|
||
];
|
||
module.residualRisk = [
|
||
"这里只模拟消息通路,不提供真实线程、importScripts、URL.createObjectURL 等完整能力。",
|
||
"若目标站点依赖 Worker 全局作用域、自定义事件循环或二进制消息通路,仍需继续扩展。",
|
||
];
|
||
|
||
return module;
|
||
}
|
||
|
||
module.exports = {
|
||
buildWorkerModule,
|
||
normalizeWorkerSeed,
|
||
};
|