buninu-browser 0.2.0 → 0.2.2

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.
Files changed (80) hide show
  1. package/AGENTS.md +4 -0
  2. package/README.md +74 -9
  3. package/bunfig.toml +1 -0
  4. package/buninu-browser.js +4 -0
  5. package/index.js +5 -0
  6. package/lib/audio/piano.js +24 -1
  7. package/lib/audio/pulse-client.js +120 -12
  8. package/lib/cdp-server.js +19 -12
  9. package/lib/happy-dom/parser.js +24 -4
  10. package/lib/happy-dom/selector-matcher.js +83 -0
  11. package/lib/headless-shell.js +120 -28
  12. package/lib/input/hit-test.js +25 -7
  13. package/lib/layout/text-layout.js +365 -54
  14. package/lib/log.js +5 -2
  15. package/lib/media/byte-pipe.js +82 -0
  16. package/lib/media/position-audio.js +42 -0
  17. package/lib/network/network-service.js +326 -46
  18. package/lib/paint/display-list.js +322 -21
  19. package/lib/paint/gradient.js +11 -5
  20. package/lib/render-tree/index.js +62 -2
  21. package/lib/render-tree/svg.js +2 -1
  22. package/lib/renderer/bindings.js +989 -72
  23. package/lib/renderer/canvas-2d.js +92 -0
  24. package/lib/renderer/host.js +159 -9
  25. package/lib/renderer/interfaces.js +215 -0
  26. package/lib/renderer/main.js +160 -12
  27. package/lib/renderer/media-source.js +819 -0
  28. package/lib/renderer/messaging.js +411 -0
  29. package/lib/renderer/native-functions.js +113 -0
  30. package/lib/renderer/page-realm.js +1407 -200
  31. package/lib/renderer/page-renderer.js +1899 -394
  32. package/lib/renderer/rejections.js +31 -0
  33. package/lib/renderer/rewrite-worker.js +22 -0
  34. package/lib/renderer/script-rewrite.js +805 -57
  35. package/lib/renderer/trusted-types.js +129 -0
  36. package/lib/renderer/workers.js +327 -0
  37. package/lib/screenshot-cli.js +2 -0
  38. package/lib/style/computed-style.js +463 -242
  39. package/lib/style/selector-keys.js +302 -0
  40. package/lib/style/selector-traits.js +240 -0
  41. package/lib/user-agent.js +14 -2
  42. package/package.json +4 -1
  43. package/patches/happy-dom/0002-use-bun-platform-adapters.patch +139 -19
  44. package/patches/happy-dom/0006-parse-css-escapes-in-selectors.patch +115 -0
  45. package/patches/happy-dom/README.md +26 -0
  46. package/patches/ses/0002-embedder-native-functions-source-text-sloppy-code.patch +106 -0
  47. package/patches/ses/0003-cache-sloppy-evaluators.patch +47 -0
  48. package/patches/ses/README.md +4 -0
  49. package/test/canvas-2d.test.js +55 -0
  50. package/test/compositor.test.js +3 -1
  51. package/test/computed-style.test.js +98 -1
  52. package/test/display-list.test.js +74 -1
  53. package/test/download.test.js +35 -0
  54. package/test/fixtures/long-video.mp4 +0 -0
  55. package/test/fixtures/media-sync.js +57 -0
  56. package/test/happy-dom-platform-adapters.test.js +25 -0
  57. package/test/headless-shell.test.js +29 -0
  58. package/test/hit-test.test.js +25 -1
  59. package/test/log.test.js +4 -1
  60. package/test/media-source.test.js +156 -0
  61. package/test/media-sync.test.js +12 -0
  62. package/test/mobile.test.js +2 -2
  63. package/test/navigation.test.js +61 -0
  64. package/test/network-service.test.js +81 -0
  65. package/test/page-scripts.test.js +1094 -7
  66. package/test/position-audio.test.js +67 -0
  67. package/test/positioning.test.js +95 -1
  68. package/test/pulse-client.test.js +69 -0
  69. package/test/render-tree.test.js +22 -1
  70. package/test/renderer-host.test.js +5 -2
  71. package/test/screenshot-cli.test.js +25 -1
  72. package/test/script-rewrite.test.js +111 -6
  73. package/test/selector-keys.test.js +39 -0
  74. package/test/selector-traits.test.js +19 -0
  75. package/test/setup.js +5 -0
  76. package/test/text-layout.test.js +69 -1
  77. package/test/user-agent.test.js +60 -4
  78. package/vendor/happy-dom/packages/happy-dom/src/nodes/html-image-element/HTMLImageElement.ts +52 -22
  79. package/vendor/happy-dom/packages/happy-dom/src/query-selector/SelectorParser.ts +18 -17
  80. package/vendor/ses/dist/ses.mjs +52 -6
