@lifeart/async-dom 2.0.0-alpha.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +623 -0
- package/dist/base.d.cts +398 -0
- package/dist/base.d.cts.map +1 -0
- package/dist/base.d.ts +398 -0
- package/dist/base.d.ts.map +1 -0
- package/dist/cli.cjs +528 -0
- package/dist/cli.cjs.map +1 -0
- package/dist/cli.d.cts +1 -0
- package/dist/cli.d.ts +1 -0
- package/dist/cli.js +493 -0
- package/dist/cli.js.map +1 -0
- package/dist/debug.d.cts +145 -0
- package/dist/debug.d.cts.map +1 -0
- package/dist/debug.d.ts +145 -0
- package/dist/debug.d.ts.map +1 -0
- package/dist/index.cjs +26 -0
- package/dist/index.d.cts +560 -0
- package/dist/index.d.cts.map +1 -0
- package/dist/index.d.ts +560 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +5 -0
- package/dist/index2.d.cts +5 -0
- package/dist/index2.d.ts +5 -0
- package/dist/index3.d.cts +882 -0
- package/dist/index3.d.cts.map +1 -0
- package/dist/index3.d.ts +882 -0
- package/dist/index3.d.ts.map +1 -0
- package/dist/main-thread.cjs +5459 -0
- package/dist/main-thread.cjs.map +1 -0
- package/dist/main-thread.js +5429 -0
- package/dist/main-thread.js.map +1 -0
- package/dist/react.cjs +116 -0
- package/dist/react.cjs.map +1 -0
- package/dist/react.d.cts +91 -0
- package/dist/react.d.cts.map +1 -0
- package/dist/react.d.ts +91 -0
- package/dist/react.d.ts.map +1 -0
- package/dist/react.js +113 -0
- package/dist/react.js.map +1 -0
- package/dist/resolve-debug.cjs +24 -0
- package/dist/resolve-debug.cjs.map +1 -0
- package/dist/resolve-debug.js +19 -0
- package/dist/resolve-debug.js.map +1 -0
- package/dist/server.cjs +250 -0
- package/dist/server.cjs.map +1 -0
- package/dist/server.d.cts +127 -0
- package/dist/server.d.cts.map +1 -0
- package/dist/server.d.ts +127 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +245 -0
- package/dist/server.js.map +1 -0
- package/dist/svelte.cjs +48 -0
- package/dist/svelte.cjs.map +1 -0
- package/dist/svelte.d.cts +38 -0
- package/dist/svelte.d.cts.map +1 -0
- package/dist/svelte.d.ts +38 -0
- package/dist/svelte.d.ts.map +1 -0
- package/dist/svelte.js +47 -0
- package/dist/svelte.js.map +1 -0
- package/dist/sync-channel.cjs +532 -0
- package/dist/sync-channel.cjs.map +1 -0
- package/dist/sync-channel.js +425 -0
- package/dist/sync-channel.js.map +1 -0
- package/dist/transport.cjs +213 -0
- package/dist/transport.cjs.map +1 -0
- package/dist/transport.d.cts +79 -0
- package/dist/transport.d.cts.map +1 -0
- package/dist/transport.d.ts +79 -0
- package/dist/transport.d.ts.map +1 -0
- package/dist/transport.js +202 -0
- package/dist/transport.js.map +1 -0
- package/dist/vite-plugin.cjs +112 -0
- package/dist/vite-plugin.cjs.map +1 -0
- package/dist/vite-plugin.d.cts +39 -0
- package/dist/vite-plugin.d.cts.map +1 -0
- package/dist/vite-plugin.d.ts +39 -0
- package/dist/vite-plugin.d.ts.map +1 -0
- package/dist/vite-plugin.js +107 -0
- package/dist/vite-plugin.js.map +1 -0
- package/dist/vue.cjs +123 -0
- package/dist/vue.cjs.map +1 -0
- package/dist/vue.d.cts +126 -0
- package/dist/vue.d.cts.map +1 -0
- package/dist/vue.d.ts +126 -0
- package/dist/vue.d.ts.map +1 -0
- package/dist/vue.js +120 -0
- package/dist/vue.js.map +1 -0
- package/dist/worker-thread.cjs +2751 -0
- package/dist/worker-thread.cjs.map +1 -0
- package/dist/worker-thread.js +2692 -0
- package/dist/worker-thread.js.map +1 -0
- package/dist/worker-transport.cjs +136 -0
- package/dist/worker-transport.cjs.map +1 -0
- package/dist/worker-transport.d.cts +162 -0
- package/dist/worker-transport.d.cts.map +1 -0
- package/dist/worker-transport.d.ts +162 -0
- package/dist/worker-transport.d.ts.map +1 -0
- package/dist/worker-transport.js +125 -0
- package/dist/worker-transport.js.map +1 -0
- package/dist/worker.cjs +12 -0
- package/dist/worker.d.cts +2 -0
- package/dist/worker.d.ts +2 -0
- package/dist/worker.js +2 -0
- package/dist/ws-server-transport.cjs +147 -0
- package/dist/ws-server-transport.cjs.map +1 -0
- package/dist/ws-server-transport.d.cts +64 -0
- package/dist/ws-server-transport.d.cts.map +1 -0
- package/dist/ws-server-transport.d.ts +64 -0
- package/dist/ws-server-transport.d.ts.map +1 -0
- package/dist/ws-server-transport.js +142 -0
- package/dist/ws-server-transport.js.map +1 -0
- package/dist/ws-transport.cjs +954 -0
- package/dist/ws-transport.cjs.map +1 -0
- package/dist/ws-transport.js +913 -0
- package/dist/ws-transport.js.map +1 -0
- package/package.json +145 -0
|
@@ -0,0 +1,2751 @@
|
|
|
1
|
+
const require_sync_channel = require("./sync-channel.cjs");
|
|
2
|
+
const require_worker_transport = require("./worker-transport.cjs");
|
|
3
|
+
//#region src/platform.ts
|
|
4
|
+
/**
|
|
5
|
+
* Create a PlatformHost for Web Worker environments (uses `self`).
|
|
6
|
+
*/
|
|
7
|
+
function createWorkerPlatform() {
|
|
8
|
+
return {
|
|
9
|
+
navigator: {
|
|
10
|
+
userAgent: self.navigator.userAgent,
|
|
11
|
+
language: self.navigator.language,
|
|
12
|
+
languages: self.navigator.languages,
|
|
13
|
+
hardwareConcurrency: self.navigator.hardwareConcurrency
|
|
14
|
+
},
|
|
15
|
+
installErrorHandlers(onError, onUnhandledRejection) {
|
|
16
|
+
const workerScope = self;
|
|
17
|
+
const prevOnError = workerScope.onerror;
|
|
18
|
+
const prevOnRejection = workerScope.onunhandledrejection;
|
|
19
|
+
workerScope.onerror = (event, source, lineno, colno, error) => {
|
|
20
|
+
onError(typeof event === "string" ? event : event.message ?? "Unknown worker error", error, source ?? (typeof event !== "string" ? event.filename : void 0), lineno ?? (typeof event !== "string" ? event.lineno : void 0), colno ?? (typeof event !== "string" ? event.colno : void 0));
|
|
21
|
+
};
|
|
22
|
+
workerScope.onunhandledrejection = (event) => {
|
|
23
|
+
onUnhandledRejection(event.reason);
|
|
24
|
+
};
|
|
25
|
+
return () => {
|
|
26
|
+
workerScope.onerror = prevOnError;
|
|
27
|
+
workerScope.onunhandledrejection = prevOnRejection;
|
|
28
|
+
};
|
|
29
|
+
},
|
|
30
|
+
onBeforeUnload(callback) {
|
|
31
|
+
if (typeof self !== "undefined" && "addEventListener" in self) {
|
|
32
|
+
self.addEventListener("beforeunload", callback);
|
|
33
|
+
return () => {
|
|
34
|
+
self.removeEventListener("beforeunload", callback);
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
return () => {};
|
|
38
|
+
}
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Create a PlatformHost for Node.js environments (uses `process`).
|
|
43
|
+
*/
|
|
44
|
+
function createNodePlatform() {
|
|
45
|
+
const os = typeof globalThis !== "undefined" ? globalThis : {};
|
|
46
|
+
return {
|
|
47
|
+
navigator: {
|
|
48
|
+
userAgent: `Node.js/${typeof process !== "undefined" ? process.version : "unknown"}`,
|
|
49
|
+
language: "en-US",
|
|
50
|
+
languages: ["en-US"],
|
|
51
|
+
hardwareConcurrency: typeof os.navigator === "object" && os.navigator !== null && "hardwareConcurrency" in os.navigator ? os.navigator.hardwareConcurrency ?? 1 : 1
|
|
52
|
+
},
|
|
53
|
+
installErrorHandlers(onError, onUnhandledRejection) {
|
|
54
|
+
if (typeof process === "undefined") return () => {};
|
|
55
|
+
const proc = process;
|
|
56
|
+
const onUncaught = (err) => {
|
|
57
|
+
onError(err.message, err, void 0, void 0, void 0);
|
|
58
|
+
};
|
|
59
|
+
const onRejection = (reason) => {
|
|
60
|
+
onUnhandledRejection(reason);
|
|
61
|
+
};
|
|
62
|
+
proc.on("uncaughtException", onUncaught);
|
|
63
|
+
proc.on("unhandledRejection", onRejection);
|
|
64
|
+
return () => {
|
|
65
|
+
proc.removeListener("uncaughtException", onUncaught);
|
|
66
|
+
proc.removeListener("unhandledRejection", onRejection);
|
|
67
|
+
};
|
|
68
|
+
},
|
|
69
|
+
onBeforeUnload(callback) {
|
|
70
|
+
if (typeof process === "undefined") return () => {};
|
|
71
|
+
const proc = process;
|
|
72
|
+
const handler = () => {
|
|
73
|
+
callback();
|
|
74
|
+
};
|
|
75
|
+
proc.on("beforeExit", handler);
|
|
76
|
+
return () => {
|
|
77
|
+
proc.removeListener("beforeExit", handler);
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Auto-detect the current platform and create the appropriate PlatformHost.
|
|
84
|
+
*/
|
|
85
|
+
function detectPlatform() {
|
|
86
|
+
if (typeof self !== "undefined" && typeof self.navigator !== "undefined") return createWorkerPlatform();
|
|
87
|
+
return createNodePlatform();
|
|
88
|
+
}
|
|
89
|
+
//#endregion
|
|
90
|
+
//#region src/worker-thread/selector-engine.ts
|
|
91
|
+
function parseSimpleSelector(input) {
|
|
92
|
+
const sel = {};
|
|
93
|
+
let i = 0;
|
|
94
|
+
const len = input.length;
|
|
95
|
+
while (i < len) {
|
|
96
|
+
const ch = input[i];
|
|
97
|
+
if (ch === "#") {
|
|
98
|
+
i++;
|
|
99
|
+
let id = "";
|
|
100
|
+
while (i < len && input[i] !== "." && input[i] !== "#" && input[i] !== "[" && input[i] !== ":") id += input[i++];
|
|
101
|
+
sel.id = id;
|
|
102
|
+
} else if (ch === ".") {
|
|
103
|
+
i++;
|
|
104
|
+
let cls = "";
|
|
105
|
+
while (i < len && input[i] !== "." && input[i] !== "#" && input[i] !== "[" && input[i] !== ":") cls += input[i++];
|
|
106
|
+
if (!sel.classes) sel.classes = [];
|
|
107
|
+
sel.classes.push(cls);
|
|
108
|
+
} else if (ch === "[") {
|
|
109
|
+
i++;
|
|
110
|
+
let name = "";
|
|
111
|
+
while (i < len && input[i] !== "]" && input[i] !== "=") name += input[i++];
|
|
112
|
+
name = name.trim();
|
|
113
|
+
let value;
|
|
114
|
+
if (i < len && input[i] === "=") {
|
|
115
|
+
i++;
|
|
116
|
+
let v = "";
|
|
117
|
+
const quote = input[i] === "\"" || input[i] === "'" ? input[i++] : "";
|
|
118
|
+
while (i < len && input[i] !== "]" && (quote ? input[i] !== quote : true)) v += input[i++];
|
|
119
|
+
if (quote && i < len) i++;
|
|
120
|
+
v = v.trim();
|
|
121
|
+
value = v;
|
|
122
|
+
}
|
|
123
|
+
if (i < len && input[i] === "]") i++;
|
|
124
|
+
if (!sel.attrs) sel.attrs = [];
|
|
125
|
+
sel.attrs.push({
|
|
126
|
+
name,
|
|
127
|
+
value
|
|
128
|
+
});
|
|
129
|
+
} else if (ch === ":") {
|
|
130
|
+
i++;
|
|
131
|
+
let pseudo = "";
|
|
132
|
+
while (i < len && input[i] !== "." && input[i] !== "#" && input[i] !== "[" && input[i] !== ":") pseudo += input[i++];
|
|
133
|
+
if (!sel.pseudos) sel.pseudos = [];
|
|
134
|
+
sel.pseudos.push(pseudo);
|
|
135
|
+
} else {
|
|
136
|
+
let tag = "";
|
|
137
|
+
while (i < len && input[i] !== "." && input[i] !== "#" && input[i] !== "[" && input[i] !== ":" && input[i] !== " " && input[i] !== ">") tag += input[i++];
|
|
138
|
+
if (tag) sel.tag = tag.toUpperCase();
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
return sel;
|
|
142
|
+
}
|
|
143
|
+
function parseSelectorGroup(input) {
|
|
144
|
+
const groups = [];
|
|
145
|
+
let current = "";
|
|
146
|
+
let inBracket = false;
|
|
147
|
+
let inQuote = "";
|
|
148
|
+
for (let i = 0; i < input.length; i++) {
|
|
149
|
+
const ch = input[i];
|
|
150
|
+
if (inQuote) {
|
|
151
|
+
current += ch;
|
|
152
|
+
if (ch === inQuote) inQuote = "";
|
|
153
|
+
} else if (ch === "\"" || ch === "'") {
|
|
154
|
+
current += ch;
|
|
155
|
+
inQuote = ch;
|
|
156
|
+
} else if (ch === "[") {
|
|
157
|
+
inBracket = true;
|
|
158
|
+
current += ch;
|
|
159
|
+
} else if (ch === "]") {
|
|
160
|
+
inBracket = false;
|
|
161
|
+
current += ch;
|
|
162
|
+
} else if (ch === "," && !inBracket) {
|
|
163
|
+
groups.push(current.trim());
|
|
164
|
+
current = "";
|
|
165
|
+
} else current += ch;
|
|
166
|
+
}
|
|
167
|
+
if (current.trim()) groups.push(current.trim());
|
|
168
|
+
return groups.map((group) => parseSelectorChain(group));
|
|
169
|
+
}
|
|
170
|
+
function parseSelectorChain(input) {
|
|
171
|
+
const parts = [];
|
|
172
|
+
const tokens = tokenize(input);
|
|
173
|
+
let combinator = "";
|
|
174
|
+
for (const token of tokens) if (token === ">") combinator = ">";
|
|
175
|
+
else if (token === " ") {
|
|
176
|
+
if (combinator !== ">") combinator = " ";
|
|
177
|
+
} else {
|
|
178
|
+
parts.push({
|
|
179
|
+
selector: parseSimpleSelector(token),
|
|
180
|
+
combinator
|
|
181
|
+
});
|
|
182
|
+
combinator = "";
|
|
183
|
+
}
|
|
184
|
+
return parts;
|
|
185
|
+
}
|
|
186
|
+
function tokenize(input) {
|
|
187
|
+
const tokens = [];
|
|
188
|
+
let current = "";
|
|
189
|
+
let inBracket = false;
|
|
190
|
+
for (let i = 0; i < input.length; i++) {
|
|
191
|
+
const ch = input[i];
|
|
192
|
+
if (ch === "[") inBracket = true;
|
|
193
|
+
if (ch === "]") inBracket = false;
|
|
194
|
+
if (!inBracket && (ch === " " || ch === ">")) {
|
|
195
|
+
if (current) {
|
|
196
|
+
tokens.push(current);
|
|
197
|
+
current = "";
|
|
198
|
+
}
|
|
199
|
+
if (ch === ">") tokens.push(">");
|
|
200
|
+
else if (tokens.length > 0 && tokens[tokens.length - 1] !== ">" && tokens[tokens.length - 1] !== " ") tokens.push(" ");
|
|
201
|
+
} else current += ch;
|
|
202
|
+
}
|
|
203
|
+
if (current) tokens.push(current);
|
|
204
|
+
return tokens;
|
|
205
|
+
}
|
|
206
|
+
function matchesSimple(el, sel) {
|
|
207
|
+
if (sel.tag && sel.tag !== "*" && el.tagName !== sel.tag) return false;
|
|
208
|
+
if (sel.id && el.getAttribute("id") !== sel.id) return false;
|
|
209
|
+
if (sel.classes) {
|
|
210
|
+
const elClasses = el.className.split(" ").filter(Boolean);
|
|
211
|
+
for (const cls of sel.classes) if (!elClasses.includes(cls)) return false;
|
|
212
|
+
}
|
|
213
|
+
if (sel.attrs) {
|
|
214
|
+
for (const attr of sel.attrs) if (attr.value !== void 0) {
|
|
215
|
+
if (el.getAttribute(attr.name) !== attr.value) return false;
|
|
216
|
+
} else if (!el.hasAttribute(attr.name)) return false;
|
|
217
|
+
}
|
|
218
|
+
if (sel.pseudos) {
|
|
219
|
+
for (const pseudo of sel.pseudos) if (pseudo === "first-child") {
|
|
220
|
+
if (!el.parentNode) return false;
|
|
221
|
+
if (el.parentNode.childNodes.filter((c) => c.nodeType === 1)[0] !== el) return false;
|
|
222
|
+
} else if (pseudo === "last-child") {
|
|
223
|
+
if (!el.parentNode) return false;
|
|
224
|
+
const siblings = el.parentNode.childNodes.filter((c) => c.nodeType === 1);
|
|
225
|
+
if (siblings[siblings.length - 1] !== el) return false;
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
return true;
|
|
229
|
+
}
|
|
230
|
+
function matchesChain(el, chain) {
|
|
231
|
+
if (chain.length === 0) return false;
|
|
232
|
+
let current = el;
|
|
233
|
+
for (let i = chain.length - 1; i >= 0; i--) {
|
|
234
|
+
const part = chain[i];
|
|
235
|
+
if (!current) return false;
|
|
236
|
+
if (i === chain.length - 1) {
|
|
237
|
+
if (!matchesSimple(current, part.selector)) return false;
|
|
238
|
+
} else if (chain[i + 1].combinator === ">") {
|
|
239
|
+
current = current.parentNode;
|
|
240
|
+
if (!current || !matchesSimple(current, part.selector)) return false;
|
|
241
|
+
} else {
|
|
242
|
+
current = current.parentNode;
|
|
243
|
+
while (current) {
|
|
244
|
+
if (matchesSimple(current, part.selector)) break;
|
|
245
|
+
current = current.parentNode;
|
|
246
|
+
}
|
|
247
|
+
if (!current) return false;
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
return true;
|
|
251
|
+
}
|
|
252
|
+
function matches(el, selector) {
|
|
253
|
+
return parseSelectorGroup(selector).some((chain) => matchesChain(el, chain));
|
|
254
|
+
}
|
|
255
|
+
function querySelectorAll(root, selector) {
|
|
256
|
+
const groups = parseSelectorGroup(selector);
|
|
257
|
+
const results = [];
|
|
258
|
+
walkElements(root, (el) => {
|
|
259
|
+
if (groups.some((chain) => matchesChain(el, chain))) results.push(el);
|
|
260
|
+
});
|
|
261
|
+
return results;
|
|
262
|
+
}
|
|
263
|
+
function querySelector(root, selector) {
|
|
264
|
+
const groups = parseSelectorGroup(selector);
|
|
265
|
+
let found = null;
|
|
266
|
+
walkElements(root, (el) => {
|
|
267
|
+
if (groups.some((chain) => matchesChain(el, chain))) {
|
|
268
|
+
found = el;
|
|
269
|
+
return true;
|
|
270
|
+
}
|
|
271
|
+
});
|
|
272
|
+
return found;
|
|
273
|
+
}
|
|
274
|
+
function walkElements(root, callback) {
|
|
275
|
+
for (const child of root.childNodes) if (child.nodeType === 1) {
|
|
276
|
+
const el = child;
|
|
277
|
+
if (callback(el) === true) return true;
|
|
278
|
+
if (walkElements(el, callback)) return true;
|
|
279
|
+
}
|
|
280
|
+
return false;
|
|
281
|
+
}
|
|
282
|
+
//#endregion
|
|
283
|
+
//#region src/worker-thread/style-proxy.ts
|
|
284
|
+
const KEBAB_REGEX = /[A-Z\u00C0-\u00D6\u00D8-\u00DE]/g;
|
|
285
|
+
const kebabCache = /* @__PURE__ */ new Map();
|
|
286
|
+
function toKebabCase(str) {
|
|
287
|
+
let cached = kebabCache.get(str);
|
|
288
|
+
if (cached === void 0) {
|
|
289
|
+
cached = str.replace(KEBAB_REGEX, (match) => `-${match.toLowerCase()}`);
|
|
290
|
+
kebabCache.set(str, cached);
|
|
291
|
+
}
|
|
292
|
+
return cached;
|
|
293
|
+
}
|
|
294
|
+
/**
|
|
295
|
+
* Creates a Proxy-based style object that intercepts property sets
|
|
296
|
+
* and emits setStyle mutations.
|
|
297
|
+
*/
|
|
298
|
+
function createStyleProxy(owner, collector, initialStyles = {}) {
|
|
299
|
+
const backing = { ...initialStyles };
|
|
300
|
+
return new Proxy(backing, {
|
|
301
|
+
get(target, prop) {
|
|
302
|
+
if (typeof prop !== "string") return "";
|
|
303
|
+
if (prop === "getPropertyValue") return (name) => target[toKebabCase(name)] ?? "";
|
|
304
|
+
if (prop === "removeProperty") return (name) => {
|
|
305
|
+
const key = toKebabCase(name);
|
|
306
|
+
const old = target[key] ?? "";
|
|
307
|
+
delete target[key];
|
|
308
|
+
const mutation = {
|
|
309
|
+
action: "setStyle",
|
|
310
|
+
id: owner._nodeId,
|
|
311
|
+
property: key,
|
|
312
|
+
value: ""
|
|
313
|
+
};
|
|
314
|
+
collector.add(mutation);
|
|
315
|
+
return old;
|
|
316
|
+
};
|
|
317
|
+
if (prop === "setProperty") return (name, value, _priority) => {
|
|
318
|
+
const key = toKebabCase(name);
|
|
319
|
+
target[key] = value;
|
|
320
|
+
const mutation = {
|
|
321
|
+
action: "setStyle",
|
|
322
|
+
id: owner._nodeId,
|
|
323
|
+
property: key,
|
|
324
|
+
value: String(value)
|
|
325
|
+
};
|
|
326
|
+
collector.add(mutation);
|
|
327
|
+
};
|
|
328
|
+
if (prop === "cssText") return Object.entries(target).map(([k, v]) => `${k}: ${v}`).join("; ");
|
|
329
|
+
return target[toKebabCase(prop)] ?? "";
|
|
330
|
+
},
|
|
331
|
+
set(target, prop, value) {
|
|
332
|
+
if (typeof prop !== "string") return true;
|
|
333
|
+
const key = toKebabCase(prop);
|
|
334
|
+
if (key === "css-text") {
|
|
335
|
+
parseStyleString(value).forEach(([k, v]) => {
|
|
336
|
+
target[k] = v;
|
|
337
|
+
const mutation = {
|
|
338
|
+
action: "setStyle",
|
|
339
|
+
id: owner._nodeId,
|
|
340
|
+
property: k,
|
|
341
|
+
value: v
|
|
342
|
+
};
|
|
343
|
+
collector.add(mutation);
|
|
344
|
+
});
|
|
345
|
+
return true;
|
|
346
|
+
}
|
|
347
|
+
target[key] = value;
|
|
348
|
+
const mutation = {
|
|
349
|
+
action: "setStyle",
|
|
350
|
+
id: owner._nodeId,
|
|
351
|
+
property: key,
|
|
352
|
+
value: String(value)
|
|
353
|
+
};
|
|
354
|
+
collector.add(mutation);
|
|
355
|
+
return true;
|
|
356
|
+
}
|
|
357
|
+
});
|
|
358
|
+
}
|
|
359
|
+
function parseStyleString(value) {
|
|
360
|
+
const result = [];
|
|
361
|
+
for (const part of value.split(";")) {
|
|
362
|
+
const colonIdx = part.indexOf(":");
|
|
363
|
+
if (colonIdx === -1) continue;
|
|
364
|
+
const key = part.slice(0, colonIdx).trim();
|
|
365
|
+
const val = part.slice(colonIdx + 1).trim();
|
|
366
|
+
if (key && val !== void 0) result.push([key, val]);
|
|
367
|
+
}
|
|
368
|
+
return result;
|
|
369
|
+
}
|
|
370
|
+
//#endregion
|
|
371
|
+
//#region src/worker-thread/element.ts
|
|
372
|
+
function kebabToCamel(str) {
|
|
373
|
+
return str.replace(/-([a-z])/g, (_match, letter) => letter.toUpperCase());
|
|
374
|
+
}
|
|
375
|
+
function escapeHtml(s) {
|
|
376
|
+
return s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
377
|
+
}
|
|
378
|
+
function escapeAttr(s) {
|
|
379
|
+
return s.replace(/&/g, "&").replace(/"/g, """).replace(/'/g, "'");
|
|
380
|
+
}
|
|
381
|
+
let listenerCounter = 0;
|
|
382
|
+
const VOID_ELEMENTS = new Set([
|
|
383
|
+
"area",
|
|
384
|
+
"base",
|
|
385
|
+
"br",
|
|
386
|
+
"col",
|
|
387
|
+
"embed",
|
|
388
|
+
"hr",
|
|
389
|
+
"img",
|
|
390
|
+
"input",
|
|
391
|
+
"link",
|
|
392
|
+
"meta",
|
|
393
|
+
"param",
|
|
394
|
+
"source",
|
|
395
|
+
"track",
|
|
396
|
+
"wbr"
|
|
397
|
+
]);
|
|
398
|
+
/**
|
|
399
|
+
* Virtual DOM element that records mutations via the MutationCollector
|
|
400
|
+
* instead of touching real DOM.
|
|
401
|
+
*
|
|
402
|
+
* Implements a broad subset of the HTMLElement API:
|
|
403
|
+
* - Attributes: setAttribute, getAttribute, removeAttribute, dataset
|
|
404
|
+
* - Children: appendChild, removeChild, insertBefore, replaceChild, etc.
|
|
405
|
+
* - Text/HTML: textContent, innerHTML, outerHTML, insertAdjacentHTML
|
|
406
|
+
* - Input state: value, checked, disabled, selectedIndex (with main-thread sync)
|
|
407
|
+
* - Media state: currentTime, duration, paused, ended, readyState
|
|
408
|
+
* - Events: addEventListener, removeEventListener, on* handlers, dispatchEvent
|
|
409
|
+
* - Layout queries: getBoundingClientRect, clientWidth/Height, etc. (via SyncChannel)
|
|
410
|
+
* - CSS: style proxy, classList, className
|
|
411
|
+
* - Traversal: querySelector, closest, contains, parentNode, children, etc.
|
|
412
|
+
*
|
|
413
|
+
* Every mutating operation emits a DomMutation to the collector. Layout queries
|
|
414
|
+
* use the SyncChannel to synchronously read values from the main thread's DOM.
|
|
415
|
+
*/
|
|
416
|
+
var VirtualElement = class VirtualElement {
|
|
417
|
+
static ELEMENT_NODE = 1;
|
|
418
|
+
static TEXT_NODE = 3;
|
|
419
|
+
static COMMENT_NODE = 8;
|
|
420
|
+
static DOCUMENT_NODE = 9;
|
|
421
|
+
static DOCUMENT_FRAGMENT_NODE = 11;
|
|
422
|
+
_nodeId;
|
|
423
|
+
nodeName;
|
|
424
|
+
tagName;
|
|
425
|
+
nodeType = 1;
|
|
426
|
+
namespaceURI;
|
|
427
|
+
parentNode = null;
|
|
428
|
+
_ownerDocument = null;
|
|
429
|
+
childNodes = [];
|
|
430
|
+
_attributes = /* @__PURE__ */ new Map();
|
|
431
|
+
_classes = [];
|
|
432
|
+
_textContent = "";
|
|
433
|
+
_value = "";
|
|
434
|
+
_checked = false;
|
|
435
|
+
_disabled = false;
|
|
436
|
+
_selectedIndex = -1;
|
|
437
|
+
_datasetProxy = null;
|
|
438
|
+
style;
|
|
439
|
+
classList;
|
|
440
|
+
get id() {
|
|
441
|
+
return this.getAttribute("id") ?? "";
|
|
442
|
+
}
|
|
443
|
+
set id(value) {
|
|
444
|
+
this.setAttribute("id", value);
|
|
445
|
+
}
|
|
446
|
+
get children() {
|
|
447
|
+
return this.childNodes.filter((c) => c.nodeType === 1);
|
|
448
|
+
}
|
|
449
|
+
get childElementCount() {
|
|
450
|
+
return this.childNodes.filter((c) => c.nodeType === 1).length;
|
|
451
|
+
}
|
|
452
|
+
get firstElementChild() {
|
|
453
|
+
return this.childNodes.find((c) => c.nodeType === 1) ?? null;
|
|
454
|
+
}
|
|
455
|
+
get lastElementChild() {
|
|
456
|
+
for (let i = this.childNodes.length - 1; i >= 0; i--) if (this.childNodes[i].nodeType === 1) return this.childNodes[i];
|
|
457
|
+
return null;
|
|
458
|
+
}
|
|
459
|
+
get clientWidth() {
|
|
460
|
+
return this._readNodeProperty("clientWidth") ?? 0;
|
|
461
|
+
}
|
|
462
|
+
get clientHeight() {
|
|
463
|
+
return this._readNodeProperty("clientHeight") ?? 0;
|
|
464
|
+
}
|
|
465
|
+
get scrollWidth() {
|
|
466
|
+
return this._readNodeProperty("scrollWidth") ?? 0;
|
|
467
|
+
}
|
|
468
|
+
get scrollHeight() {
|
|
469
|
+
return this._readNodeProperty("scrollHeight") ?? 0;
|
|
470
|
+
}
|
|
471
|
+
get offsetWidth() {
|
|
472
|
+
return this._readNodeProperty("offsetWidth") ?? 0;
|
|
473
|
+
}
|
|
474
|
+
get offsetHeight() {
|
|
475
|
+
return this._readNodeProperty("offsetHeight") ?? 0;
|
|
476
|
+
}
|
|
477
|
+
get offsetTop() {
|
|
478
|
+
return this._readNodeProperty("offsetTop") ?? 0;
|
|
479
|
+
}
|
|
480
|
+
get offsetLeft() {
|
|
481
|
+
return this._readNodeProperty("offsetLeft") ?? 0;
|
|
482
|
+
}
|
|
483
|
+
get scrollTop() {
|
|
484
|
+
return this._readNodeProperty("scrollTop") ?? 0;
|
|
485
|
+
}
|
|
486
|
+
set scrollTop(v) {
|
|
487
|
+
const mutation = {
|
|
488
|
+
action: "setProperty",
|
|
489
|
+
id: this._nodeId,
|
|
490
|
+
property: "scrollTop",
|
|
491
|
+
value: v
|
|
492
|
+
};
|
|
493
|
+
this.collector.add(mutation);
|
|
494
|
+
}
|
|
495
|
+
get scrollLeft() {
|
|
496
|
+
return this._readNodeProperty("scrollLeft") ?? 0;
|
|
497
|
+
}
|
|
498
|
+
set scrollLeft(v) {
|
|
499
|
+
const mutation = {
|
|
500
|
+
action: "setProperty",
|
|
501
|
+
id: this._nodeId,
|
|
502
|
+
property: "scrollLeft",
|
|
503
|
+
value: v
|
|
504
|
+
};
|
|
505
|
+
this.collector.add(mutation);
|
|
506
|
+
}
|
|
507
|
+
/**
|
|
508
|
+
* Synchronously read a numeric DOM property from the main thread via SyncChannel.
|
|
509
|
+
* Returns null if no sync channel is available (e.g., no SharedArrayBuffer support).
|
|
510
|
+
*/
|
|
511
|
+
_readNodeProperty(prop) {
|
|
512
|
+
const channel = this._ownerDocument?._syncChannel;
|
|
513
|
+
if (channel) {
|
|
514
|
+
const result = channel.request(require_sync_channel.QueryType.NodeProperty, JSON.stringify({
|
|
515
|
+
nodeId: this._nodeId,
|
|
516
|
+
property: prop
|
|
517
|
+
}));
|
|
518
|
+
if (typeof result === "number") return result;
|
|
519
|
+
}
|
|
520
|
+
return null;
|
|
521
|
+
}
|
|
522
|
+
constructor(tag, collector, id) {
|
|
523
|
+
this.collector = collector;
|
|
524
|
+
this.nodeName = tag.toUpperCase();
|
|
525
|
+
this.tagName = this.nodeName;
|
|
526
|
+
this._nodeId = id ?? require_sync_channel.createNodeId();
|
|
527
|
+
this.namespaceURI = "http://www.w3.org/1999/xhtml";
|
|
528
|
+
this.style = createStyleProxy(this, collector);
|
|
529
|
+
this.classList = new VirtualClassList(this);
|
|
530
|
+
}
|
|
531
|
+
_setNamespaceURI(ns) {
|
|
532
|
+
this.namespaceURI = ns;
|
|
533
|
+
}
|
|
534
|
+
setAttribute(name, value) {
|
|
535
|
+
if (name === "id") {
|
|
536
|
+
const oldId = this._attributes.get("id");
|
|
537
|
+
this._attributes.set(name, value);
|
|
538
|
+
if (this._ownerDocument) {
|
|
539
|
+
if (oldId) this._ownerDocument.unregisterElementById(oldId);
|
|
540
|
+
this._ownerDocument.registerElementById(value, this);
|
|
541
|
+
}
|
|
542
|
+
const mutation = {
|
|
543
|
+
action: "setAttribute",
|
|
544
|
+
id: this._nodeId,
|
|
545
|
+
name: "id",
|
|
546
|
+
value
|
|
547
|
+
};
|
|
548
|
+
this.collector.add(mutation);
|
|
549
|
+
return;
|
|
550
|
+
}
|
|
551
|
+
if (name === "class") {
|
|
552
|
+
this._classes = value ? value.split(/\s+/).filter(Boolean) : [];
|
|
553
|
+
this._attributes.set("class", value);
|
|
554
|
+
const mutation = {
|
|
555
|
+
action: "setClassName",
|
|
556
|
+
id: this._nodeId,
|
|
557
|
+
name: value
|
|
558
|
+
};
|
|
559
|
+
this.collector.add(mutation);
|
|
560
|
+
return;
|
|
561
|
+
}
|
|
562
|
+
if (name === "style") {
|
|
563
|
+
this._parseAndSetStyles(value);
|
|
564
|
+
const mutation = {
|
|
565
|
+
action: "setAttribute",
|
|
566
|
+
id: this._nodeId,
|
|
567
|
+
name: "style",
|
|
568
|
+
value,
|
|
569
|
+
optional: true
|
|
570
|
+
};
|
|
571
|
+
this.collector.add(mutation);
|
|
572
|
+
return;
|
|
573
|
+
}
|
|
574
|
+
this._attributes.set(name, value);
|
|
575
|
+
const mutation = {
|
|
576
|
+
action: "setAttribute",
|
|
577
|
+
id: this._nodeId,
|
|
578
|
+
name,
|
|
579
|
+
value
|
|
580
|
+
};
|
|
581
|
+
this.collector.add(mutation);
|
|
582
|
+
}
|
|
583
|
+
getAttribute(name) {
|
|
584
|
+
return this._attributes.get(name) ?? null;
|
|
585
|
+
}
|
|
586
|
+
hasAttribute(name) {
|
|
587
|
+
return this._attributes.has(name);
|
|
588
|
+
}
|
|
589
|
+
removeAttribute(name) {
|
|
590
|
+
if (name === "class") this._classes = [];
|
|
591
|
+
this._attributes.delete(name);
|
|
592
|
+
const mutation = {
|
|
593
|
+
action: "removeAttribute",
|
|
594
|
+
id: this._nodeId,
|
|
595
|
+
name
|
|
596
|
+
};
|
|
597
|
+
this.collector.add(mutation);
|
|
598
|
+
}
|
|
599
|
+
getAttributeNS(_ns, name) {
|
|
600
|
+
return this.getAttribute(name);
|
|
601
|
+
}
|
|
602
|
+
setAttributeNS(_ns, name, value) {
|
|
603
|
+
this.setAttribute(name, value);
|
|
604
|
+
}
|
|
605
|
+
removeAttributeNS(_ns, name) {
|
|
606
|
+
this.removeAttribute(name);
|
|
607
|
+
}
|
|
608
|
+
get attributes() {
|
|
609
|
+
const entries = [...this._attributes.entries()];
|
|
610
|
+
return {
|
|
611
|
+
length: entries.length,
|
|
612
|
+
item(index) {
|
|
613
|
+
const entry = entries[index];
|
|
614
|
+
return entry ? {
|
|
615
|
+
name: entry[0],
|
|
616
|
+
value: entry[1]
|
|
617
|
+
} : null;
|
|
618
|
+
}
|
|
619
|
+
};
|
|
620
|
+
}
|
|
621
|
+
appendChild(child) {
|
|
622
|
+
if (child instanceof VirtualElement && child.nodeName === "#DOCUMENT-FRAGMENT") {
|
|
623
|
+
const fragmentChildren = [...child.childNodes];
|
|
624
|
+
for (const fc of fragmentChildren) this._appendSingleChild(fc);
|
|
625
|
+
child.childNodes.length = 0;
|
|
626
|
+
return child;
|
|
627
|
+
}
|
|
628
|
+
this._appendSingleChild(child);
|
|
629
|
+
return child;
|
|
630
|
+
}
|
|
631
|
+
_appendSingleChild(child) {
|
|
632
|
+
if (child.parentNode) child.parentNode.childNodes = child.parentNode.childNodes.filter((c) => c !== child);
|
|
633
|
+
child.parentNode = this;
|
|
634
|
+
this.childNodes.push(child);
|
|
635
|
+
const mutation = {
|
|
636
|
+
action: "appendChild",
|
|
637
|
+
id: this._nodeId,
|
|
638
|
+
childId: child._nodeId
|
|
639
|
+
};
|
|
640
|
+
this.collector.add(mutation);
|
|
641
|
+
}
|
|
642
|
+
removeChild(child) {
|
|
643
|
+
if (child instanceof VirtualElement) child._cleanupFromDocument();
|
|
644
|
+
this.childNodes = this.childNodes.filter((c) => c !== child);
|
|
645
|
+
child.parentNode = null;
|
|
646
|
+
const mutation = {
|
|
647
|
+
action: "removeChild",
|
|
648
|
+
id: this._nodeId,
|
|
649
|
+
childId: child._nodeId
|
|
650
|
+
};
|
|
651
|
+
this.collector.add(mutation);
|
|
652
|
+
return child;
|
|
653
|
+
}
|
|
654
|
+
insertBefore(newChild, refChild) {
|
|
655
|
+
if (newChild instanceof VirtualElement && newChild.nodeName === "#DOCUMENT-FRAGMENT") {
|
|
656
|
+
const fragmentChildren = [...newChild.childNodes];
|
|
657
|
+
for (const fc of fragmentChildren) this.insertBefore(fc, refChild);
|
|
658
|
+
newChild.childNodes.length = 0;
|
|
659
|
+
return newChild;
|
|
660
|
+
}
|
|
661
|
+
if (newChild.parentNode) newChild.parentNode.childNodes = newChild.parentNode.childNodes.filter((c) => c !== newChild);
|
|
662
|
+
newChild.parentNode = this;
|
|
663
|
+
if (refChild === null) this.childNodes.push(newChild);
|
|
664
|
+
else {
|
|
665
|
+
const index = this.childNodes.indexOf(refChild);
|
|
666
|
+
if (index === -1) this.childNodes.push(newChild);
|
|
667
|
+
else this.childNodes.splice(index, 0, newChild);
|
|
668
|
+
}
|
|
669
|
+
const mutation = {
|
|
670
|
+
action: "insertBefore",
|
|
671
|
+
id: this._nodeId,
|
|
672
|
+
newId: newChild._nodeId,
|
|
673
|
+
refId: refChild?._nodeId ?? null
|
|
674
|
+
};
|
|
675
|
+
this.collector.add(mutation);
|
|
676
|
+
return newChild;
|
|
677
|
+
}
|
|
678
|
+
remove() {
|
|
679
|
+
this._cleanupFromDocument();
|
|
680
|
+
if (this.parentNode) this.parentNode.childNodes = this.parentNode.childNodes.filter((c) => c !== this);
|
|
681
|
+
this.parentNode = null;
|
|
682
|
+
const mutation = {
|
|
683
|
+
action: "removeNode",
|
|
684
|
+
id: this._nodeId
|
|
685
|
+
};
|
|
686
|
+
this.collector.add(mutation);
|
|
687
|
+
}
|
|
688
|
+
append(...nodes) {
|
|
689
|
+
for (const node of nodes) this.appendChild(node);
|
|
690
|
+
}
|
|
691
|
+
prepend(...nodes) {
|
|
692
|
+
const first = this.firstChild;
|
|
693
|
+
for (const node of nodes) this.insertBefore(node, first);
|
|
694
|
+
}
|
|
695
|
+
replaceWith(...nodes) {
|
|
696
|
+
const parent = this.parentNode;
|
|
697
|
+
if (!parent) return;
|
|
698
|
+
const nextSib = this.nextSibling;
|
|
699
|
+
this.remove();
|
|
700
|
+
for (const node of nodes) parent.insertBefore(node, nextSib);
|
|
701
|
+
}
|
|
702
|
+
before(...nodes) {
|
|
703
|
+
const parent = this.parentNode;
|
|
704
|
+
if (!parent) return;
|
|
705
|
+
for (const node of nodes) parent.insertBefore(node, this);
|
|
706
|
+
}
|
|
707
|
+
after(...nodes) {
|
|
708
|
+
const parent = this.parentNode;
|
|
709
|
+
if (!parent) return;
|
|
710
|
+
const nextSib = this.nextSibling;
|
|
711
|
+
for (const node of nodes) parent.insertBefore(node, nextSib);
|
|
712
|
+
}
|
|
713
|
+
replaceChildren(...nodes) {
|
|
714
|
+
while (this.childNodes.length > 0) this.removeChild(this.childNodes[0]);
|
|
715
|
+
for (const node of nodes) this.appendChild(node);
|
|
716
|
+
}
|
|
717
|
+
get textContent() {
|
|
718
|
+
if (this.childNodes.length === 0) return this._textContent;
|
|
719
|
+
let result = "";
|
|
720
|
+
for (const child of this.childNodes) if (child.nodeType === 3) result += child.nodeValue;
|
|
721
|
+
else if (child.nodeType === 1) result += child.textContent;
|
|
722
|
+
return result;
|
|
723
|
+
}
|
|
724
|
+
set textContent(value) {
|
|
725
|
+
for (const child of this.childNodes) {
|
|
726
|
+
if (child instanceof VirtualElement) child._cleanupFromDocument();
|
|
727
|
+
else if (this._ownerDocument) this._ownerDocument.unregisterElement(child._nodeId);
|
|
728
|
+
child.parentNode = null;
|
|
729
|
+
}
|
|
730
|
+
this.childNodes.length = 0;
|
|
731
|
+
this._textContent = value;
|
|
732
|
+
const mutation = {
|
|
733
|
+
action: "setTextContent",
|
|
734
|
+
id: this._nodeId,
|
|
735
|
+
textContent: value
|
|
736
|
+
};
|
|
737
|
+
this.collector.add(mutation);
|
|
738
|
+
}
|
|
739
|
+
get innerHTML() {
|
|
740
|
+
if (this.childNodes.length === 0) return this._textContent ? escapeHtml(this._textContent) : "";
|
|
741
|
+
return this.childNodes.map((child) => {
|
|
742
|
+
if (child.nodeType === 3) return escapeHtml(child.nodeValue);
|
|
743
|
+
if (child.nodeType === 8) return `<!--${child.nodeValue.replace(/--/g, "")}-->`;
|
|
744
|
+
if (child instanceof VirtualElement) return child.outerHTML;
|
|
745
|
+
return "";
|
|
746
|
+
}).join("");
|
|
747
|
+
}
|
|
748
|
+
set innerHTML(value) {
|
|
749
|
+
this._textContent = "";
|
|
750
|
+
for (const child of this.childNodes) {
|
|
751
|
+
if (child instanceof VirtualElement) child._cleanupFromDocument();
|
|
752
|
+
else if (this._ownerDocument) this._ownerDocument.unregisterElement(child._nodeId);
|
|
753
|
+
child.parentNode = null;
|
|
754
|
+
}
|
|
755
|
+
this.childNodes.length = 0;
|
|
756
|
+
const mutation = {
|
|
757
|
+
action: "setHTML",
|
|
758
|
+
id: this._nodeId,
|
|
759
|
+
html: value
|
|
760
|
+
};
|
|
761
|
+
this.collector.add(mutation);
|
|
762
|
+
}
|
|
763
|
+
get outerHTML() {
|
|
764
|
+
const tag = this.tagName.toLowerCase();
|
|
765
|
+
let attrs = "";
|
|
766
|
+
for (const [key, value] of this._attributes) attrs += ` ${key}="${escapeAttr(value)}"`;
|
|
767
|
+
if (this._classes.length > 0 && !this._attributes.has("class")) attrs += ` class="${escapeAttr(this._classes.join(" "))}"`;
|
|
768
|
+
const cssText = this.style.cssText;
|
|
769
|
+
if (cssText) attrs += ` style="${escapeAttr(cssText)}"`;
|
|
770
|
+
const inner = this.innerHTML;
|
|
771
|
+
if (VOID_ELEMENTS.has(tag)) return `<${tag}${attrs}>`;
|
|
772
|
+
return `<${tag}${attrs}>${inner}</${tag}>`;
|
|
773
|
+
}
|
|
774
|
+
get value() {
|
|
775
|
+
return this._value;
|
|
776
|
+
}
|
|
777
|
+
set value(v) {
|
|
778
|
+
this._value = v;
|
|
779
|
+
const mutation = {
|
|
780
|
+
action: "setProperty",
|
|
781
|
+
id: this._nodeId,
|
|
782
|
+
property: "value",
|
|
783
|
+
value: v
|
|
784
|
+
};
|
|
785
|
+
this.collector.add(mutation);
|
|
786
|
+
}
|
|
787
|
+
get checked() {
|
|
788
|
+
return this._checked;
|
|
789
|
+
}
|
|
790
|
+
set checked(v) {
|
|
791
|
+
this._checked = v;
|
|
792
|
+
const mutation = {
|
|
793
|
+
action: "setProperty",
|
|
794
|
+
id: this._nodeId,
|
|
795
|
+
property: "checked",
|
|
796
|
+
value: v
|
|
797
|
+
};
|
|
798
|
+
this.collector.add(mutation);
|
|
799
|
+
}
|
|
800
|
+
get disabled() {
|
|
801
|
+
return this._disabled;
|
|
802
|
+
}
|
|
803
|
+
set disabled(v) {
|
|
804
|
+
this._disabled = v;
|
|
805
|
+
const mutation = {
|
|
806
|
+
action: "setProperty",
|
|
807
|
+
id: this._nodeId,
|
|
808
|
+
property: "disabled",
|
|
809
|
+
value: v
|
|
810
|
+
};
|
|
811
|
+
this.collector.add(mutation);
|
|
812
|
+
}
|
|
813
|
+
get selectedIndex() {
|
|
814
|
+
return this._selectedIndex;
|
|
815
|
+
}
|
|
816
|
+
set selectedIndex(v) {
|
|
817
|
+
this._selectedIndex = v;
|
|
818
|
+
const mutation = {
|
|
819
|
+
action: "setProperty",
|
|
820
|
+
id: this._nodeId,
|
|
821
|
+
property: "selectedIndex",
|
|
822
|
+
value: v
|
|
823
|
+
};
|
|
824
|
+
this.collector.add(mutation);
|
|
825
|
+
}
|
|
826
|
+
/**
|
|
827
|
+
* Sync input state from a main-thread event without emitting mutations.
|
|
828
|
+
* Called by VirtualDocument.dispatchEvent to keep the worker's model in sync
|
|
829
|
+
* with real DOM input values after user interaction.
|
|
830
|
+
*/
|
|
831
|
+
_updateInputState(state) {
|
|
832
|
+
if (state.value !== void 0) this._value = state.value;
|
|
833
|
+
if (state.checked !== void 0) this._checked = state.checked;
|
|
834
|
+
if (state.selectedIndex !== void 0) this._selectedIndex = state.selectedIndex;
|
|
835
|
+
}
|
|
836
|
+
_currentTime = 0;
|
|
837
|
+
_duration = 0;
|
|
838
|
+
_paused = true;
|
|
839
|
+
_ended = false;
|
|
840
|
+
_readyState = 0;
|
|
841
|
+
get currentTime() {
|
|
842
|
+
return this._currentTime;
|
|
843
|
+
}
|
|
844
|
+
set currentTime(v) {
|
|
845
|
+
this._currentTime = v;
|
|
846
|
+
this.collector.add({
|
|
847
|
+
action: "setProperty",
|
|
848
|
+
id: this._nodeId,
|
|
849
|
+
property: "currentTime",
|
|
850
|
+
value: v
|
|
851
|
+
});
|
|
852
|
+
}
|
|
853
|
+
get duration() {
|
|
854
|
+
return this._duration;
|
|
855
|
+
}
|
|
856
|
+
get paused() {
|
|
857
|
+
return this._paused;
|
|
858
|
+
}
|
|
859
|
+
get ended() {
|
|
860
|
+
return this._ended;
|
|
861
|
+
}
|
|
862
|
+
get readyState() {
|
|
863
|
+
return this._readyState;
|
|
864
|
+
}
|
|
865
|
+
/** Sync media element state from a main-thread event without emitting mutations. */
|
|
866
|
+
_updateMediaState(state) {
|
|
867
|
+
if (state.currentTime !== void 0) this._currentTime = state.currentTime;
|
|
868
|
+
if (state.duration !== void 0) this._duration = state.duration;
|
|
869
|
+
if (state.paused !== void 0) this._paused = state.paused;
|
|
870
|
+
if (state.ended !== void 0) this._ended = state.ended;
|
|
871
|
+
if (state.readyState !== void 0) this._readyState = state.readyState;
|
|
872
|
+
}
|
|
873
|
+
get className() {
|
|
874
|
+
return this._classes.join(" ");
|
|
875
|
+
}
|
|
876
|
+
set className(value) {
|
|
877
|
+
this._classes = value ? value.split(/\s+/).filter(Boolean) : [];
|
|
878
|
+
if (this._classes.length > 0) this._attributes.set("class", this._classes.join(" "));
|
|
879
|
+
else this._attributes.delete("class");
|
|
880
|
+
const mutation = {
|
|
881
|
+
action: "setClassName",
|
|
882
|
+
id: this._nodeId,
|
|
883
|
+
name: value
|
|
884
|
+
};
|
|
885
|
+
this.collector.add(mutation);
|
|
886
|
+
}
|
|
887
|
+
/** Map of listenerId -> callback for event dispatch. */
|
|
888
|
+
_eventListeners = /* @__PURE__ */ new Map();
|
|
889
|
+
/** Map of listenerId -> event name, used to match removeEventListener by name+callback. */
|
|
890
|
+
_listenerEventNames = /* @__PURE__ */ new Map();
|
|
891
|
+
/** Map of event name -> callback for on* property handlers (e.g., onclick). */
|
|
892
|
+
_onHandlers = /* @__PURE__ */ new Map();
|
|
893
|
+
/**
|
|
894
|
+
* Register an event listener. Emits an addEventListener mutation so the main thread
|
|
895
|
+
* attaches a real DOM listener that will serialize and forward events back.
|
|
896
|
+
* Supports the `once` option by wrapping the callback with auto-removal.
|
|
897
|
+
*/
|
|
898
|
+
addEventListener(name, callback, options) {
|
|
899
|
+
if (!name) return;
|
|
900
|
+
const listenerId = `${this._nodeId}_${name}_${++listenerCounter}`;
|
|
901
|
+
const once = typeof options === "object" ? options?.once ?? false : false;
|
|
902
|
+
let effectiveCallback = callback;
|
|
903
|
+
if (once) {
|
|
904
|
+
const originalCb = callback;
|
|
905
|
+
effectiveCallback = (e) => {
|
|
906
|
+
originalCb(e);
|
|
907
|
+
this.removeEventListener(name, effectiveCallback);
|
|
908
|
+
};
|
|
909
|
+
}
|
|
910
|
+
this._eventListeners.set(listenerId, effectiveCallback);
|
|
911
|
+
this._listenerEventNames.set(listenerId, name);
|
|
912
|
+
this._ownerDocument?.registerListener(listenerId, this);
|
|
913
|
+
const mutation = {
|
|
914
|
+
action: "addEventListener",
|
|
915
|
+
id: this._nodeId,
|
|
916
|
+
name,
|
|
917
|
+
listenerId
|
|
918
|
+
};
|
|
919
|
+
this.collector.add(mutation);
|
|
920
|
+
}
|
|
921
|
+
getEventListener(listenerId) {
|
|
922
|
+
return this._eventListeners.get(listenerId);
|
|
923
|
+
}
|
|
924
|
+
/** Remove a listener by event name and callback reference. Emits a removeEventListener mutation. */
|
|
925
|
+
removeEventListener(_name, callback) {
|
|
926
|
+
for (const [listenerId, cb] of this._eventListeners.entries()) if (cb === callback && this._listenerEventNames.get(listenerId) === _name) {
|
|
927
|
+
this._eventListeners.delete(listenerId);
|
|
928
|
+
this._listenerEventNames.delete(listenerId);
|
|
929
|
+
this._ownerDocument?.unregisterListener(listenerId);
|
|
930
|
+
const mutation = {
|
|
931
|
+
action: "removeEventListener",
|
|
932
|
+
id: this._nodeId,
|
|
933
|
+
listenerId
|
|
934
|
+
};
|
|
935
|
+
this.collector.add(mutation);
|
|
936
|
+
break;
|
|
937
|
+
}
|
|
938
|
+
}
|
|
939
|
+
/** Invoke all listeners matching the event type. Called during bubbling by VirtualDocument.dispatchEvent. */
|
|
940
|
+
_dispatchBubbledEvent(event) {
|
|
941
|
+
for (const [listenerId, cb] of this._eventListeners.entries()) if (this._listenerEventNames.get(listenerId) === event.type) {
|
|
942
|
+
cb(event);
|
|
943
|
+
if (event.immediatePropagationStopped) break;
|
|
944
|
+
}
|
|
945
|
+
}
|
|
946
|
+
/**
|
|
947
|
+
* Recursively clean up this element and all children from the document's registries.
|
|
948
|
+
* Called before emitting removal mutations to prevent memory leaks.
|
|
949
|
+
*/
|
|
950
|
+
_cleanupFromDocument() {
|
|
951
|
+
for (const listenerId of this._eventListeners.keys()) this._ownerDocument?.unregisterListener(listenerId);
|
|
952
|
+
this._eventListeners.clear();
|
|
953
|
+
this._listenerEventNames.clear();
|
|
954
|
+
this._onHandlers.clear();
|
|
955
|
+
if (this._ownerDocument) {
|
|
956
|
+
const elId = this._attributes.get("id");
|
|
957
|
+
if (elId) this._ownerDocument.unregisterElementById(elId);
|
|
958
|
+
this._ownerDocument.unregisterElement(this._nodeId);
|
|
959
|
+
}
|
|
960
|
+
for (const child of this.childNodes) if (child instanceof VirtualElement) child._cleanupFromDocument();
|
|
961
|
+
else if (this._ownerDocument) this._ownerDocument.unregisterElement(child._nodeId);
|
|
962
|
+
}
|
|
963
|
+
/** Emit a configureEvent mutation to tell the main thread to call preventDefault for this event. */
|
|
964
|
+
preventDefaultFor(eventName) {
|
|
965
|
+
const mutation = {
|
|
966
|
+
action: "configureEvent",
|
|
967
|
+
id: this._nodeId,
|
|
968
|
+
name: eventName,
|
|
969
|
+
preventDefault: true
|
|
970
|
+
};
|
|
971
|
+
this.collector.add(mutation);
|
|
972
|
+
}
|
|
973
|
+
/** Implement on* handler properties (onclick, etc.) by managing a single listener per event name. */
|
|
974
|
+
_setOnHandler(eventName, cb) {
|
|
975
|
+
const prev = this._onHandlers.get(eventName);
|
|
976
|
+
if (prev) this.removeEventListener(eventName, prev);
|
|
977
|
+
if (cb) {
|
|
978
|
+
this.addEventListener(eventName, cb);
|
|
979
|
+
this._onHandlers.set(eventName, cb);
|
|
980
|
+
} else this._onHandlers.delete(eventName);
|
|
981
|
+
}
|
|
982
|
+
set onclick(cb) {
|
|
983
|
+
this._setOnHandler("click", cb);
|
|
984
|
+
}
|
|
985
|
+
set ondblclick(cb) {
|
|
986
|
+
this._setOnHandler("dblclick", cb);
|
|
987
|
+
}
|
|
988
|
+
set onmouseenter(cb) {
|
|
989
|
+
this._setOnHandler("mouseenter", cb);
|
|
990
|
+
}
|
|
991
|
+
set onmouseleave(cb) {
|
|
992
|
+
this._setOnHandler("mouseleave", cb);
|
|
993
|
+
}
|
|
994
|
+
set onmousedown(cb) {
|
|
995
|
+
this._setOnHandler("mousedown", cb);
|
|
996
|
+
}
|
|
997
|
+
set onmouseup(cb) {
|
|
998
|
+
this._setOnHandler("mouseup", cb);
|
|
999
|
+
}
|
|
1000
|
+
set onmouseover(cb) {
|
|
1001
|
+
this._setOnHandler("mouseover", cb);
|
|
1002
|
+
}
|
|
1003
|
+
set onmousemove(cb) {
|
|
1004
|
+
this._setOnHandler("mousemove", cb);
|
|
1005
|
+
}
|
|
1006
|
+
set onkeydown(cb) {
|
|
1007
|
+
this._setOnHandler("keydown", cb);
|
|
1008
|
+
}
|
|
1009
|
+
set onkeyup(cb) {
|
|
1010
|
+
this._setOnHandler("keyup", cb);
|
|
1011
|
+
}
|
|
1012
|
+
set onkeypress(cb) {
|
|
1013
|
+
this._setOnHandler("keypress", cb);
|
|
1014
|
+
}
|
|
1015
|
+
set onchange(cb) {
|
|
1016
|
+
this._setOnHandler("change", cb);
|
|
1017
|
+
}
|
|
1018
|
+
set oncontextmenu(cb) {
|
|
1019
|
+
this._setOnHandler("contextmenu", cb);
|
|
1020
|
+
}
|
|
1021
|
+
set oninput(cb) {
|
|
1022
|
+
this._setOnHandler("input", cb);
|
|
1023
|
+
}
|
|
1024
|
+
set onfocus(cb) {
|
|
1025
|
+
this._setOnHandler("focus", cb);
|
|
1026
|
+
}
|
|
1027
|
+
set onblur(cb) {
|
|
1028
|
+
this._setOnHandler("blur", cb);
|
|
1029
|
+
}
|
|
1030
|
+
set onsubmit(cb) {
|
|
1031
|
+
this._setOnHandler("submit", cb);
|
|
1032
|
+
}
|
|
1033
|
+
get firstChild() {
|
|
1034
|
+
return this.childNodes[0] ?? null;
|
|
1035
|
+
}
|
|
1036
|
+
get lastChild() {
|
|
1037
|
+
return this.childNodes[this.childNodes.length - 1] ?? null;
|
|
1038
|
+
}
|
|
1039
|
+
get nextSibling() {
|
|
1040
|
+
if (!this.parentNode) return null;
|
|
1041
|
+
const idx = this.parentNode.childNodes.indexOf(this);
|
|
1042
|
+
return this.parentNode.childNodes[idx + 1] ?? null;
|
|
1043
|
+
}
|
|
1044
|
+
get previousSibling() {
|
|
1045
|
+
if (!this.parentNode) return null;
|
|
1046
|
+
const idx = this.parentNode.childNodes.indexOf(this);
|
|
1047
|
+
return this.parentNode.childNodes[idx - 1] ?? null;
|
|
1048
|
+
}
|
|
1049
|
+
get parentElement() {
|
|
1050
|
+
return this.parentNode;
|
|
1051
|
+
}
|
|
1052
|
+
get ownerDocument() {
|
|
1053
|
+
return this._ownerDocument;
|
|
1054
|
+
}
|
|
1055
|
+
get isConnected() {
|
|
1056
|
+
let current = this;
|
|
1057
|
+
while (current) {
|
|
1058
|
+
if (current._ownerDocument && current === current._ownerDocument.documentElement) return true;
|
|
1059
|
+
current = current.parentNode;
|
|
1060
|
+
}
|
|
1061
|
+
return false;
|
|
1062
|
+
}
|
|
1063
|
+
getRootNode() {
|
|
1064
|
+
let current = this;
|
|
1065
|
+
while (current.parentNode) current = current.parentNode;
|
|
1066
|
+
return current;
|
|
1067
|
+
}
|
|
1068
|
+
get nextElementSibling() {
|
|
1069
|
+
if (!this.parentNode) return null;
|
|
1070
|
+
const siblings = this.parentNode.childNodes;
|
|
1071
|
+
const idx = siblings.indexOf(this);
|
|
1072
|
+
for (let i = idx + 1; i < siblings.length; i++) if (siblings[i].nodeType === 1) return siblings[i];
|
|
1073
|
+
return null;
|
|
1074
|
+
}
|
|
1075
|
+
get previousElementSibling() {
|
|
1076
|
+
if (!this.parentNode) return null;
|
|
1077
|
+
const siblings = this.parentNode.childNodes;
|
|
1078
|
+
const idx = siblings.indexOf(this);
|
|
1079
|
+
for (let i = idx - 1; i >= 0; i--) if (siblings[i].nodeType === 1) return siblings[i];
|
|
1080
|
+
return null;
|
|
1081
|
+
}
|
|
1082
|
+
hasChildNodes() {
|
|
1083
|
+
return this.childNodes.length > 0;
|
|
1084
|
+
}
|
|
1085
|
+
replaceChild(newChild, oldChild) {
|
|
1086
|
+
if (this.childNodes.indexOf(oldChild) === -1) return oldChild;
|
|
1087
|
+
this.insertBefore(newChild, oldChild);
|
|
1088
|
+
this.removeChild(oldChild);
|
|
1089
|
+
return oldChild;
|
|
1090
|
+
}
|
|
1091
|
+
normalize() {}
|
|
1092
|
+
dispatchEvent(event) {
|
|
1093
|
+
const evt = event;
|
|
1094
|
+
if (evt.type) this._dispatchBubbledEvent(evt);
|
|
1095
|
+
return true;
|
|
1096
|
+
}
|
|
1097
|
+
cloneNode(deep) {
|
|
1098
|
+
const clone = new VirtualElement(this.nodeName, this.collector);
|
|
1099
|
+
const createMutation = {
|
|
1100
|
+
action: "createNode",
|
|
1101
|
+
id: clone._nodeId,
|
|
1102
|
+
tag: this.tagName,
|
|
1103
|
+
textContent: this._textContent || ""
|
|
1104
|
+
};
|
|
1105
|
+
this.collector.add(createMutation);
|
|
1106
|
+
for (const [k, v] of this._attributes) clone.setAttribute(k, v);
|
|
1107
|
+
clone._classes = [...this._classes];
|
|
1108
|
+
clone._ownerDocument = this._ownerDocument;
|
|
1109
|
+
if (deep) for (const child of this.childNodes) clone.appendChild(child.cloneNode(true));
|
|
1110
|
+
return clone;
|
|
1111
|
+
}
|
|
1112
|
+
get dataset() {
|
|
1113
|
+
if (this._datasetProxy) return this._datasetProxy;
|
|
1114
|
+
const el = this;
|
|
1115
|
+
this._datasetProxy = new Proxy({}, {
|
|
1116
|
+
get(_target, prop) {
|
|
1117
|
+
if (typeof prop !== "string") return void 0;
|
|
1118
|
+
const attrName = `data-${toKebabCase(prop)}`;
|
|
1119
|
+
return el.getAttribute(attrName) ?? void 0;
|
|
1120
|
+
},
|
|
1121
|
+
set(_target, prop, value) {
|
|
1122
|
+
if (typeof prop !== "string") return true;
|
|
1123
|
+
const attrName = `data-${toKebabCase(prop)}`;
|
|
1124
|
+
el.setAttribute(attrName, String(value));
|
|
1125
|
+
return true;
|
|
1126
|
+
},
|
|
1127
|
+
deleteProperty(_target, prop) {
|
|
1128
|
+
if (typeof prop !== "string") return true;
|
|
1129
|
+
const attrName = `data-${toKebabCase(prop)}`;
|
|
1130
|
+
el.removeAttribute(attrName);
|
|
1131
|
+
return true;
|
|
1132
|
+
},
|
|
1133
|
+
has(_target, prop) {
|
|
1134
|
+
if (typeof prop !== "string") return false;
|
|
1135
|
+
const attrName = `data-${toKebabCase(prop)}`;
|
|
1136
|
+
return el.hasAttribute(attrName);
|
|
1137
|
+
},
|
|
1138
|
+
ownKeys() {
|
|
1139
|
+
const keys = [];
|
|
1140
|
+
const attrs = el.attributes;
|
|
1141
|
+
for (let i = 0; i < attrs.length; i++) {
|
|
1142
|
+
const attr = attrs.item(i);
|
|
1143
|
+
if (attr?.name.startsWith("data-")) keys.push(kebabToCamel(attr.name.slice(5)));
|
|
1144
|
+
}
|
|
1145
|
+
return keys;
|
|
1146
|
+
},
|
|
1147
|
+
getOwnPropertyDescriptor(_target, prop) {
|
|
1148
|
+
if (typeof prop !== "string") return void 0;
|
|
1149
|
+
const attrName = `data-${toKebabCase(prop)}`;
|
|
1150
|
+
if (!el.hasAttribute(attrName)) return void 0;
|
|
1151
|
+
return {
|
|
1152
|
+
configurable: true,
|
|
1153
|
+
enumerable: true,
|
|
1154
|
+
writable: true,
|
|
1155
|
+
value: el.getAttribute(attrName)
|
|
1156
|
+
};
|
|
1157
|
+
}
|
|
1158
|
+
});
|
|
1159
|
+
return this._datasetProxy;
|
|
1160
|
+
}
|
|
1161
|
+
insertAdjacentHTML(position, html) {
|
|
1162
|
+
const mutation = {
|
|
1163
|
+
action: "insertAdjacentHTML",
|
|
1164
|
+
id: this._nodeId,
|
|
1165
|
+
position,
|
|
1166
|
+
html
|
|
1167
|
+
};
|
|
1168
|
+
this.collector.add(mutation);
|
|
1169
|
+
}
|
|
1170
|
+
contains(other) {
|
|
1171
|
+
if (!other) return false;
|
|
1172
|
+
if (other === this) return true;
|
|
1173
|
+
return this.childNodes.some((child) => child === other || child instanceof VirtualElement && child.contains(other));
|
|
1174
|
+
}
|
|
1175
|
+
querySelector(selector) {
|
|
1176
|
+
return querySelector(this, selector);
|
|
1177
|
+
}
|
|
1178
|
+
querySelectorAll(selector) {
|
|
1179
|
+
return querySelectorAll(this, selector);
|
|
1180
|
+
}
|
|
1181
|
+
matches(selector) {
|
|
1182
|
+
return matches(this, selector);
|
|
1183
|
+
}
|
|
1184
|
+
getElementsByTagName(tagName) {
|
|
1185
|
+
const upper = tagName.toUpperCase();
|
|
1186
|
+
return querySelectorAll(this, upper === "*" ? "*" : tagName);
|
|
1187
|
+
}
|
|
1188
|
+
getElementsByClassName(className) {
|
|
1189
|
+
const selector = className.split(/\s+/).filter(Boolean).map((c) => `.${c}`).join("");
|
|
1190
|
+
return querySelectorAll(this, selector);
|
|
1191
|
+
}
|
|
1192
|
+
closest(selector) {
|
|
1193
|
+
let current = this;
|
|
1194
|
+
while (current) {
|
|
1195
|
+
if (matches(current, selector)) return current;
|
|
1196
|
+
current = current.parentNode;
|
|
1197
|
+
}
|
|
1198
|
+
return null;
|
|
1199
|
+
}
|
|
1200
|
+
focus() {
|
|
1201
|
+
this.collector.add({
|
|
1202
|
+
action: "callMethod",
|
|
1203
|
+
id: this._nodeId,
|
|
1204
|
+
method: "focus",
|
|
1205
|
+
args: []
|
|
1206
|
+
});
|
|
1207
|
+
}
|
|
1208
|
+
blur() {
|
|
1209
|
+
this.collector.add({
|
|
1210
|
+
action: "callMethod",
|
|
1211
|
+
id: this._nodeId,
|
|
1212
|
+
method: "blur",
|
|
1213
|
+
args: []
|
|
1214
|
+
});
|
|
1215
|
+
}
|
|
1216
|
+
play() {
|
|
1217
|
+
this.collector.add({
|
|
1218
|
+
action: "callMethod",
|
|
1219
|
+
id: this._nodeId,
|
|
1220
|
+
method: "play",
|
|
1221
|
+
args: []
|
|
1222
|
+
});
|
|
1223
|
+
}
|
|
1224
|
+
pause() {
|
|
1225
|
+
this.collector.add({
|
|
1226
|
+
action: "callMethod",
|
|
1227
|
+
id: this._nodeId,
|
|
1228
|
+
method: "pause",
|
|
1229
|
+
args: []
|
|
1230
|
+
});
|
|
1231
|
+
}
|
|
1232
|
+
load() {
|
|
1233
|
+
this.collector.add({
|
|
1234
|
+
action: "callMethod",
|
|
1235
|
+
id: this._nodeId,
|
|
1236
|
+
method: "load",
|
|
1237
|
+
args: []
|
|
1238
|
+
});
|
|
1239
|
+
}
|
|
1240
|
+
click() {
|
|
1241
|
+
this.collector.add({
|
|
1242
|
+
action: "callMethod",
|
|
1243
|
+
id: this._nodeId,
|
|
1244
|
+
method: "click",
|
|
1245
|
+
args: []
|
|
1246
|
+
});
|
|
1247
|
+
}
|
|
1248
|
+
scrollIntoView(options) {
|
|
1249
|
+
this.collector.add({
|
|
1250
|
+
action: "callMethod",
|
|
1251
|
+
id: this._nodeId,
|
|
1252
|
+
method: "scrollIntoView",
|
|
1253
|
+
args: options ? [options] : []
|
|
1254
|
+
});
|
|
1255
|
+
}
|
|
1256
|
+
select() {
|
|
1257
|
+
this.collector.add({
|
|
1258
|
+
action: "callMethod",
|
|
1259
|
+
id: this._nodeId,
|
|
1260
|
+
method: "select",
|
|
1261
|
+
args: []
|
|
1262
|
+
});
|
|
1263
|
+
}
|
|
1264
|
+
showModal() {
|
|
1265
|
+
this.collector.add({
|
|
1266
|
+
action: "callMethod",
|
|
1267
|
+
id: this._nodeId,
|
|
1268
|
+
method: "showModal",
|
|
1269
|
+
args: []
|
|
1270
|
+
});
|
|
1271
|
+
}
|
|
1272
|
+
close() {
|
|
1273
|
+
this.collector.add({
|
|
1274
|
+
action: "callMethod",
|
|
1275
|
+
id: this._nodeId,
|
|
1276
|
+
method: "close",
|
|
1277
|
+
args: []
|
|
1278
|
+
});
|
|
1279
|
+
}
|
|
1280
|
+
/**
|
|
1281
|
+
* Synchronously query the main thread for this element's bounding rect via SyncChannel.
|
|
1282
|
+
* Returns a zero rect if no sync channel is available.
|
|
1283
|
+
*/
|
|
1284
|
+
getBoundingClientRect() {
|
|
1285
|
+
const channel = this._ownerDocument?._syncChannel;
|
|
1286
|
+
if (channel) {
|
|
1287
|
+
const result = channel.request(require_sync_channel.QueryType.BoundingRect, JSON.stringify({ nodeId: this._nodeId }));
|
|
1288
|
+
if (result && typeof result === "object") {
|
|
1289
|
+
const r = result;
|
|
1290
|
+
return {
|
|
1291
|
+
top: r.top ?? 0,
|
|
1292
|
+
left: r.left ?? 0,
|
|
1293
|
+
right: r.right ?? 0,
|
|
1294
|
+
bottom: r.bottom ?? 0,
|
|
1295
|
+
width: r.width ?? 0,
|
|
1296
|
+
height: r.height ?? 0,
|
|
1297
|
+
x: r.x ?? r.left ?? 0,
|
|
1298
|
+
y: r.y ?? r.top ?? 0
|
|
1299
|
+
};
|
|
1300
|
+
}
|
|
1301
|
+
}
|
|
1302
|
+
return {
|
|
1303
|
+
top: 0,
|
|
1304
|
+
left: 0,
|
|
1305
|
+
right: 0,
|
|
1306
|
+
bottom: 0,
|
|
1307
|
+
width: 0,
|
|
1308
|
+
height: 0,
|
|
1309
|
+
x: 0,
|
|
1310
|
+
y: 0
|
|
1311
|
+
};
|
|
1312
|
+
}
|
|
1313
|
+
/** Parse a CSS style string (e.g., "color: red; font-size: 14px") and set each property individually. */
|
|
1314
|
+
_parseAndSetStyles(value) {
|
|
1315
|
+
for (const part of value.split(";")) {
|
|
1316
|
+
const colonIdx = part.indexOf(":");
|
|
1317
|
+
if (colonIdx === -1) continue;
|
|
1318
|
+
const key = part.slice(0, colonIdx).trim();
|
|
1319
|
+
const val = part.slice(colonIdx + 1).trim();
|
|
1320
|
+
if (key) this.style[key] = val;
|
|
1321
|
+
}
|
|
1322
|
+
}
|
|
1323
|
+
};
|
|
1324
|
+
/**
|
|
1325
|
+
* Virtual text node (nodeType 3). Setting nodeValue emits a setProperty mutation
|
|
1326
|
+
* so the main thread updates the real text node.
|
|
1327
|
+
*/
|
|
1328
|
+
var VirtualTextNode = class VirtualTextNode {
|
|
1329
|
+
static ELEMENT_NODE = 1;
|
|
1330
|
+
static TEXT_NODE = 3;
|
|
1331
|
+
static COMMENT_NODE = 8;
|
|
1332
|
+
static DOCUMENT_NODE = 9;
|
|
1333
|
+
static DOCUMENT_FRAGMENT_NODE = 11;
|
|
1334
|
+
nodeType = 3;
|
|
1335
|
+
nodeName = "#text";
|
|
1336
|
+
parentNode = null;
|
|
1337
|
+
_ownerDocument = null;
|
|
1338
|
+
_nodeValue;
|
|
1339
|
+
constructor(text, _nodeId, collector) {
|
|
1340
|
+
this._nodeId = _nodeId;
|
|
1341
|
+
this.collector = collector;
|
|
1342
|
+
this._nodeValue = text;
|
|
1343
|
+
}
|
|
1344
|
+
get parentElement() {
|
|
1345
|
+
return this.parentNode;
|
|
1346
|
+
}
|
|
1347
|
+
get nodeValue() {
|
|
1348
|
+
return this._nodeValue;
|
|
1349
|
+
}
|
|
1350
|
+
set nodeValue(value) {
|
|
1351
|
+
this._nodeValue = value;
|
|
1352
|
+
const mutation = {
|
|
1353
|
+
action: "setProperty",
|
|
1354
|
+
id: this._nodeId,
|
|
1355
|
+
property: "nodeValue",
|
|
1356
|
+
value
|
|
1357
|
+
};
|
|
1358
|
+
this.collector.add(mutation);
|
|
1359
|
+
}
|
|
1360
|
+
get textContent() {
|
|
1361
|
+
return this._nodeValue;
|
|
1362
|
+
}
|
|
1363
|
+
set textContent(value) {
|
|
1364
|
+
this.nodeValue = value;
|
|
1365
|
+
}
|
|
1366
|
+
get nextSibling() {
|
|
1367
|
+
if (!this.parentNode) return null;
|
|
1368
|
+
const siblings = this.parentNode.childNodes;
|
|
1369
|
+
return siblings[siblings.indexOf(this) + 1] ?? null;
|
|
1370
|
+
}
|
|
1371
|
+
get previousSibling() {
|
|
1372
|
+
if (!this.parentNode) return null;
|
|
1373
|
+
const siblings = this.parentNode.childNodes;
|
|
1374
|
+
return siblings[siblings.indexOf(this) - 1] ?? null;
|
|
1375
|
+
}
|
|
1376
|
+
get childNodes() {
|
|
1377
|
+
return [];
|
|
1378
|
+
}
|
|
1379
|
+
remove() {
|
|
1380
|
+
if (this.parentNode) this.parentNode.childNodes = this.parentNode.childNodes.filter((c) => c !== this);
|
|
1381
|
+
this.parentNode = null;
|
|
1382
|
+
const mutation = {
|
|
1383
|
+
action: "removeNode",
|
|
1384
|
+
id: this._nodeId
|
|
1385
|
+
};
|
|
1386
|
+
this.collector.add(mutation);
|
|
1387
|
+
}
|
|
1388
|
+
cloneNode(_deep) {
|
|
1389
|
+
const id = require_sync_channel.createNodeId();
|
|
1390
|
+
const clone = new VirtualTextNode(this._nodeValue, id, this.collector);
|
|
1391
|
+
clone._ownerDocument = this._ownerDocument;
|
|
1392
|
+
const mutation = {
|
|
1393
|
+
action: "createNode",
|
|
1394
|
+
id,
|
|
1395
|
+
tag: "#text",
|
|
1396
|
+
textContent: this._nodeValue
|
|
1397
|
+
};
|
|
1398
|
+
this.collector.add(mutation);
|
|
1399
|
+
return clone;
|
|
1400
|
+
}
|
|
1401
|
+
};
|
|
1402
|
+
/**
|
|
1403
|
+
* Virtual comment node (nodeType 8). Used primarily as DOM markers by frameworks.
|
|
1404
|
+
* Comment nodes do not emit mutations on value changes.
|
|
1405
|
+
*/
|
|
1406
|
+
var VirtualCommentNode = class VirtualCommentNode {
|
|
1407
|
+
static ELEMENT_NODE = 1;
|
|
1408
|
+
static TEXT_NODE = 3;
|
|
1409
|
+
static COMMENT_NODE = 8;
|
|
1410
|
+
static DOCUMENT_NODE = 9;
|
|
1411
|
+
static DOCUMENT_FRAGMENT_NODE = 11;
|
|
1412
|
+
nodeType = 8;
|
|
1413
|
+
nodeName = "#comment";
|
|
1414
|
+
parentNode = null;
|
|
1415
|
+
_ownerDocument = null;
|
|
1416
|
+
_nodeValue;
|
|
1417
|
+
constructor(text, _nodeId, collector) {
|
|
1418
|
+
this._nodeId = _nodeId;
|
|
1419
|
+
this.collector = collector;
|
|
1420
|
+
this._nodeValue = text;
|
|
1421
|
+
}
|
|
1422
|
+
get parentElement() {
|
|
1423
|
+
return this.parentNode;
|
|
1424
|
+
}
|
|
1425
|
+
get nodeValue() {
|
|
1426
|
+
return this._nodeValue;
|
|
1427
|
+
}
|
|
1428
|
+
set nodeValue(value) {
|
|
1429
|
+
this._nodeValue = value;
|
|
1430
|
+
}
|
|
1431
|
+
get textContent() {
|
|
1432
|
+
return this._nodeValue;
|
|
1433
|
+
}
|
|
1434
|
+
get nextSibling() {
|
|
1435
|
+
if (!this.parentNode) return null;
|
|
1436
|
+
const siblings = this.parentNode.childNodes;
|
|
1437
|
+
return siblings[siblings.indexOf(this) + 1] ?? null;
|
|
1438
|
+
}
|
|
1439
|
+
get previousSibling() {
|
|
1440
|
+
if (!this.parentNode) return null;
|
|
1441
|
+
const siblings = this.parentNode.childNodes;
|
|
1442
|
+
return siblings[siblings.indexOf(this) - 1] ?? null;
|
|
1443
|
+
}
|
|
1444
|
+
get childNodes() {
|
|
1445
|
+
return [];
|
|
1446
|
+
}
|
|
1447
|
+
remove() {
|
|
1448
|
+
if (this.parentNode) this.parentNode.childNodes = this.parentNode.childNodes.filter((c) => c !== this);
|
|
1449
|
+
this.parentNode = null;
|
|
1450
|
+
const mutation = {
|
|
1451
|
+
action: "removeNode",
|
|
1452
|
+
id: this._nodeId
|
|
1453
|
+
};
|
|
1454
|
+
this.collector.add(mutation);
|
|
1455
|
+
}
|
|
1456
|
+
cloneNode(_deep) {
|
|
1457
|
+
const id = require_sync_channel.createNodeId();
|
|
1458
|
+
const clone = new VirtualCommentNode(this._nodeValue, id, this.collector);
|
|
1459
|
+
clone._ownerDocument = this._ownerDocument;
|
|
1460
|
+
const mutation = {
|
|
1461
|
+
action: "createComment",
|
|
1462
|
+
id,
|
|
1463
|
+
textContent: this._nodeValue
|
|
1464
|
+
};
|
|
1465
|
+
this.collector.add(mutation);
|
|
1466
|
+
return clone;
|
|
1467
|
+
}
|
|
1468
|
+
};
|
|
1469
|
+
/** DOMTokenList-compatible classList implementation that delegates to VirtualElement.className. */
|
|
1470
|
+
var VirtualClassList = class {
|
|
1471
|
+
constructor(element) {
|
|
1472
|
+
this.element = element;
|
|
1473
|
+
}
|
|
1474
|
+
add(...names) {
|
|
1475
|
+
const classes = this.element.className.split(" ").filter(Boolean);
|
|
1476
|
+
for (const name of names) if (!classes.includes(name)) classes.push(name);
|
|
1477
|
+
this.element.className = classes.join(" ");
|
|
1478
|
+
}
|
|
1479
|
+
remove(...names) {
|
|
1480
|
+
const nameSet = new Set(names);
|
|
1481
|
+
const classes = this.element.className.split(" ").filter((c) => c !== "" && !nameSet.has(c));
|
|
1482
|
+
this.element.className = classes.join(" ");
|
|
1483
|
+
}
|
|
1484
|
+
contains(name) {
|
|
1485
|
+
return this.element.className.split(" ").includes(name);
|
|
1486
|
+
}
|
|
1487
|
+
toggle(name, force) {
|
|
1488
|
+
const has = this.contains(name);
|
|
1489
|
+
if (force !== void 0) {
|
|
1490
|
+
if (force && !has) this.add(name);
|
|
1491
|
+
else if (!force && has) this.remove(name);
|
|
1492
|
+
return force;
|
|
1493
|
+
}
|
|
1494
|
+
if (has) {
|
|
1495
|
+
this.remove(name);
|
|
1496
|
+
return false;
|
|
1497
|
+
}
|
|
1498
|
+
this.add(name);
|
|
1499
|
+
return true;
|
|
1500
|
+
}
|
|
1501
|
+
get length() {
|
|
1502
|
+
return this.element.className.split(" ").filter(Boolean).length;
|
|
1503
|
+
}
|
|
1504
|
+
};
|
|
1505
|
+
//#endregion
|
|
1506
|
+
//#region src/worker-thread/events.ts
|
|
1507
|
+
/**
|
|
1508
|
+
* Virtual event classes that simulate DOM event behavior
|
|
1509
|
+
* including bubbling, propagation control, and default prevention.
|
|
1510
|
+
*/
|
|
1511
|
+
var VirtualEvent = class {
|
|
1512
|
+
type;
|
|
1513
|
+
target;
|
|
1514
|
+
currentTarget;
|
|
1515
|
+
bubbles;
|
|
1516
|
+
cancelable;
|
|
1517
|
+
defaultPrevented = false;
|
|
1518
|
+
timeStamp;
|
|
1519
|
+
isTrusted;
|
|
1520
|
+
eventPhase = 0;
|
|
1521
|
+
_stopPropagation = false;
|
|
1522
|
+
_stopImmediatePropagation = false;
|
|
1523
|
+
constructor(type, init) {
|
|
1524
|
+
this.type = type;
|
|
1525
|
+
this.target = init?.target ?? null;
|
|
1526
|
+
this.currentTarget = init?.currentTarget ?? null;
|
|
1527
|
+
this.bubbles = init?.bubbles ?? false;
|
|
1528
|
+
this.cancelable = init?.cancelable ?? true;
|
|
1529
|
+
this.timeStamp = init?.timeStamp ?? Date.now();
|
|
1530
|
+
this.isTrusted = init?.isTrusted ?? false;
|
|
1531
|
+
if (init) {
|
|
1532
|
+
for (const key of Object.keys(init)) if (!(key in this)) this[key] = init[key];
|
|
1533
|
+
}
|
|
1534
|
+
}
|
|
1535
|
+
preventDefault() {
|
|
1536
|
+
if (this.cancelable) this.defaultPrevented = true;
|
|
1537
|
+
}
|
|
1538
|
+
stopPropagation() {
|
|
1539
|
+
this._stopPropagation = true;
|
|
1540
|
+
}
|
|
1541
|
+
stopImmediatePropagation() {
|
|
1542
|
+
this._stopImmediatePropagation = true;
|
|
1543
|
+
this._stopPropagation = true;
|
|
1544
|
+
}
|
|
1545
|
+
get propagationStopped() {
|
|
1546
|
+
return this._stopPropagation;
|
|
1547
|
+
}
|
|
1548
|
+
get immediatePropagationStopped() {
|
|
1549
|
+
return this._stopImmediatePropagation;
|
|
1550
|
+
}
|
|
1551
|
+
};
|
|
1552
|
+
var VirtualCustomEvent = class extends VirtualEvent {
|
|
1553
|
+
detail;
|
|
1554
|
+
constructor(type, init) {
|
|
1555
|
+
super(type, init);
|
|
1556
|
+
this.detail = init?.detail ?? null;
|
|
1557
|
+
}
|
|
1558
|
+
};
|
|
1559
|
+
//#endregion
|
|
1560
|
+
//#region src/worker-thread/mutation-collector.ts
|
|
1561
|
+
const MAX_COALESCED_LOG = 50;
|
|
1562
|
+
/**
|
|
1563
|
+
* Collects DOM mutations during synchronous execution and flushes them
|
|
1564
|
+
* as a batched message at the end of the current microtask.
|
|
1565
|
+
*/
|
|
1566
|
+
var MutationCollector = class {
|
|
1567
|
+
queue = [];
|
|
1568
|
+
scheduled = false;
|
|
1569
|
+
uidCounter = 0;
|
|
1570
|
+
transport = null;
|
|
1571
|
+
_coalesceEnabled = true;
|
|
1572
|
+
_stats = {
|
|
1573
|
+
added: 0,
|
|
1574
|
+
coalesced: 0,
|
|
1575
|
+
flushed: 0
|
|
1576
|
+
};
|
|
1577
|
+
_coalescedLog = [];
|
|
1578
|
+
_perTypeCoalesced = /* @__PURE__ */ new Map();
|
|
1579
|
+
/** Total mutations added (monotonically increasing counter for diff-based tracking). */
|
|
1580
|
+
get totalAdded() {
|
|
1581
|
+
return this._stats.added;
|
|
1582
|
+
}
|
|
1583
|
+
/** Feature 15: Current causal event tag for this flush cycle */
|
|
1584
|
+
_causalEvent = null;
|
|
1585
|
+
getStats() {
|
|
1586
|
+
return { ...this._stats };
|
|
1587
|
+
}
|
|
1588
|
+
getCoalescedLog() {
|
|
1589
|
+
return this._coalescedLog.slice();
|
|
1590
|
+
}
|
|
1591
|
+
getPerTypeCoalesced() {
|
|
1592
|
+
const result = {};
|
|
1593
|
+
for (const [action, counts] of this._perTypeCoalesced) result[action] = { ...counts };
|
|
1594
|
+
return result;
|
|
1595
|
+
}
|
|
1596
|
+
constructor(appId) {
|
|
1597
|
+
this.appId = appId;
|
|
1598
|
+
}
|
|
1599
|
+
/** Feature 15: Set the causal event for the current mutation cycle. */
|
|
1600
|
+
setCausalEvent(event) {
|
|
1601
|
+
this._causalEvent = event;
|
|
1602
|
+
}
|
|
1603
|
+
/** Feature 15: Get current causal event. */
|
|
1604
|
+
getCausalEvent() {
|
|
1605
|
+
return this._causalEvent;
|
|
1606
|
+
}
|
|
1607
|
+
enableCoalescing(enabled) {
|
|
1608
|
+
this._coalesceEnabled = enabled;
|
|
1609
|
+
}
|
|
1610
|
+
setTransport(transport) {
|
|
1611
|
+
this.transport = transport;
|
|
1612
|
+
}
|
|
1613
|
+
add(mutation) {
|
|
1614
|
+
this._stats.added++;
|
|
1615
|
+
const counts = this._perTypeCoalesced.get(mutation.action);
|
|
1616
|
+
if (counts) counts.added++;
|
|
1617
|
+
else this._perTypeCoalesced.set(mutation.action, {
|
|
1618
|
+
added: 1,
|
|
1619
|
+
coalesced: 0
|
|
1620
|
+
});
|
|
1621
|
+
this.queue.push(mutation);
|
|
1622
|
+
if (!this.scheduled) {
|
|
1623
|
+
this.scheduled = true;
|
|
1624
|
+
queueMicrotask(() => this.flush());
|
|
1625
|
+
}
|
|
1626
|
+
}
|
|
1627
|
+
coalesce(mutations) {
|
|
1628
|
+
if (mutations.length <= 1) return mutations;
|
|
1629
|
+
const lastIndex = /* @__PURE__ */ new Map();
|
|
1630
|
+
const toRemove = /* @__PURE__ */ new Set();
|
|
1631
|
+
const createdAt = /* @__PURE__ */ new Map();
|
|
1632
|
+
const attachedIds = /* @__PURE__ */ new Set();
|
|
1633
|
+
const eliminatedIds = /* @__PURE__ */ new Set();
|
|
1634
|
+
for (let i = 0; i < mutations.length; i++) {
|
|
1635
|
+
const m = mutations[i];
|
|
1636
|
+
let key = null;
|
|
1637
|
+
switch (m.action) {
|
|
1638
|
+
case "setStyle":
|
|
1639
|
+
key = `setStyle:${m.id}:${m.property}`;
|
|
1640
|
+
break;
|
|
1641
|
+
case "setAttribute":
|
|
1642
|
+
key = `setAttribute:${m.id}:${m.name}`;
|
|
1643
|
+
break;
|
|
1644
|
+
case "setClassName":
|
|
1645
|
+
key = `setClassName:${m.id}`;
|
|
1646
|
+
break;
|
|
1647
|
+
case "setTextContent":
|
|
1648
|
+
key = `setTextContent:${m.id}`;
|
|
1649
|
+
break;
|
|
1650
|
+
case "setProperty":
|
|
1651
|
+
key = `setProperty:${m.id}:${m.property}`;
|
|
1652
|
+
break;
|
|
1653
|
+
case "setHTML":
|
|
1654
|
+
key = `setHTML:${m.id}`;
|
|
1655
|
+
break;
|
|
1656
|
+
case "createNode":
|
|
1657
|
+
createdAt.set(m.id, i);
|
|
1658
|
+
break;
|
|
1659
|
+
case "appendChild":
|
|
1660
|
+
attachedIds.add(m.childId);
|
|
1661
|
+
break;
|
|
1662
|
+
case "insertBefore":
|
|
1663
|
+
attachedIds.add(m.newId);
|
|
1664
|
+
break;
|
|
1665
|
+
case "removeNode":
|
|
1666
|
+
if (createdAt.has(m.id) && !attachedIds.has(m.id)) {
|
|
1667
|
+
const createdIdx = createdAt.get(m.id);
|
|
1668
|
+
if (createdIdx !== void 0) toRemove.add(createdIdx);
|
|
1669
|
+
toRemove.add(i);
|
|
1670
|
+
createdAt.delete(m.id);
|
|
1671
|
+
eliminatedIds.add(m.id);
|
|
1672
|
+
}
|
|
1673
|
+
break;
|
|
1674
|
+
}
|
|
1675
|
+
if (key !== null) {
|
|
1676
|
+
const prev = lastIndex.get(key);
|
|
1677
|
+
if (prev !== void 0) toRemove.add(prev);
|
|
1678
|
+
lastIndex.set(key, i);
|
|
1679
|
+
}
|
|
1680
|
+
}
|
|
1681
|
+
if (eliminatedIds.size > 0) for (let j = 0; j < mutations.length; j++) {
|
|
1682
|
+
if (toRemove.has(j)) continue;
|
|
1683
|
+
const mut = mutations[j];
|
|
1684
|
+
if ("id" in mut && eliminatedIds.has(mut.id)) toRemove.add(j);
|
|
1685
|
+
}
|
|
1686
|
+
if (toRemove.size > 0) {
|
|
1687
|
+
const now = Date.now();
|
|
1688
|
+
for (const idx of toRemove) {
|
|
1689
|
+
const removed = mutations[idx];
|
|
1690
|
+
const action = removed.action;
|
|
1691
|
+
const entry = {
|
|
1692
|
+
action,
|
|
1693
|
+
key: this._buildKey(removed),
|
|
1694
|
+
timestamp: now
|
|
1695
|
+
};
|
|
1696
|
+
this._coalescedLog.push(entry);
|
|
1697
|
+
if (this._coalescedLog.length > MAX_COALESCED_LOG) this._coalescedLog.shift();
|
|
1698
|
+
const counts = this._perTypeCoalesced.get(action);
|
|
1699
|
+
if (counts) counts.coalesced++;
|
|
1700
|
+
}
|
|
1701
|
+
}
|
|
1702
|
+
if (toRemove.size === 0) return mutations;
|
|
1703
|
+
return mutations.filter((_, i) => !toRemove.has(i));
|
|
1704
|
+
}
|
|
1705
|
+
_buildKey(m) {
|
|
1706
|
+
switch (m.action) {
|
|
1707
|
+
case "setStyle": return `setStyle:${m.id}:${m.property}`;
|
|
1708
|
+
case "setAttribute": return `setAttribute:${m.id}:${m.name}`;
|
|
1709
|
+
case "setClassName": return `setClassName:${m.id}`;
|
|
1710
|
+
case "setTextContent": return `setTextContent:${m.id}`;
|
|
1711
|
+
case "setProperty": return `setProperty:${m.id}:${m.property}`;
|
|
1712
|
+
case "setHTML": return `setHTML:${m.id}`;
|
|
1713
|
+
default: return `${m.action}:${"id" in m ? m.id : "?"}`;
|
|
1714
|
+
}
|
|
1715
|
+
}
|
|
1716
|
+
flush() {
|
|
1717
|
+
if (this.queue.length === 0) {
|
|
1718
|
+
this.scheduled = false;
|
|
1719
|
+
return;
|
|
1720
|
+
}
|
|
1721
|
+
const perfMarkName = `async-dom:flush:${this.appId}`;
|
|
1722
|
+
if (typeof performance !== "undefined" && performance.mark) performance.mark(`${perfMarkName}:start`);
|
|
1723
|
+
const rawLength = this.queue.length;
|
|
1724
|
+
const batch = this._coalesceEnabled ? this.coalesce(this.queue.splice(0)) : this.queue.splice(0);
|
|
1725
|
+
this.scheduled = false;
|
|
1726
|
+
this._stats.coalesced += rawLength - batch.length;
|
|
1727
|
+
this._stats.flushed += batch.length;
|
|
1728
|
+
if (batch.length === 0) {
|
|
1729
|
+
this._causalEvent = null;
|
|
1730
|
+
return;
|
|
1731
|
+
}
|
|
1732
|
+
this.uidCounter++;
|
|
1733
|
+
if (this.transport?.readyState !== "open") {
|
|
1734
|
+
this._causalEvent = null;
|
|
1735
|
+
return;
|
|
1736
|
+
}
|
|
1737
|
+
const message = {
|
|
1738
|
+
type: "mutation",
|
|
1739
|
+
appId: this.appId,
|
|
1740
|
+
uid: this.uidCounter,
|
|
1741
|
+
mutations: batch,
|
|
1742
|
+
sentAt: Date.now()
|
|
1743
|
+
};
|
|
1744
|
+
if (this._causalEvent) {
|
|
1745
|
+
message.causalEvent = this._causalEvent;
|
|
1746
|
+
this._causalEvent = null;
|
|
1747
|
+
}
|
|
1748
|
+
this.transport.send(message);
|
|
1749
|
+
if (typeof performance !== "undefined" && performance.mark && performance.measure) {
|
|
1750
|
+
performance.mark(`${perfMarkName}:end`);
|
|
1751
|
+
try {
|
|
1752
|
+
performance.measure(perfMarkName, `${perfMarkName}:start`, `${perfMarkName}:end`);
|
|
1753
|
+
} catch {}
|
|
1754
|
+
}
|
|
1755
|
+
}
|
|
1756
|
+
/** Force-flush all pending mutations immediately */
|
|
1757
|
+
flushSync() {
|
|
1758
|
+
this.flush();
|
|
1759
|
+
}
|
|
1760
|
+
/** Get number of pending mutations (useful for testing) */
|
|
1761
|
+
get pendingCount() {
|
|
1762
|
+
return this.queue.length;
|
|
1763
|
+
}
|
|
1764
|
+
};
|
|
1765
|
+
//#endregion
|
|
1766
|
+
//#region src/worker-thread/document.ts
|
|
1767
|
+
/**
|
|
1768
|
+
* Virtual Document that exists in a worker thread.
|
|
1769
|
+
*
|
|
1770
|
+
* Mirrors a subset of the browser's Document API, allowing framework code
|
|
1771
|
+
* to call createElement, querySelector, addEventListener, etc. without
|
|
1772
|
+
* touching the real DOM. Every mutating operation records a DomMutation via
|
|
1773
|
+
* the MutationCollector, which batches and sends them to the main thread.
|
|
1774
|
+
*
|
|
1775
|
+
* Key subsystems:
|
|
1776
|
+
* - Element creation: createElement, createTextNode, createComment
|
|
1777
|
+
* - Event dispatch: routes serialized events from the main thread to the
|
|
1778
|
+
* correct VirtualElement listener using O(1) listenerId lookup
|
|
1779
|
+
* - ID registries: maintains both user-visible id attributes (_ids) and
|
|
1780
|
+
* internal NodeId -> VirtualElement mappings (_nodeIdToElement)
|
|
1781
|
+
* - Sync channel: optional SharedArrayBuffer channel for blocking DOM queries
|
|
1782
|
+
*/
|
|
1783
|
+
var VirtualDocument = class {
|
|
1784
|
+
body;
|
|
1785
|
+
head;
|
|
1786
|
+
documentElement;
|
|
1787
|
+
nodeType = 9;
|
|
1788
|
+
nodeName = "#document";
|
|
1789
|
+
/** Collects mutations and batches them for transport to the main thread. */
|
|
1790
|
+
collector;
|
|
1791
|
+
/** Reference to the VirtualWindow, providing location/navigator access. */
|
|
1792
|
+
_defaultView = null;
|
|
1793
|
+
/** Optional sync channel for blocking DOM queries (e.g., getBoundingClientRect). */
|
|
1794
|
+
_syncChannel = null;
|
|
1795
|
+
_title = "";
|
|
1796
|
+
_cookie = "";
|
|
1797
|
+
/** Map of user-visible id attribute -> VirtualElement for getElementById. */
|
|
1798
|
+
_ids = /* @__PURE__ */ new Map();
|
|
1799
|
+
/** Map of internal NodeId -> VirtualElement for event target resolution. */
|
|
1800
|
+
_nodeIdToElement = /* @__PURE__ */ new Map();
|
|
1801
|
+
/** Document-level event listeners keyed by listenerId. */
|
|
1802
|
+
_listenerMap = /* @__PURE__ */ new Map();
|
|
1803
|
+
/** Map of listenerId -> owning VirtualElement for O(1) event dispatch routing. */
|
|
1804
|
+
_listenerToElement = /* @__PURE__ */ new Map();
|
|
1805
|
+
_listenerCounter = 0;
|
|
1806
|
+
constructor(appId) {
|
|
1807
|
+
this.collector = new MutationCollector(appId);
|
|
1808
|
+
this.documentElement = new VirtualElement("HTML", this.collector, 3);
|
|
1809
|
+
this.head = new VirtualElement("HEAD", this.collector, 2);
|
|
1810
|
+
this.body = new VirtualElement("BODY", this.collector, 1);
|
|
1811
|
+
this.documentElement._ownerDocument = this;
|
|
1812
|
+
this.head._ownerDocument = this;
|
|
1813
|
+
this.body._ownerDocument = this;
|
|
1814
|
+
this._nodeIdToElement.set(3, this.documentElement);
|
|
1815
|
+
this._nodeIdToElement.set(2, this.head);
|
|
1816
|
+
this._nodeIdToElement.set(1, this.body);
|
|
1817
|
+
this.documentElement.appendChild(this.head);
|
|
1818
|
+
this.documentElement.appendChild(this.body);
|
|
1819
|
+
this.collector.flush();
|
|
1820
|
+
}
|
|
1821
|
+
/**
|
|
1822
|
+
* Create a new virtual element and emit a createNode mutation.
|
|
1823
|
+
* The element is registered by NodeId for event target resolution.
|
|
1824
|
+
*/
|
|
1825
|
+
createElement(tag) {
|
|
1826
|
+
const id = require_sync_channel.createNodeId();
|
|
1827
|
+
const element = new VirtualElement(tag, this.collector, id);
|
|
1828
|
+
element._ownerDocument = this;
|
|
1829
|
+
this._nodeIdToElement.set(id, element);
|
|
1830
|
+
const mutation = {
|
|
1831
|
+
action: "createNode",
|
|
1832
|
+
id,
|
|
1833
|
+
tag: element.tagName,
|
|
1834
|
+
textContent: ""
|
|
1835
|
+
};
|
|
1836
|
+
this.collector.add(mutation);
|
|
1837
|
+
return element;
|
|
1838
|
+
}
|
|
1839
|
+
/** Create a namespaced element (e.g., SVG). Delegates to createElement then sets the namespace. */
|
|
1840
|
+
createElementNS(ns, tag) {
|
|
1841
|
+
const el = this.createElement(tag);
|
|
1842
|
+
el._setNamespaceURI(ns);
|
|
1843
|
+
return el;
|
|
1844
|
+
}
|
|
1845
|
+
/** Create a virtual text node and emit a createNode mutation with tag "#text". */
|
|
1846
|
+
createTextNode(text) {
|
|
1847
|
+
const id = require_sync_channel.createNodeId();
|
|
1848
|
+
const node = new VirtualTextNode(text, id, this.collector);
|
|
1849
|
+
node._ownerDocument = this;
|
|
1850
|
+
const mutation = {
|
|
1851
|
+
action: "createNode",
|
|
1852
|
+
id,
|
|
1853
|
+
tag: "#text",
|
|
1854
|
+
textContent: text
|
|
1855
|
+
};
|
|
1856
|
+
this.collector.add(mutation);
|
|
1857
|
+
return node;
|
|
1858
|
+
}
|
|
1859
|
+
/** Create a virtual comment node and emit a createComment mutation. */
|
|
1860
|
+
createComment(text) {
|
|
1861
|
+
const id = require_sync_channel.createNodeId();
|
|
1862
|
+
const node = new VirtualCommentNode(text, id, this.collector);
|
|
1863
|
+
node._ownerDocument = this;
|
|
1864
|
+
const mutation = {
|
|
1865
|
+
action: "createComment",
|
|
1866
|
+
id,
|
|
1867
|
+
textContent: text
|
|
1868
|
+
};
|
|
1869
|
+
this.collector.add(mutation);
|
|
1870
|
+
return node;
|
|
1871
|
+
}
|
|
1872
|
+
createDocumentFragment() {
|
|
1873
|
+
const frag = new VirtualElement("#document-fragment", this.collector, require_sync_channel.createNodeId());
|
|
1874
|
+
frag._ownerDocument = this;
|
|
1875
|
+
return frag;
|
|
1876
|
+
}
|
|
1877
|
+
getElementById(id) {
|
|
1878
|
+
return this._ids.get(id) ?? null;
|
|
1879
|
+
}
|
|
1880
|
+
/**
|
|
1881
|
+
* Add a document-level event listener. The listener is stored locally and
|
|
1882
|
+
* a mutation is emitted to tell the main thread to attach a real listener.
|
|
1883
|
+
*/
|
|
1884
|
+
addEventListener(name, callback) {
|
|
1885
|
+
if (!name) return;
|
|
1886
|
+
const listenerId = `document_${name}_${++this._listenerCounter}`;
|
|
1887
|
+
this._listenerMap.set(listenerId, callback);
|
|
1888
|
+
const mutation = {
|
|
1889
|
+
action: "addEventListener",
|
|
1890
|
+
id: 4,
|
|
1891
|
+
name,
|
|
1892
|
+
listenerId
|
|
1893
|
+
};
|
|
1894
|
+
this.collector.add(mutation);
|
|
1895
|
+
}
|
|
1896
|
+
/** Remove a document-level listener by callback reference. Emits a removeEventListener mutation. */
|
|
1897
|
+
removeEventListener(_name, callback) {
|
|
1898
|
+
for (const [listenerId, cb] of this._listenerMap.entries()) if (cb === callback) {
|
|
1899
|
+
this._listenerMap.delete(listenerId);
|
|
1900
|
+
const mutation = {
|
|
1901
|
+
action: "removeEventListener",
|
|
1902
|
+
id: 4,
|
|
1903
|
+
listenerId
|
|
1904
|
+
};
|
|
1905
|
+
this.collector.add(mutation);
|
|
1906
|
+
break;
|
|
1907
|
+
}
|
|
1908
|
+
}
|
|
1909
|
+
/**
|
|
1910
|
+
* Route an event from the main thread to the appropriate listener.
|
|
1911
|
+
* Resolves serialized target IDs to actual VirtualElement references.
|
|
1912
|
+
*/
|
|
1913
|
+
_resolveTarget(value) {
|
|
1914
|
+
if (typeof value === "number") return this._nodeIdToElement.get(value) ?? null;
|
|
1915
|
+
if (typeof value === "string") {
|
|
1916
|
+
const num = Number(value);
|
|
1917
|
+
if (!Number.isNaN(num)) return this._nodeIdToElement.get(num) ?? null;
|
|
1918
|
+
return this._ids.get(value) ?? null;
|
|
1919
|
+
}
|
|
1920
|
+
return null;
|
|
1921
|
+
}
|
|
1922
|
+
/**
|
|
1923
|
+
* Route a serialized event from the main thread to the appropriate listener.
|
|
1924
|
+
*
|
|
1925
|
+
* Flow:
|
|
1926
|
+
* 1. Resolve serialized target/currentTarget/relatedTarget IDs to VirtualElement refs
|
|
1927
|
+
* 2. Wrap the event data in a VirtualEvent for bubbling support
|
|
1928
|
+
* 3. Tag the MutationCollector with causal event info for causality tracking
|
|
1929
|
+
* 4. Sync input/media state from the event to the target element
|
|
1930
|
+
* 5. Dispatch to document-level listeners, or bubble through the element tree
|
|
1931
|
+
*/
|
|
1932
|
+
dispatchEvent(listenerId, event) {
|
|
1933
|
+
const evt = event;
|
|
1934
|
+
if (evt.target != null && typeof evt.target !== "object") evt.target = this._resolveTarget(evt.target);
|
|
1935
|
+
if (evt.currentTarget != null && typeof evt.currentTarget !== "object") evt.currentTarget = this._resolveTarget(evt.currentTarget);
|
|
1936
|
+
if (evt.relatedTarget != null && typeof evt.relatedTarget !== "object") evt.relatedTarget = this._resolveTarget(evt.relatedTarget);
|
|
1937
|
+
const virtualEvent = new VirtualEvent(evt.type, evt);
|
|
1938
|
+
const eventType = evt.type ?? "unknown";
|
|
1939
|
+
this.collector.setCausalEvent({
|
|
1940
|
+
eventType,
|
|
1941
|
+
listenerId,
|
|
1942
|
+
timestamp: Date.now()
|
|
1943
|
+
});
|
|
1944
|
+
const perfMarkName = `async-dom:event:${eventType}:${listenerId}`;
|
|
1945
|
+
if (typeof performance !== "undefined" && performance.mark) performance.mark(`${perfMarkName}:start`);
|
|
1946
|
+
const targetEl = virtualEvent.target;
|
|
1947
|
+
if (targetEl && typeof targetEl === "object" && "_updateInputState" in targetEl) {
|
|
1948
|
+
const inputState = {};
|
|
1949
|
+
if (evt.value !== void 0) inputState.value = evt.value;
|
|
1950
|
+
if (evt.checked !== void 0) inputState.checked = evt.checked;
|
|
1951
|
+
if (evt.selectedIndex !== void 0) inputState.selectedIndex = evt.selectedIndex;
|
|
1952
|
+
if (Object.keys(inputState).length > 0) targetEl._updateInputState(inputState);
|
|
1953
|
+
}
|
|
1954
|
+
if (targetEl && typeof targetEl === "object") {
|
|
1955
|
+
const mediaState = {};
|
|
1956
|
+
if (evt.currentTime !== void 0) mediaState.currentTime = evt.currentTime;
|
|
1957
|
+
if (evt.duration !== void 0) mediaState.duration = evt.duration;
|
|
1958
|
+
if (evt.paused !== void 0) mediaState.paused = evt.paused;
|
|
1959
|
+
if (evt.ended !== void 0) mediaState.ended = evt.ended;
|
|
1960
|
+
if (evt.readyState !== void 0) mediaState.readyState = evt.readyState;
|
|
1961
|
+
if (Object.keys(mediaState).length > 0 && "_updateMediaState" in targetEl) targetEl._updateMediaState(mediaState);
|
|
1962
|
+
}
|
|
1963
|
+
const docListener = this._listenerMap.get(listenerId);
|
|
1964
|
+
if (docListener) {
|
|
1965
|
+
docListener(virtualEvent);
|
|
1966
|
+
this._finishEventPerf(perfMarkName);
|
|
1967
|
+
return;
|
|
1968
|
+
}
|
|
1969
|
+
const targetElement = this._listenerToElement.get(listenerId) ?? null;
|
|
1970
|
+
if (targetElement) {
|
|
1971
|
+
virtualEvent.currentTarget = targetElement;
|
|
1972
|
+
targetElement._dispatchBubbledEvent(virtualEvent);
|
|
1973
|
+
if (virtualEvent.bubbles && !virtualEvent.propagationStopped) {
|
|
1974
|
+
let current = targetElement.parentNode;
|
|
1975
|
+
while (current && !virtualEvent.propagationStopped) {
|
|
1976
|
+
virtualEvent.currentTarget = current;
|
|
1977
|
+
current._dispatchBubbledEvent(virtualEvent);
|
|
1978
|
+
if (virtualEvent.propagationStopped) break;
|
|
1979
|
+
current = current.parentNode;
|
|
1980
|
+
}
|
|
1981
|
+
}
|
|
1982
|
+
}
|
|
1983
|
+
this._finishEventPerf(perfMarkName);
|
|
1984
|
+
}
|
|
1985
|
+
/** Feature 16: finish performance measurement for an event dispatch */
|
|
1986
|
+
_finishEventPerf(perfMarkName) {
|
|
1987
|
+
if (typeof performance !== "undefined" && performance.mark && performance.measure) {
|
|
1988
|
+
performance.mark(`${perfMarkName}:end`);
|
|
1989
|
+
try {
|
|
1990
|
+
performance.measure(perfMarkName, `${perfMarkName}:start`, `${perfMarkName}:end`);
|
|
1991
|
+
} catch {}
|
|
1992
|
+
}
|
|
1993
|
+
}
|
|
1994
|
+
/**
|
|
1995
|
+
* Register an element by its internal NodeId.
|
|
1996
|
+
*/
|
|
1997
|
+
registerElement(id, element) {
|
|
1998
|
+
this._nodeIdToElement.set(id, element);
|
|
1999
|
+
}
|
|
2000
|
+
/**
|
|
2001
|
+
* Unregister an element by its internal NodeId (called during cleanup on removal).
|
|
2002
|
+
*/
|
|
2003
|
+
unregisterElement(id) {
|
|
2004
|
+
this._nodeIdToElement.delete(id);
|
|
2005
|
+
}
|
|
2006
|
+
/**
|
|
2007
|
+
* Register an element by its user-visible id attribute (distinct from internal NodeId).
|
|
2008
|
+
*/
|
|
2009
|
+
registerElementById(id, element) {
|
|
2010
|
+
this._ids.set(id, element);
|
|
2011
|
+
}
|
|
2012
|
+
/**
|
|
2013
|
+
* Unregister an element by its user-visible id attribute.
|
|
2014
|
+
*/
|
|
2015
|
+
unregisterElementById(id) {
|
|
2016
|
+
this._ids.delete(id);
|
|
2017
|
+
}
|
|
2018
|
+
/**
|
|
2019
|
+
* Register a listener ID to its owning element for O(1) event dispatch.
|
|
2020
|
+
*/
|
|
2021
|
+
registerListener(listenerId, element) {
|
|
2022
|
+
this._listenerToElement.set(listenerId, element);
|
|
2023
|
+
}
|
|
2024
|
+
/**
|
|
2025
|
+
* Unregister a listener ID (called on removeEventListener or element cleanup).
|
|
2026
|
+
*/
|
|
2027
|
+
unregisterListener(listenerId) {
|
|
2028
|
+
this._listenerToElement.delete(listenerId);
|
|
2029
|
+
}
|
|
2030
|
+
/** Stub implementation of document.createEvent for legacy API compatibility. */
|
|
2031
|
+
createEvent(_type) {
|
|
2032
|
+
return {
|
|
2033
|
+
type: "",
|
|
2034
|
+
initEvent(type, bubbles, cancelable) {
|
|
2035
|
+
this.type = type;
|
|
2036
|
+
this.bubbles = bubbles ?? false;
|
|
2037
|
+
this.cancelable = cancelable ?? false;
|
|
2038
|
+
},
|
|
2039
|
+
bubbles: false,
|
|
2040
|
+
cancelable: false,
|
|
2041
|
+
preventDefault() {},
|
|
2042
|
+
stopPropagation() {},
|
|
2043
|
+
stopImmediatePropagation() {}
|
|
2044
|
+
};
|
|
2045
|
+
}
|
|
2046
|
+
get activeElement() {
|
|
2047
|
+
return this.body;
|
|
2048
|
+
}
|
|
2049
|
+
/** Stub implementation of document.createRange for framework compatibility. */
|
|
2050
|
+
createRange() {
|
|
2051
|
+
const doc = this;
|
|
2052
|
+
return {
|
|
2053
|
+
createContextualFragment(_html) {
|
|
2054
|
+
return doc.createDocumentFragment();
|
|
2055
|
+
},
|
|
2056
|
+
setStart() {},
|
|
2057
|
+
setEnd() {},
|
|
2058
|
+
collapse() {},
|
|
2059
|
+
selectNodeContents() {},
|
|
2060
|
+
cloneRange() {
|
|
2061
|
+
return doc.createRange();
|
|
2062
|
+
}
|
|
2063
|
+
};
|
|
2064
|
+
}
|
|
2065
|
+
/** Simplified TreeWalker that pre-collects all descendant nodes for sequential traversal. */
|
|
2066
|
+
createTreeWalker(root, _whatToShow) {
|
|
2067
|
+
const nodes = [];
|
|
2068
|
+
function collect(node) {
|
|
2069
|
+
nodes.push(node);
|
|
2070
|
+
if (node instanceof VirtualElement) for (const child of node.childNodes) collect(child);
|
|
2071
|
+
}
|
|
2072
|
+
collect(root);
|
|
2073
|
+
let idx = 0;
|
|
2074
|
+
return {
|
|
2075
|
+
currentNode: root,
|
|
2076
|
+
nextNode() {
|
|
2077
|
+
idx++;
|
|
2078
|
+
if (idx < nodes.length) {
|
|
2079
|
+
this.currentNode = nodes[idx];
|
|
2080
|
+
return nodes[idx];
|
|
2081
|
+
}
|
|
2082
|
+
return null;
|
|
2083
|
+
}
|
|
2084
|
+
};
|
|
2085
|
+
}
|
|
2086
|
+
querySelector(selector) {
|
|
2087
|
+
if (selector.startsWith("#")) {
|
|
2088
|
+
const found = this.getElementById(selector.slice(1));
|
|
2089
|
+
if (found) return found;
|
|
2090
|
+
}
|
|
2091
|
+
return querySelector(this.body, selector) ?? querySelector(this.head, selector);
|
|
2092
|
+
}
|
|
2093
|
+
querySelectorAll(selector) {
|
|
2094
|
+
return [...querySelectorAll(this.head, selector), ...querySelectorAll(this.body, selector)];
|
|
2095
|
+
}
|
|
2096
|
+
getElementsByTagName(tagName) {
|
|
2097
|
+
const upper = tagName.toUpperCase();
|
|
2098
|
+
return this.querySelectorAll(upper === "*" ? "*" : tagName);
|
|
2099
|
+
}
|
|
2100
|
+
getElementsByClassName(className) {
|
|
2101
|
+
const selector = className.split(/\s+/).filter(Boolean).map((c) => `.${c}`).join("");
|
|
2102
|
+
return this.querySelectorAll(selector);
|
|
2103
|
+
}
|
|
2104
|
+
get title() {
|
|
2105
|
+
return this._title;
|
|
2106
|
+
}
|
|
2107
|
+
set title(value) {
|
|
2108
|
+
this._title = value;
|
|
2109
|
+
}
|
|
2110
|
+
get URL() {
|
|
2111
|
+
return this._defaultView?.location?.href ?? "";
|
|
2112
|
+
}
|
|
2113
|
+
get location() {
|
|
2114
|
+
return this._defaultView?.location ?? null;
|
|
2115
|
+
}
|
|
2116
|
+
get cookie() {
|
|
2117
|
+
return this._cookie;
|
|
2118
|
+
}
|
|
2119
|
+
set cookie(value) {
|
|
2120
|
+
this._cookie = value;
|
|
2121
|
+
}
|
|
2122
|
+
get readyState() {
|
|
2123
|
+
return "complete";
|
|
2124
|
+
}
|
|
2125
|
+
get compatMode() {
|
|
2126
|
+
return "CSS1Compat";
|
|
2127
|
+
}
|
|
2128
|
+
get characterSet() {
|
|
2129
|
+
return "UTF-8";
|
|
2130
|
+
}
|
|
2131
|
+
get contentType() {
|
|
2132
|
+
return "text/html";
|
|
2133
|
+
}
|
|
2134
|
+
get visibilityState() {
|
|
2135
|
+
return "visible";
|
|
2136
|
+
}
|
|
2137
|
+
get hidden() {
|
|
2138
|
+
return false;
|
|
2139
|
+
}
|
|
2140
|
+
get childNodes() {
|
|
2141
|
+
return [this.documentElement];
|
|
2142
|
+
}
|
|
2143
|
+
get children() {
|
|
2144
|
+
return [this.documentElement];
|
|
2145
|
+
}
|
|
2146
|
+
get firstChild() {
|
|
2147
|
+
return this.documentElement;
|
|
2148
|
+
}
|
|
2149
|
+
contains(node) {
|
|
2150
|
+
if (node === this) return true;
|
|
2151
|
+
return this.documentElement.contains(node);
|
|
2152
|
+
}
|
|
2153
|
+
get implementation() {
|
|
2154
|
+
return { hasFeature() {
|
|
2155
|
+
return false;
|
|
2156
|
+
} };
|
|
2157
|
+
}
|
|
2158
|
+
get defaultView() {
|
|
2159
|
+
return this._defaultView;
|
|
2160
|
+
}
|
|
2161
|
+
get ownerDocument() {
|
|
2162
|
+
return this;
|
|
2163
|
+
}
|
|
2164
|
+
/**
|
|
2165
|
+
* Clean up all internal state. Called when the worker DOM instance is being destroyed.
|
|
2166
|
+
* Clears element registries, listener maps, and resets counters.
|
|
2167
|
+
*/
|
|
2168
|
+
destroy() {
|
|
2169
|
+
this.collector.flushSync();
|
|
2170
|
+
this._ids.clear();
|
|
2171
|
+
this._nodeIdToElement.clear();
|
|
2172
|
+
this._listenerMap.clear();
|
|
2173
|
+
this._listenerToElement.clear();
|
|
2174
|
+
this._listenerCounter = 0;
|
|
2175
|
+
this._syncChannel = null;
|
|
2176
|
+
this._defaultView = null;
|
|
2177
|
+
}
|
|
2178
|
+
/** Serialize the entire virtual DOM tree to a JSON-compatible structure for debugging. */
|
|
2179
|
+
toJSON() {
|
|
2180
|
+
return this._serializeNode(this.documentElement);
|
|
2181
|
+
}
|
|
2182
|
+
_serializeNode(node) {
|
|
2183
|
+
if (node.nodeType === 3) return {
|
|
2184
|
+
type: "text",
|
|
2185
|
+
id: node._nodeId,
|
|
2186
|
+
text: node.nodeValue
|
|
2187
|
+
};
|
|
2188
|
+
if (node.nodeType === 8) return {
|
|
2189
|
+
type: "comment",
|
|
2190
|
+
id: node._nodeId,
|
|
2191
|
+
text: node.nodeValue
|
|
2192
|
+
};
|
|
2193
|
+
const el = node;
|
|
2194
|
+
const attrs = {};
|
|
2195
|
+
const a = el.attributes;
|
|
2196
|
+
for (let i = 0; i < a.length; i++) {
|
|
2197
|
+
const attr = a.item(i);
|
|
2198
|
+
if (attr) attrs[attr.name] = attr.value;
|
|
2199
|
+
}
|
|
2200
|
+
return {
|
|
2201
|
+
type: "element",
|
|
2202
|
+
id: el._nodeId,
|
|
2203
|
+
tag: el.tagName,
|
|
2204
|
+
...Object.keys(attrs).length > 0 ? { attributes: attrs } : {},
|
|
2205
|
+
...el.className ? { className: el.className } : {},
|
|
2206
|
+
children: el.childNodes.map((c) => this._serializeNode(c))
|
|
2207
|
+
};
|
|
2208
|
+
}
|
|
2209
|
+
};
|
|
2210
|
+
//#endregion
|
|
2211
|
+
//#region src/worker-thread/observers.ts
|
|
2212
|
+
/**
|
|
2213
|
+
* Stub observer classes that prevent crashes when frameworks
|
|
2214
|
+
* attempt to use browser observers in a worker context.
|
|
2215
|
+
*/
|
|
2216
|
+
var VirtualMutationObserver = class {
|
|
2217
|
+
constructor(_callback) {}
|
|
2218
|
+
observe(_target, _options) {}
|
|
2219
|
+
disconnect() {}
|
|
2220
|
+
takeRecords() {
|
|
2221
|
+
return [];
|
|
2222
|
+
}
|
|
2223
|
+
};
|
|
2224
|
+
var VirtualResizeObserver = class {
|
|
2225
|
+
constructor(_callback) {}
|
|
2226
|
+
observe(_target, _options) {}
|
|
2227
|
+
unobserve(_target) {}
|
|
2228
|
+
disconnect() {}
|
|
2229
|
+
};
|
|
2230
|
+
var VirtualIntersectionObserver = class {
|
|
2231
|
+
root = null;
|
|
2232
|
+
rootMargin = "0px";
|
|
2233
|
+
thresholds = [0];
|
|
2234
|
+
constructor(_callback, _options) {}
|
|
2235
|
+
observe(_target) {}
|
|
2236
|
+
unobserve(_target) {}
|
|
2237
|
+
disconnect() {}
|
|
2238
|
+
takeRecords() {
|
|
2239
|
+
return [];
|
|
2240
|
+
}
|
|
2241
|
+
};
|
|
2242
|
+
//#endregion
|
|
2243
|
+
//#region src/worker-thread/storage.ts
|
|
2244
|
+
/**
|
|
2245
|
+
* Scoped Storage implementation that can optionally sync with
|
|
2246
|
+
* the main thread's real localStorage/sessionStorage via the sync channel.
|
|
2247
|
+
*
|
|
2248
|
+
* Each worker app gets its own isolated storage with a unique prefix.
|
|
2249
|
+
* When a sync channel is available, reads/writes are persisted to the
|
|
2250
|
+
* real browser storage on the main thread.
|
|
2251
|
+
*/
|
|
2252
|
+
var ScopedStorage = class {
|
|
2253
|
+
cache = /* @__PURE__ */ new Map();
|
|
2254
|
+
prefix;
|
|
2255
|
+
storageType;
|
|
2256
|
+
getSyncChannel;
|
|
2257
|
+
queryType;
|
|
2258
|
+
constructor(prefix, storageType, getSyncChannel, queryType) {
|
|
2259
|
+
this.prefix = prefix;
|
|
2260
|
+
this.storageType = storageType;
|
|
2261
|
+
this.getSyncChannel = getSyncChannel;
|
|
2262
|
+
this.queryType = queryType;
|
|
2263
|
+
}
|
|
2264
|
+
syncCall(method, args) {
|
|
2265
|
+
const channel = this.getSyncChannel();
|
|
2266
|
+
if (!channel) return null;
|
|
2267
|
+
return channel.request(this.queryType, JSON.stringify({
|
|
2268
|
+
property: `${this.storageType}.${method}`,
|
|
2269
|
+
args
|
|
2270
|
+
}));
|
|
2271
|
+
}
|
|
2272
|
+
get length() {
|
|
2273
|
+
return this.cache.size;
|
|
2274
|
+
}
|
|
2275
|
+
key(index) {
|
|
2276
|
+
return [...this.cache.keys()][index] ?? null;
|
|
2277
|
+
}
|
|
2278
|
+
getItem(key) {
|
|
2279
|
+
const cached = this.cache.get(key);
|
|
2280
|
+
if (cached !== void 0) return cached;
|
|
2281
|
+
const result = this.syncCall("getItem", [this.prefix + key]);
|
|
2282
|
+
if (typeof result === "string") {
|
|
2283
|
+
this.cache.set(key, result);
|
|
2284
|
+
return result;
|
|
2285
|
+
}
|
|
2286
|
+
return null;
|
|
2287
|
+
}
|
|
2288
|
+
setItem(key, value) {
|
|
2289
|
+
const strValue = String(value);
|
|
2290
|
+
this.cache.set(key, strValue);
|
|
2291
|
+
this.syncCall("setItem", [this.prefix + key, strValue]);
|
|
2292
|
+
}
|
|
2293
|
+
removeItem(key) {
|
|
2294
|
+
this.cache.delete(key);
|
|
2295
|
+
this.syncCall("removeItem", [this.prefix + key]);
|
|
2296
|
+
}
|
|
2297
|
+
clear() {
|
|
2298
|
+
for (const key of this.cache.keys()) this.syncCall("removeItem", [this.prefix + key]);
|
|
2299
|
+
this.cache.clear();
|
|
2300
|
+
}
|
|
2301
|
+
};
|
|
2302
|
+
//#endregion
|
|
2303
|
+
//#region src/worker-thread/index.ts
|
|
2304
|
+
/**
|
|
2305
|
+
* Creates a virtual DOM environment inside a Web Worker.
|
|
2306
|
+
*
|
|
2307
|
+
* Returns a `document` and `window` that can be used by frameworks
|
|
2308
|
+
* or vanilla JS. All DOM mutations are automatically collected and
|
|
2309
|
+
* sent to the main thread for rendering.
|
|
2310
|
+
*/
|
|
2311
|
+
function createWorkerDom(config) {
|
|
2312
|
+
const appId = config?.appId ?? require_sync_channel.createAppId("worker");
|
|
2313
|
+
const transport = config?.transport ?? new require_worker_transport.WorkerSelfTransport();
|
|
2314
|
+
const platform = config?.platform ?? detectPlatform();
|
|
2315
|
+
const doc = new VirtualDocument(appId);
|
|
2316
|
+
doc.collector.setTransport(transport);
|
|
2317
|
+
transport.onMessage((message) => {
|
|
2318
|
+
if (require_sync_channel.isSystemMessage(message) && message.type === "debugQuery") {
|
|
2319
|
+
const debugMsg = message;
|
|
2320
|
+
let result = null;
|
|
2321
|
+
if (debugMsg.query === "tree") result = doc.toJSON();
|
|
2322
|
+
else if (debugMsg.query === "stats") result = doc.collector.getStats();
|
|
2323
|
+
else if (debugMsg.query === "pendingCount") result = doc.collector.pendingCount;
|
|
2324
|
+
else if (debugMsg.query === "coalescedLog") result = doc.collector.getCoalescedLog();
|
|
2325
|
+
else if (debugMsg.query === "perTypeCoalesced") result = doc.collector.getPerTypeCoalesced();
|
|
2326
|
+
transport.send({
|
|
2327
|
+
type: "debugResult",
|
|
2328
|
+
query: debugMsg.query,
|
|
2329
|
+
result
|
|
2330
|
+
});
|
|
2331
|
+
return;
|
|
2332
|
+
}
|
|
2333
|
+
if (require_sync_channel.isSystemMessage(message) && message.type === "init" && "location" in message) {
|
|
2334
|
+
const initMsg = message;
|
|
2335
|
+
const initLoc = initMsg.location;
|
|
2336
|
+
if (initLoc) {
|
|
2337
|
+
location.href = initLoc.href;
|
|
2338
|
+
location.protocol = initLoc.protocol;
|
|
2339
|
+
location.hostname = initLoc.hostname;
|
|
2340
|
+
location.port = initLoc.port;
|
|
2341
|
+
location.host = initLoc.host;
|
|
2342
|
+
location.origin = initLoc.origin;
|
|
2343
|
+
location.pathname = initLoc.pathname;
|
|
2344
|
+
location.search = initLoc.search;
|
|
2345
|
+
location.hash = initLoc.hash;
|
|
2346
|
+
}
|
|
2347
|
+
if (initMsg.sharedBuffer) doc._syncChannel = require_sync_channel.SyncChannel.fromBuffer(initMsg.sharedBuffer);
|
|
2348
|
+
return;
|
|
2349
|
+
}
|
|
2350
|
+
if (require_sync_channel.isEventMessage(message)) {
|
|
2351
|
+
const eventMsg = message;
|
|
2352
|
+
const mutsBefore = doc.collector.totalAdded;
|
|
2353
|
+
const dispatchStart = performance.now();
|
|
2354
|
+
doc.dispatchEvent(eventMsg.listenerId, eventMsg.event);
|
|
2355
|
+
const dispatchMs = performance.now() - dispatchStart;
|
|
2356
|
+
const mutationCount = doc.collector.totalAdded - mutsBefore;
|
|
2357
|
+
const evt = eventMsg.event;
|
|
2358
|
+
const transportMs = evt.timeStamp != null ? dispatchStart - evt.timeStamp : void 0;
|
|
2359
|
+
transport.send({
|
|
2360
|
+
type: "eventTimingResult",
|
|
2361
|
+
listenerId: eventMsg.listenerId,
|
|
2362
|
+
eventType: evt.type ?? "",
|
|
2363
|
+
dispatchMs,
|
|
2364
|
+
mutationCount,
|
|
2365
|
+
transportMs: transportMs ?? 0
|
|
2366
|
+
});
|
|
2367
|
+
}
|
|
2368
|
+
});
|
|
2369
|
+
const cleanupErrorHandlers = platform.installErrorHandlers((message, error, filename, lineno, colno) => {
|
|
2370
|
+
const serializedError = {
|
|
2371
|
+
message,
|
|
2372
|
+
stack: error?.stack,
|
|
2373
|
+
name: error?.name,
|
|
2374
|
+
filename,
|
|
2375
|
+
lineno,
|
|
2376
|
+
colno
|
|
2377
|
+
};
|
|
2378
|
+
transport.send({
|
|
2379
|
+
type: "error",
|
|
2380
|
+
appId,
|
|
2381
|
+
error: serializedError
|
|
2382
|
+
});
|
|
2383
|
+
}, (reason) => {
|
|
2384
|
+
const serializedError = {
|
|
2385
|
+
message: reason instanceof Error ? reason.message : String(reason),
|
|
2386
|
+
stack: reason instanceof Error ? reason.stack : void 0,
|
|
2387
|
+
name: reason instanceof Error ? reason.name : "UnhandledRejection",
|
|
2388
|
+
isUnhandledRejection: true
|
|
2389
|
+
};
|
|
2390
|
+
transport.send({
|
|
2391
|
+
type: "error",
|
|
2392
|
+
appId,
|
|
2393
|
+
error: serializedError
|
|
2394
|
+
});
|
|
2395
|
+
});
|
|
2396
|
+
transport.send({
|
|
2397
|
+
type: "ready",
|
|
2398
|
+
appId
|
|
2399
|
+
});
|
|
2400
|
+
const perfEntriesInterval = setInterval(() => {
|
|
2401
|
+
if (typeof performance === "undefined" || !performance.getEntriesByType) return;
|
|
2402
|
+
const measures = performance.getEntriesByType("measure").filter((e) => e.name.startsWith("async-dom:"));
|
|
2403
|
+
if (measures.length === 0) return;
|
|
2404
|
+
const entries = measures.map((e) => ({
|
|
2405
|
+
name: e.name,
|
|
2406
|
+
startTime: e.startTime,
|
|
2407
|
+
duration: e.duration,
|
|
2408
|
+
entryType: e.entryType
|
|
2409
|
+
}));
|
|
2410
|
+
transport.send({
|
|
2411
|
+
type: "perfEntries",
|
|
2412
|
+
appId,
|
|
2413
|
+
entries
|
|
2414
|
+
});
|
|
2415
|
+
for (const e of measures) try {
|
|
2416
|
+
performance.clearMeasures(e.name);
|
|
2417
|
+
} catch {}
|
|
2418
|
+
}, 2e3);
|
|
2419
|
+
const cleanupBeforeUnload = platform.onBeforeUnload(() => clearInterval(perfEntriesInterval));
|
|
2420
|
+
const storagePrefix = `__async_dom_${appId}_`;
|
|
2421
|
+
const localStorage = new ScopedStorage(storagePrefix, "localStorage", () => doc._syncChannel, require_sync_channel.QueryType.WindowProperty);
|
|
2422
|
+
const sessionStorage = new ScopedStorage(`${storagePrefix}session_`, "sessionStorage", () => null, require_sync_channel.QueryType.WindowProperty);
|
|
2423
|
+
function updateLocationFromURL(loc, url) {
|
|
2424
|
+
try {
|
|
2425
|
+
const parsed = new URL(url, loc.href);
|
|
2426
|
+
loc.href = parsed.href;
|
|
2427
|
+
loc.protocol = parsed.protocol;
|
|
2428
|
+
loc.hostname = parsed.hostname;
|
|
2429
|
+
loc.port = parsed.port;
|
|
2430
|
+
loc.host = parsed.host;
|
|
2431
|
+
loc.origin = parsed.origin;
|
|
2432
|
+
loc.pathname = parsed.pathname;
|
|
2433
|
+
loc.search = parsed.search;
|
|
2434
|
+
loc.hash = parsed.hash;
|
|
2435
|
+
} catch {}
|
|
2436
|
+
}
|
|
2437
|
+
const location = {
|
|
2438
|
+
hash: "",
|
|
2439
|
+
href: "http://localhost/",
|
|
2440
|
+
port: "",
|
|
2441
|
+
host: "localhost",
|
|
2442
|
+
origin: "http://localhost",
|
|
2443
|
+
hostname: "localhost",
|
|
2444
|
+
pathname: "/",
|
|
2445
|
+
protocol: "http:",
|
|
2446
|
+
search: "",
|
|
2447
|
+
toString() {
|
|
2448
|
+
return this.href;
|
|
2449
|
+
},
|
|
2450
|
+
assign(url) {
|
|
2451
|
+
updateLocationFromURL(location, url);
|
|
2452
|
+
doc.collector.add({
|
|
2453
|
+
action: "pushState",
|
|
2454
|
+
state: null,
|
|
2455
|
+
title: "",
|
|
2456
|
+
url
|
|
2457
|
+
});
|
|
2458
|
+
},
|
|
2459
|
+
replace(url) {
|
|
2460
|
+
updateLocationFromURL(location, url);
|
|
2461
|
+
doc.collector.add({
|
|
2462
|
+
action: "replaceState",
|
|
2463
|
+
state: null,
|
|
2464
|
+
title: "",
|
|
2465
|
+
url
|
|
2466
|
+
});
|
|
2467
|
+
},
|
|
2468
|
+
reload() {}
|
|
2469
|
+
};
|
|
2470
|
+
const history = {
|
|
2471
|
+
state: null,
|
|
2472
|
+
length: 1,
|
|
2473
|
+
pushState(state, title, url) {
|
|
2474
|
+
history.state = state;
|
|
2475
|
+
updateLocationFromURL(location, url);
|
|
2476
|
+
doc.collector.add({
|
|
2477
|
+
action: "pushState",
|
|
2478
|
+
state,
|
|
2479
|
+
title,
|
|
2480
|
+
url
|
|
2481
|
+
});
|
|
2482
|
+
},
|
|
2483
|
+
replaceState(state, title, url) {
|
|
2484
|
+
history.state = state;
|
|
2485
|
+
updateLocationFromURL(location, url);
|
|
2486
|
+
doc.collector.add({
|
|
2487
|
+
action: "replaceState",
|
|
2488
|
+
state,
|
|
2489
|
+
title,
|
|
2490
|
+
url
|
|
2491
|
+
});
|
|
2492
|
+
},
|
|
2493
|
+
back() {},
|
|
2494
|
+
forward() {},
|
|
2495
|
+
go(_delta) {}
|
|
2496
|
+
};
|
|
2497
|
+
const win = {
|
|
2498
|
+
document: doc,
|
|
2499
|
+
location,
|
|
2500
|
+
history,
|
|
2501
|
+
screen: {
|
|
2502
|
+
get width() {
|
|
2503
|
+
if (doc._syncChannel) {
|
|
2504
|
+
const result = doc._syncChannel.request(require_sync_channel.QueryType.WindowProperty, JSON.stringify({ property: "screen.width" }));
|
|
2505
|
+
if (typeof result === "number") return result;
|
|
2506
|
+
}
|
|
2507
|
+
return 1280;
|
|
2508
|
+
},
|
|
2509
|
+
get height() {
|
|
2510
|
+
if (doc._syncChannel) {
|
|
2511
|
+
const result = doc._syncChannel.request(require_sync_channel.QueryType.WindowProperty, JSON.stringify({ property: "screen.height" }));
|
|
2512
|
+
if (typeof result === "number") return result;
|
|
2513
|
+
}
|
|
2514
|
+
return 720;
|
|
2515
|
+
}
|
|
2516
|
+
},
|
|
2517
|
+
innerWidth: 1280,
|
|
2518
|
+
innerHeight: 720,
|
|
2519
|
+
localStorage,
|
|
2520
|
+
sessionStorage,
|
|
2521
|
+
addEventListener(name, callback) {
|
|
2522
|
+
doc.addEventListener(name, callback);
|
|
2523
|
+
},
|
|
2524
|
+
removeEventListener(name, callback) {
|
|
2525
|
+
doc.removeEventListener(name, callback);
|
|
2526
|
+
},
|
|
2527
|
+
scrollTo(x, y) {
|
|
2528
|
+
doc.collector.add({
|
|
2529
|
+
action: "scrollTo",
|
|
2530
|
+
x,
|
|
2531
|
+
y
|
|
2532
|
+
});
|
|
2533
|
+
},
|
|
2534
|
+
getComputedStyle(el) {
|
|
2535
|
+
if (doc._syncChannel && el && typeof el === "object" && "_nodeId" in el) {
|
|
2536
|
+
const result = doc._syncChannel.request(require_sync_channel.QueryType.ComputedStyle, JSON.stringify({ nodeId: el._nodeId }));
|
|
2537
|
+
if (result && typeof result === "object") return result;
|
|
2538
|
+
}
|
|
2539
|
+
return {};
|
|
2540
|
+
},
|
|
2541
|
+
requestAnimationFrame(cb) {
|
|
2542
|
+
return setTimeout(() => cb(performance.now()), 16);
|
|
2543
|
+
},
|
|
2544
|
+
cancelAnimationFrame(id) {
|
|
2545
|
+
clearTimeout(id);
|
|
2546
|
+
},
|
|
2547
|
+
MutationObserver: VirtualMutationObserver,
|
|
2548
|
+
ResizeObserver: VirtualResizeObserver,
|
|
2549
|
+
IntersectionObserver: VirtualIntersectionObserver,
|
|
2550
|
+
setTimeout,
|
|
2551
|
+
setInterval,
|
|
2552
|
+
clearTimeout,
|
|
2553
|
+
clearInterval,
|
|
2554
|
+
queueMicrotask,
|
|
2555
|
+
performance,
|
|
2556
|
+
fetch: typeof fetch !== "undefined" ? fetch : void 0,
|
|
2557
|
+
URL,
|
|
2558
|
+
URLSearchParams,
|
|
2559
|
+
console,
|
|
2560
|
+
btoa,
|
|
2561
|
+
atob,
|
|
2562
|
+
navigator: platform.navigator,
|
|
2563
|
+
Event: VirtualEvent,
|
|
2564
|
+
CustomEvent: VirtualCustomEvent,
|
|
2565
|
+
Node: {
|
|
2566
|
+
ELEMENT_NODE: 1,
|
|
2567
|
+
TEXT_NODE: 3,
|
|
2568
|
+
COMMENT_NODE: 8,
|
|
2569
|
+
DOCUMENT_NODE: 9,
|
|
2570
|
+
DOCUMENT_FRAGMENT_NODE: 11
|
|
2571
|
+
},
|
|
2572
|
+
HTMLElement: VirtualElement,
|
|
2573
|
+
devicePixelRatio: 1,
|
|
2574
|
+
matchMedia: (query) => ({
|
|
2575
|
+
matches: false,
|
|
2576
|
+
media: query,
|
|
2577
|
+
addEventListener() {},
|
|
2578
|
+
removeEventListener() {}
|
|
2579
|
+
}),
|
|
2580
|
+
getSelection: () => ({
|
|
2581
|
+
rangeCount: 0,
|
|
2582
|
+
getRangeAt() {
|
|
2583
|
+
return null;
|
|
2584
|
+
},
|
|
2585
|
+
addRange() {},
|
|
2586
|
+
removeAllRanges() {}
|
|
2587
|
+
}),
|
|
2588
|
+
dispatchEvent: (event) => {
|
|
2589
|
+
doc.dispatchEvent("", event);
|
|
2590
|
+
return true;
|
|
2591
|
+
},
|
|
2592
|
+
eval: (_code) => {
|
|
2593
|
+
throw new Error("sandbox eval is not enabled — set sandbox: true or sandbox: 'eval'");
|
|
2594
|
+
}
|
|
2595
|
+
};
|
|
2596
|
+
Object.defineProperties(win, {
|
|
2597
|
+
innerWidth: {
|
|
2598
|
+
get() {
|
|
2599
|
+
if (doc._syncChannel) {
|
|
2600
|
+
const result = doc._syncChannel.request(require_sync_channel.QueryType.WindowProperty, JSON.stringify({ property: "innerWidth" }));
|
|
2601
|
+
if (typeof result === "number") return result;
|
|
2602
|
+
}
|
|
2603
|
+
return 1280;
|
|
2604
|
+
},
|
|
2605
|
+
configurable: true
|
|
2606
|
+
},
|
|
2607
|
+
innerHeight: {
|
|
2608
|
+
get() {
|
|
2609
|
+
if (doc._syncChannel) {
|
|
2610
|
+
const result = doc._syncChannel.request(require_sync_channel.QueryType.WindowProperty, JSON.stringify({ property: "innerHeight" }));
|
|
2611
|
+
if (typeof result === "number") return result;
|
|
2612
|
+
}
|
|
2613
|
+
return 720;
|
|
2614
|
+
},
|
|
2615
|
+
configurable: true
|
|
2616
|
+
}
|
|
2617
|
+
});
|
|
2618
|
+
const sandboxMode = config?.sandbox;
|
|
2619
|
+
if (sandboxMode === "eval" || sandboxMode === true) win.eval = (code) => {
|
|
2620
|
+
const sandbox = new Proxy(win, {
|
|
2621
|
+
has() {
|
|
2622
|
+
return true;
|
|
2623
|
+
},
|
|
2624
|
+
get(target, prop) {
|
|
2625
|
+
if (prop === Symbol.unscopables) return void 0;
|
|
2626
|
+
if (prop in target) return target[prop];
|
|
2627
|
+
if (prop in globalThis) return globalThis[prop];
|
|
2628
|
+
},
|
|
2629
|
+
set(target, prop, value) {
|
|
2630
|
+
target[prop] = value;
|
|
2631
|
+
return true;
|
|
2632
|
+
}
|
|
2633
|
+
});
|
|
2634
|
+
return new Function("window", "self", "globalThis", "document", `with(window) {\n\t\t\t\treturn (function() { ${code} }).call(window);\n\t\t\t}`)(sandbox, sandbox, sandbox, doc);
|
|
2635
|
+
};
|
|
2636
|
+
if (sandboxMode === "global" || sandboxMode === true) {
|
|
2637
|
+
const workerGlobal = globalThis;
|
|
2638
|
+
workerGlobal.document = doc;
|
|
2639
|
+
workerGlobal.window = win;
|
|
2640
|
+
workerGlobal.location = win.location;
|
|
2641
|
+
workerGlobal.history = win.history;
|
|
2642
|
+
workerGlobal.navigator = win.navigator;
|
|
2643
|
+
workerGlobal.screen = win.screen;
|
|
2644
|
+
workerGlobal.localStorage = win.localStorage;
|
|
2645
|
+
workerGlobal.sessionStorage = win.sessionStorage;
|
|
2646
|
+
workerGlobal.getComputedStyle = win.getComputedStyle.bind(win);
|
|
2647
|
+
workerGlobal.requestAnimationFrame = win.requestAnimationFrame.bind(win);
|
|
2648
|
+
workerGlobal.cancelAnimationFrame = win.cancelAnimationFrame.bind(win);
|
|
2649
|
+
workerGlobal.scrollTo = win.scrollTo.bind(win);
|
|
2650
|
+
workerGlobal.matchMedia = win.matchMedia;
|
|
2651
|
+
workerGlobal.getSelection = win.getSelection;
|
|
2652
|
+
workerGlobal.dispatchEvent = win.dispatchEvent;
|
|
2653
|
+
workerGlobal.MutationObserver = win.MutationObserver;
|
|
2654
|
+
workerGlobal.ResizeObserver = win.ResizeObserver;
|
|
2655
|
+
workerGlobal.IntersectionObserver = win.IntersectionObserver;
|
|
2656
|
+
workerGlobal.Event = win.Event;
|
|
2657
|
+
workerGlobal.CustomEvent = win.CustomEvent;
|
|
2658
|
+
workerGlobal.Node = win.Node;
|
|
2659
|
+
workerGlobal.HTMLElement = win.HTMLElement;
|
|
2660
|
+
workerGlobal.devicePixelRatio = win.devicePixelRatio;
|
|
2661
|
+
const innerWidthDesc = Object.getOwnPropertyDescriptor(win, "innerWidth");
|
|
2662
|
+
const innerHeightDesc = Object.getOwnPropertyDescriptor(win, "innerHeight");
|
|
2663
|
+
if (innerWidthDesc) Object.defineProperty(workerGlobal, "innerWidth", innerWidthDesc);
|
|
2664
|
+
if (innerHeightDesc) Object.defineProperty(workerGlobal, "innerHeight", innerHeightDesc);
|
|
2665
|
+
}
|
|
2666
|
+
doc._defaultView = win;
|
|
2667
|
+
if (config?.debug?.exposeDevtools) globalThis.__ASYNC_DOM_DEVTOOLS__ = {
|
|
2668
|
+
document: doc,
|
|
2669
|
+
tree: () => doc.toJSON(),
|
|
2670
|
+
findNode: (id) => doc.getElementById(id) ?? doc.querySelector(`[id="${id}"]`),
|
|
2671
|
+
stats: () => doc.collector.getStats(),
|
|
2672
|
+
mutations: () => ({ pending: doc.collector.pendingCount }),
|
|
2673
|
+
flush: () => doc.collector.flushSync()
|
|
2674
|
+
};
|
|
2675
|
+
if (config?.debug?.logMutations) require_sync_channel.resolveDebugHooks(config.debug);
|
|
2676
|
+
function destroy() {
|
|
2677
|
+
doc.destroy();
|
|
2678
|
+
clearInterval(perfEntriesInterval);
|
|
2679
|
+
cleanupErrorHandlers();
|
|
2680
|
+
cleanupBeforeUnload();
|
|
2681
|
+
transport.close();
|
|
2682
|
+
}
|
|
2683
|
+
return {
|
|
2684
|
+
document: doc,
|
|
2685
|
+
window: win,
|
|
2686
|
+
destroy
|
|
2687
|
+
};
|
|
2688
|
+
}
|
|
2689
|
+
//#endregion
|
|
2690
|
+
Object.defineProperty(exports, "MutationCollector", {
|
|
2691
|
+
enumerable: true,
|
|
2692
|
+
get: function() {
|
|
2693
|
+
return MutationCollector;
|
|
2694
|
+
}
|
|
2695
|
+
});
|
|
2696
|
+
Object.defineProperty(exports, "ScopedStorage", {
|
|
2697
|
+
enumerable: true,
|
|
2698
|
+
get: function() {
|
|
2699
|
+
return ScopedStorage;
|
|
2700
|
+
}
|
|
2701
|
+
});
|
|
2702
|
+
Object.defineProperty(exports, "VirtualCommentNode", {
|
|
2703
|
+
enumerable: true,
|
|
2704
|
+
get: function() {
|
|
2705
|
+
return VirtualCommentNode;
|
|
2706
|
+
}
|
|
2707
|
+
});
|
|
2708
|
+
Object.defineProperty(exports, "VirtualDocument", {
|
|
2709
|
+
enumerable: true,
|
|
2710
|
+
get: function() {
|
|
2711
|
+
return VirtualDocument;
|
|
2712
|
+
}
|
|
2713
|
+
});
|
|
2714
|
+
Object.defineProperty(exports, "VirtualElement", {
|
|
2715
|
+
enumerable: true,
|
|
2716
|
+
get: function() {
|
|
2717
|
+
return VirtualElement;
|
|
2718
|
+
}
|
|
2719
|
+
});
|
|
2720
|
+
Object.defineProperty(exports, "VirtualTextNode", {
|
|
2721
|
+
enumerable: true,
|
|
2722
|
+
get: function() {
|
|
2723
|
+
return VirtualTextNode;
|
|
2724
|
+
}
|
|
2725
|
+
});
|
|
2726
|
+
Object.defineProperty(exports, "createNodePlatform", {
|
|
2727
|
+
enumerable: true,
|
|
2728
|
+
get: function() {
|
|
2729
|
+
return createNodePlatform;
|
|
2730
|
+
}
|
|
2731
|
+
});
|
|
2732
|
+
Object.defineProperty(exports, "createWorkerDom", {
|
|
2733
|
+
enumerable: true,
|
|
2734
|
+
get: function() {
|
|
2735
|
+
return createWorkerDom;
|
|
2736
|
+
}
|
|
2737
|
+
});
|
|
2738
|
+
Object.defineProperty(exports, "createWorkerPlatform", {
|
|
2739
|
+
enumerable: true,
|
|
2740
|
+
get: function() {
|
|
2741
|
+
return createWorkerPlatform;
|
|
2742
|
+
}
|
|
2743
|
+
});
|
|
2744
|
+
Object.defineProperty(exports, "detectPlatform", {
|
|
2745
|
+
enumerable: true,
|
|
2746
|
+
get: function() {
|
|
2747
|
+
return detectPlatform;
|
|
2748
|
+
}
|
|
2749
|
+
});
|
|
2750
|
+
|
|
2751
|
+
//# sourceMappingURL=worker-thread.cjs.map
|