@@ -0,0 +1,411 @@
1
+ /**
2
+ * Cross-document messaging for one document's realm (HTML §9.3, §9.4):
3
+ * window.postMessage between the frames of a page, the WindowProxy-like
4
+ * objects that stand for other frames, and MessageChannel / MessagePort.
5
+ *
6
+ * Frames are reached through window handles (see page-renderer.js): host
7
+ * objects with the frame's origin, its parent and a way to deliver a message
8
+ * into its current realm. Every frame of a page lives in this renderer
9
+ * process, so message ports are host objects too, and a transferred port
10
+ * keeps its queue while it moves to another realm.
11
+ *
12
+ * A same-origin frame's window is its document's own global object (and its
13
+ * document is reachable), as in browsers; any other frame is seen through
14
+ * a proxy that only posts messages.
15
+ */
16
+
17
+ import { AsyncLocalStorage } from "node:async_hooks";
18
+
19
+ // Captured before lockdown: messages are cloned by the host.
20
+ const hostStructuredClone = globalThis.structuredClone;
21
+ // Message delivery is a task, not a timer: setImmediate runs it on the next
22
+ // turn of the event loop, where setTimeout(0) waits for the next millisecond
23
+ // tick (pages that yield through a MessageChannel do it thousands of times).
24
+ const hostQueueTask = globalThis.setImmediate ?? ((task) => globalThis.setTimeout(task, 0));
25
+
26
+ /**
27
+ * One end of a message channel. `owner` is the messaging of the realm that
28
+ * holds the port now (null while it is in transit); messages wait in `queue`
29
+ * until that realm has started the port.
30
+ */
31
+ class PortEndpoint {
32
+ peer = null;
33
+ owner = null;
34
+ facade = null;
35
+ queue = [];
36
+ started = false;
37
+ closed = false;
38
+
39
+ enqueue(message) {
40
+ if (this.closed) return;
41
+ this.queue.push(message);
42
+ this.flush();
43
+ }
44
+
45
+ flush() {
46
+ while (this.owner && this.started && !this.closed && this.queue.length) {
47
+ this.owner.deliverToPort(this, this.queue.shift());
48
+ }
49
+ }
50
+ }
51
+
52
+ // Every realm's port objects, by object: a same-origin frame may pass a port
53
+ // of its own realm to another realm's postMessage.
54
+ const portEndpoints = new WeakMap();
55
+
56
+ // The window handle of the realm running page code. A
57
+ // same-origin frame calls another frame's postMessage directly; the message
58
+ // is still from the caller (HTML's incumbent settings object). Keep it across
59
+ // promise continuations too: a synchronous stack loses the sender after await.
60
+ // This host-only context is never exposed to page scripts.
61
+ const incumbent = new AsyncLocalStorage();
62
+
63
+ /** Runs `task` as code of the realm whose window is `handle`. */
64
+ export function asIncumbent(handle, task) {
65
+ if (!handle) return task();
66
+ return incumbent.run(handle, task);
67
+ }
68
+
69
+ /** The origin a document's URL serializes to ("null" when opaque). */
70
+ export function originOf(url) {
71
+ try {
72
+ const parsed = new URL(url);
73
+ return ["http:", "https:"].includes(parsed.protocol) ? parsed.origin : "null";
74
+ } catch {
75
+ return "null";
76
+ }
77
+ }
78
+
79
+ /**
80
+ * Message ports for one realm (a document's or a worker's): MessageChannel,
81
+ * MessagePort, and moving ports in and out of the realm with postMessage.
82
+ * `makeEvent(data, ports)` builds the realm's message event; `queueTask`
83
+ * runs a task of the realm; `call` invokes page functions.
84
+ */
85
+ export function createPorts({ makeEvent, queueTask, call, DOMException }) {
86
+ const endpoints = portEndpoints;
87
+ const owned = new Set();
88
+ const listeners = new WeakMap();
89
+ const handlers = new WeakMap();
90
+
91
+ function MessagePort() {
92
+ throw new TypeError("Illegal constructor");
93
+ }
94
+ // A port that was transferred away keeps its listeners but no endpoint: it does nothing.
95
+ const endpointOf = (port) => {
96
+ if (!listeners.has(port)) throw new TypeError("Illegal invocation");
97
+ return endpoints.get(port) ?? null;
98
+ };
99
+
100
+ /**
101
+ * A message and its transfer list as they leave this realm: the data
102
+ * cloned (other transferables, such as ArrayBuffers, move with it) and the
103
+ * ports taken out. Nothing is taken when cloning fails.
104
+ */
105
+ function pack(message, transfer) {
106
+ const list = transfer == null ? [] : Array.from(transfer);
107
+ const ports = list.filter((item) => endpoints.has(item));
108
+ const others = list.filter((item) => !endpoints.has(item));
109
+ if (new Set(list).size !== list.length) throw new DOMException("An object is transferred twice", "DataCloneError");
110
+ const data = clone(message, DOMException, others);
111
+ return { data, ports: take(ports) };
112
+ }
113
+
114
+ /** Takes the ports of a transfer list out of this realm. */
115
+ function take(transfer) {
116
+ const list = transfer == null ? [] : Array.from(transfer);
117
+ const taken = [];
118
+ for (const item of list) {
119
+ const endpoint = endpoints.get(item);
120
+ if (!endpoint) throw new DOMException("Only MessagePort objects can be transferred", "DataCloneError");
121
+ if (taken.includes(endpoint)) throw new DOMException("A port is transferred twice", "DataCloneError");
122
+ taken.push(endpoint);
123
+ }
124
+ for (const endpoint of taken) {
125
+ endpoints.delete(endpoint.facade);
126
+ // The realm holding it may be another frame's.
127
+ endpoint.owner?.release(endpoint);
128
+ Object.assign(endpoint, { owner: null, facade: null, started: false });
129
+ }
130
+ return taken;
131
+ }
132
+
133
+ /** This realm's port object for an endpoint (it now holds that end). */
134
+ function adopt(endpoint) {
135
+ if (endpoint.facade && endpoint.owner === api) return endpoint.facade;
136
+ const port = Object.create(MessagePort.prototype);
137
+ listeners.set(port, []);
138
+ handlers.set(port, {});
139
+ endpoints.set(port, endpoint);
140
+ Object.assign(endpoint, { owner: api, facade: port });
141
+ owned.add(endpoint);
142
+ return port;
143
+ }
144
+
145
+ function dispatch(port, event) {
146
+ const handler = handlers.get(port)?.[event.type];
147
+ if (handler) call(handler, port, [event]);
148
+ for (const { type, listener } of [...(listeners.get(port) ?? [])]) {
149
+ if (type !== event.type) continue;
150
+ if (typeof listener === "function") call(listener, port, [event]);
151
+ else call(listener.handleEvent, listener, [event]);
152
+ }
153
+ }
154
+
155
+ MessagePort.prototype = {
156
+ constructor: MessagePort,
157
+ postMessage(message, options = undefined) {
158
+ const endpoint = endpointOf(this);
159
+ const transfer = Array.isArray(options) ? options : options?.transfer;
160
+ const packed = pack(message, transfer);
161
+ if (!endpoint || endpoint.closed || !endpoint.peer) return;
162
+ endpoint.peer.enqueue(packed);
163
+ },
164
+ start() {
165
+ const endpoint = endpointOf(this);
166
+ if (!endpoint || endpoint.started) return;
167
+ endpoint.started = true;
168
+ endpoint.flush();
169
+ },
170
+ close() {
171
+ const endpoint = endpointOf(this);
172
+ if (!endpoint) return;
173
+ endpoint.closed = true;
174
+ if (endpoint.peer) endpoint.peer.closed = true;
175
+ },
176
+ addEventListener(type, listener) {
177
+ if (typeof listener !== "function" && typeof listener?.handleEvent !== "function") return;
178
+ const list = listeners.get(this);
179
+ if (list && !list.some((entry) => entry.type === type && entry.listener === listener)) list.push({ type, listener });
180
+ },
181
+ removeEventListener(type, listener) {
182
+ const list = listeners.get(this);
183
+ const index = list?.findIndex((entry) => entry.type === type && entry.listener === listener) ?? -1;
184
+ if (index >= 0) list.splice(index, 1);
185
+ },
186
+ dispatchEvent(event) {
187
+ dispatch(this, event);
188
+ return !event?.defaultPrevented;
189
+ },
190
+ };
191
+ for (const type of ["message", "messageerror"]) {
192
+ Object.defineProperty(MessagePort.prototype, `on${type}`, {
193
+ configurable: true,
194
+ enumerable: true,
195
+ get() {
196
+ return handlers.get(this)?.[type] ?? null;
197
+ },
198
+ set(value) {
199
+ const own = handlers.get(this);
200
+ if (!own) return;
201
+ own[type] = typeof value === "function" ? value : null;
202
+ // Setting onmessage starts the port (HTML §9.4.4).
203
+ if (type === "message") this.start();
204
+ },
205
+ });
206
+ }
207
+
208
+ function MessageChannel() {
209
+ if (!new.target) throw new TypeError("Constructor MessageChannel requires 'new'");
210
+ const left = new PortEndpoint();
211
+ const right = new PortEndpoint();
212
+ left.peer = right;
213
+ right.peer = left;
214
+ const channel = Object.create(MessageChannel.prototype);
215
+ Object.defineProperty(channel, "port1", { value: adopt(left), enumerable: true });
216
+ Object.defineProperty(channel, "port2", { value: adopt(right), enumerable: true });
217
+ return channel;
218
+ }
219
+ MessageChannel.prototype = { constructor: MessageChannel };
220
+
221
+ const api = {
222
+ MessageChannel,
223
+ MessagePort,
224
+ pack,
225
+ take,
226
+ adopt,
227
+ release(endpoint) {
228
+ owned.delete(endpoint);
229
+ },
230
+ /** A message for a port of this realm (see PortEndpoint). */
231
+ deliverToPort(endpoint, { data, ports }) {
232
+ queueTask(() => {
233
+ if (endpoint.owner !== api || !endpoint.facade) return;
234
+ dispatch(endpoint.facade, makeEvent(data, ports.map(adopt)));
235
+ });
236
+ },
237
+ dispose() {
238
+ for (const endpoint of owned) Object.assign(endpoint, { owner: null, facade: null });
239
+ owned.clear();
240
+ },
241
+ };
242
+ return api;
243
+ }
244
+
245
+ /** A host structured clone of a page value (moving `transfer`); failures are the page's DataCloneError. */
246
+ export function clone(value, DOMException, transfer = []) {
247
+ try {
248
+ return hostStructuredClone(value, transfer.length ? { transfer } : undefined);
249
+ } catch (error) {
250
+ throw new DOMException(String(error?.message ?? error), "DataCloneError");
251
+ }
252
+ }
253
+
254
+ /**
255
+ * @param {{
256
+ * realm: object, window: object, pageGlobal(): object, runTask(task: () => void): void,
257
+ * callPage(fn: Function, thisArg: object, args: unknown[]): unknown, DOMException: Function,
258
+ * frames: { self: object, parent: object | null, top: object, childOf(node: object): object | null } | null,
259
+ * }} options
260
+ */
261
+ export function createMessaging({ realm, window, pageGlobal, runTask, callPage, DOMException, frames }) {
262
+ let disposed = false;
263
+ const proxies = new WeakMap();
264
+ const self = frames?.self ?? null;
265
+
266
+ const queueTask = (task) => hostQueueTask(() => {
267
+ if (!disposed) runTask(task);
268
+ });
269
+ const ports = createPorts({
270
+ makeEvent: (data, adopted) => realm.messageEvent("message", { data, ports: adopted }),
271
+ queueTask,
272
+ call: callPage,
273
+ DOMException,
274
+ });
275
+
276
+ // ---- windows
277
+ /** Whether a frame shows a document of this document's origin. */
278
+ const sameOrigin = (handle) => {
279
+ const origin = self?.origin();
280
+ return Boolean(origin && origin !== "null" && handle.origin() === origin);
281
+ };
282
+
283
+ /**
284
+ * window.frames: the window itself, where frames[i] and frames[name] are
285
+ * its child frames (by position, or by browsing context name).
286
+ */
287
+ function framesView(handle, target) {
288
+ const child = (key) => {
289
+ if (typeof key !== "string") return null;
290
+ const children = handle.children();
291
+ return /^(0|[1-9]\d*)$/.test(key) ? children[Number(key)] ?? null : children.find((frame) => frame.name() === key) ?? null;
292
+ };
293
+ // A child may not stand for a fixed property of the window (Proxy invariants).
294
+ const fixed = (key) => Object.getOwnPropertyDescriptor(target, key)?.configurable === false;
295
+ return new Proxy(target, {
296
+ get(object, key) {
297
+ const frame = fixed(key) ? null : child(key);
298
+ return frame ? proxyFor(frame) : Reflect.get(object, key);
299
+ },
300
+ has(object, key) {
301
+ return Boolean(child(key)) || Reflect.has(object, key);
302
+ },
303
+ });
304
+ }
305
+
306
+ /** The page's view of a frame: its own window, its global when same-origin, or a proxy that posts to it. */
307
+ function proxyFor(handle) {
308
+ if (!handle) return null;
309
+ if (handle === self) return pageGlobal();
310
+ if (sameOrigin(handle)) {
311
+ const global = handle.global();
312
+ if (global) return global;
313
+ }
314
+ let proxy = proxies.get(handle);
315
+ if (proxy) return proxy;
316
+ proxy = {
317
+ postMessage(message, targetOrigin = undefined, transfer = undefined) {
318
+ post(handle, message, targetOrigin, transfer);
319
+ },
320
+ get closed() {
321
+ return handle.closed();
322
+ },
323
+ get parent() {
324
+ return proxyFor(handle.parent()) ?? proxy;
325
+ },
326
+ get top() {
327
+ return proxyFor(handle.top());
328
+ },
329
+ get length() {
330
+ return handle.children().length;
331
+ },
332
+ focus() {},
333
+ blur() {},
334
+ };
335
+ Object.defineProperty(proxy, "window", { get: () => proxy });
336
+ Object.defineProperty(proxy, "self", { get: () => proxy });
337
+ const frames = framesView(handle, proxy);
338
+ Object.defineProperty(proxy, "frames", { get: () => frames });
339
+ proxies.set(handle, proxy);
340
+ return proxy;
341
+ }
342
+
343
+ /** window.postMessage(message, targetOrigin | options, transfer) to the frame behind `handle`. */
344
+ function post(handle, message, targetOrigin, transfer) {
345
+ let target = targetOrigin;
346
+ if (targetOrigin !== null && typeof targetOrigin === "object") {
347
+ target = targetOrigin.targetOrigin;
348
+ transfer = targetOrigin.transfer;
349
+ }
350
+ target = target === undefined ? "/" : String(target);
351
+ const sender = incumbent.getStore() ?? self;
352
+ const origin = sender?.origin() ?? originOf(window.location?.href ?? "");
353
+ let wanted = null;
354
+ if (target === "/") wanted = origin;
355
+ else if (target !== "*") {
356
+ wanted = originOf(target);
357
+ if (wanted === "null") throw new DOMException(`Invalid target origin '${target}'`, "SyntaxError");
358
+ }
359
+ const { data, ports: taken } = ports.pack(message, transfer);
360
+ // Delivered later, and only if the frame still shows a document of the
361
+ // origin asked for (it may have navigated meanwhile).
362
+ hostQueueTask(() => {
363
+ if (handle.closed() || (wanted !== null && handle.origin() !== wanted)) return;
364
+ handle.post({ data, origin, source: sender, ports: taken });
365
+ });
366
+ }
367
+
368
+ const api = {
369
+ MessageChannel: ports.MessageChannel,
370
+ MessagePort: ports.MessagePort,
371
+ ports,
372
+ postMessage(message, targetOrigin = undefined, transfer = undefined) {
373
+ if (self) post(self, message, targetOrigin, transfer);
374
+ else {
375
+ // A document without frames (no renderer handle): its own window only.
376
+ const { data, ports: taken } = ports.pack(message, transfer);
377
+ api.receive({ data, origin: originOf(window.location?.href ?? ""), source: null, ports: taken });
378
+ }
379
+ },
380
+ proxyFor,
381
+ /** window.frames for this document's window. */
382
+ framesOf: (global) => self ? framesView(self, global) : global,
383
+ contentWindow: (node) => proxyFor(frames?.childOf(node) ?? null),
384
+ contentDocument(node) {
385
+ const handle = frames?.childOf(node) ?? null;
386
+ return handle && sameOrigin(handle) ? handle.document() : null;
387
+ },
388
+ get parent() {
389
+ return proxyFor(frames?.parent ?? null);
390
+ },
391
+ get top() {
392
+ return frames && frames.top !== self ? proxyFor(frames.top) : null;
393
+ },
394
+
395
+ /** A message posted to this document's window. */
396
+ receive({ data, origin, source, ports: endpoints }) {
397
+ queueTask(() => {
398
+ const event = realm.messageEvent("message", {
399
+ data, origin, source: source ? proxyFor(source) : null, ports: endpoints.map(ports.adopt),
400
+ });
401
+ window.dispatchEvent(realm.unwrapEvent(event));
402
+ });
403
+ },
404
+
405
+ dispose() {
406
+ disposed = true;
407
+ ports.dispose();
408
+ },
409
+ };
410
+ return api;
411
+ }
@@ -0,0 +1,113 @@
1
+ /**
2
+ * Web APIs implemented in JavaScript, presented the way a browser presents
3
+ * its built-ins. Chromium's bindings are generated C++: each operation and
4
+ * attribute accessor prints as "function name() { [native code] }", is named
5
+ * after its IDL member, and accessors are named "get x" / "set x". Page
6
+ * scripts (and integrity checks) read exactly these things.
7
+ *
8
+ * The patched SES bundle leaves its toString taming's marker on the host
9
+ * global under a registered symbol; the first use here takes it and removes it.
10
+ */
11
+ const MARKER_KEY = Symbol.for("ses.markVirtualizedNativeFunction");
12
+ let marker = null;
13
+
14
+ function markVirtualizedNative(fn) {
15
+ if (!marker) {
16
+ const found = globalThis[MARKER_KEY];
17
+ if (typeof found !== "function") return;
18
+ marker = found;
19
+ delete globalThis[MARKER_KEY];
20
+ }
21
+ marker(fn);
22
+ }
23
+
24
+ function memberName(key) {
25
+ return typeof key === "symbol" ? `[${key.description ?? ""}]` : key;
26
+ }
27
+
28
+ /** Marks `fn` as built-in, renaming it to `name` when its own name is different and still changeable. */
29
+ export function markNative(fn, name = undefined) {
30
+ if (typeof fn !== "function") return fn;
31
+ if (name !== undefined && fn.name !== name) {
32
+ const descriptor = Object.getOwnPropertyDescriptor(fn, "name");
33
+ if (!descriptor || descriptor.configurable) {
34
+ Object.defineProperty(fn, "name", { value: name, configurable: true });
35
+ }
36
+ }
37
+ markVirtualizedNative(fn);
38
+ return fn;
39
+ }
40
+
41
+ /** Marks the own operations and accessors of `object` (not its prototypes). */
42
+ export function markNativeMembers(object) {
43
+ for (const key of Reflect.ownKeys(object)) {
44
+ if (key === "constructor") continue;
45
+ const descriptor = Object.getOwnPropertyDescriptor(object, key);
46
+ if (!descriptor) continue;
47
+ const name = memberName(key);
48
+ if (typeof descriptor.value === "function") markNative(descriptor.value, name);
49
+ if (descriptor.get) markNative(descriptor.get, `get ${name}`);
50
+ if (descriptor.set) markNative(descriptor.set, `set ${name}`);
51
+ }
52
+ }
53
+
54
+ /**
55
+ * Marks every host-implemented API reachable from a page global before page
56
+ * script runs: global functions (with their prototypes and statics) and the
57
+ * objects behind global properties, following prototype chains up to the
58
+ * shared intrinsics, which are already native.
59
+ */
60
+ export function markNativeApi(root, { stop = [Object.prototype, Function.prototype] } = {}) {
61
+ const seen = new Set(stop);
62
+ const visit = (object, depth) => {
63
+ if (object === null || (typeof object !== "object" && typeof object !== "function") || seen.has(object)) return;
64
+ seen.add(object);
65
+ if (Array.isArray(object)) return;
66
+ markNativeMembers(object);
67
+ if (depth <= 0) return;
68
+ for (const key of Reflect.ownKeys(object)) {
69
+ const descriptor = Object.getOwnPropertyDescriptor(object, key);
70
+ if (!descriptor) continue;
71
+ if (!("value" in descriptor)) continue;
72
+ const value = descriptor.value;
73
+ if (typeof value === "function") {
74
+ visit(value, depth - 1);
75
+ if (value.prototype && typeof value.prototype === "object") visit(value.prototype, depth - 1);
76
+ } else if (value && typeof value === "object") {
77
+ visit(value, depth - 1);
78
+ }
79
+ }
80
+ visit(Object.getPrototypeOf(object), depth);
81
+ };
82
+ for (const key of Reflect.ownKeys(root)) {
83
+ const descriptor = Object.getOwnPropertyDescriptor(root, key);
84
+ if (!descriptor) continue;
85
+ if (descriptor.get) markNative(descriptor.get, `get ${memberName(key)}`);
86
+ if (descriptor.set) markNative(descriptor.set, `set ${memberName(key)}`);
87
+ const value = descriptor.value;
88
+ if (typeof value === "function") {
89
+ markNative(value, memberName(key));
90
+ visit(value, 2);
91
+ if (value.prototype && typeof value.prototype === "object") visit(value.prototype, 2);
92
+ } else if (value && typeof value === "object" && value !== root) {
93
+ visit(value, 2);
94
+ }
95
+ }
96
+ visit(Object.getPrototypeOf(root), 2);
97
+ }
98
+
99
+ const TRANSFORM_KEY = Symbol.for("ses.setFunctionSourceTextTransform");
100
+
101
+ /**
102
+ * Has Function.prototype.toString show the page's own source text for code
103
+ * the engine rewrote before evaluating it (see restoreSourceText). The
104
+ * patched SES bundle offers the setter once; later calls do nothing.
105
+ *
106
+ * @param {(text: string) => string} transform
107
+ */
108
+ export function setFunctionSourceTextTransform(transform) {
109
+ const setter = globalThis[TRANSFORM_KEY];
110
+ if (typeof setter !== "function") return;
111
+ delete globalThis[TRANSFORM_KEY];
112
+ setter(transform);
113
+ }