buninu-browser 0.2.3 → 0.2.4

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 (105) hide show
  1. package/lib/cdp-server.js +47 -14
  2. package/lib/happy-dom/document-base.js +33 -0
  3. package/lib/happy-dom/parser.js +15 -6
  4. package/lib/happy-dom/selector-matcher.js +2 -2
  5. package/lib/happy-dom/selector-pseudo-adapter.js +19 -0
  6. package/lib/happy-dom/svg-attribute-adapter.js +50 -0
  7. package/lib/headless-shell.js +9 -6
  8. package/lib/input/hit-test.js +67 -19
  9. package/lib/layout/text-layout.js +213 -91
  10. package/lib/media/byte-pipe.js +2 -2
  11. package/lib/media/position-audio.js +10 -1
  12. package/lib/media/source-snapshots.js +126 -0
  13. package/lib/media/video-keyframe.js +17 -4
  14. package/lib/media/webm-stream.js +106 -0
  15. package/lib/network/network-service.js +268 -120
  16. package/lib/paint/display-list.js +8 -28
  17. package/lib/paint/frame-cache.js +25 -0
  18. package/lib/paint/frame.js +42 -13
  19. package/lib/paint/paragraph-text.js +24 -0
  20. package/lib/paint/raster-readback.js +41 -0
  21. package/lib/paint/rounded-rect.js +82 -0
  22. package/lib/paint/shared-frame-pool.js +105 -0
  23. package/lib/render-tree/index.js +38 -6
  24. package/lib/render-tree/svg.js +15 -7
  25. package/lib/renderer/bindings.js +233 -80
  26. package/lib/renderer/broadcast-channel.js +76 -0
  27. package/lib/renderer/canvas-2d.js +257 -14
  28. package/lib/renderer/canvas-text.js +120 -0
  29. package/lib/renderer/compositor.js +13 -11
  30. package/lib/renderer/custom-element-reactions.js +25 -1
  31. package/lib/renderer/font-loading.js +227 -0
  32. package/lib/renderer/host.js +28 -4
  33. package/lib/renderer/image-resources.js +65 -0
  34. package/lib/renderer/interfaces.js +28 -0
  35. package/lib/renderer/layout-observers.js +236 -0
  36. package/lib/renderer/main.js +30 -4
  37. package/lib/renderer/media-source.js +126 -55
  38. package/lib/renderer/page-realm.js +270 -98
  39. package/lib/renderer/page-renderer.js +647 -235
  40. package/lib/renderer/prepared-script-cache.js +50 -0
  41. package/lib/renderer/rendered-text.js +108 -0
  42. package/lib/renderer/rewrite-worker.js +4 -3
  43. package/lib/renderer/script-rewrite.js +13 -2
  44. package/lib/renderer/scroll-into-view.js +28 -0
  45. package/lib/renderer/slot-assignment.js +60 -0
  46. package/lib/renderer/video-presentation.js +23 -4
  47. package/lib/renderer/workers.js +2 -2
  48. package/lib/style/computed-style.js +611 -104
  49. package/lib/style/selector-traits.js +49 -5
  50. package/package.json +1 -1
  51. package/test/broadcast-channel.test.js +26 -0
  52. package/test/canvas-2d.test.js +83 -2
  53. package/test/canvas-paint.test.js +9 -0
  54. package/test/canvas-text.test.js +41 -0
  55. package/test/cdp-stream.test.js +56 -0
  56. package/test/computed-style-binding.test.js +151 -0
  57. package/test/computed-style.test.js +258 -1
  58. package/test/display-list.test.js +12 -0
  59. package/test/document-base.test.js +87 -0
  60. package/test/dom-platform.test.js +461 -0
  61. package/test/fetch-lifecycle.test.js +161 -0
  62. package/test/fixtures/canvas-paint.js +117 -0
  63. package/test/fixtures/canvas-text.js +42 -0
  64. package/test/fixtures/image-lifetime.js +90 -0
  65. package/test/fixtures/media-seek-snapshot.js +77 -0
  66. package/test/fixtures/mse-audio-chirp.webm +0 -0
  67. package/test/fixtures/mse-switch-large.mp4 +0 -0
  68. package/test/fixtures/mse-switch-small.mp4 +0 -0
  69. package/test/fixtures/renderer-memory.js +110 -0
  70. package/test/fixtures/summary-style.js +28 -0
  71. package/test/font-loading.test.js +97 -0
  72. package/test/grid.test.js +58 -0
  73. package/test/hit-test.test.js +21 -1
  74. package/test/image-lifetime.test.js +12 -0
  75. package/test/image-resources.test.js +94 -0
  76. package/test/local-media.test.js +84 -0
  77. package/test/media-seek-snapshot.test.js +11 -0
  78. package/test/media-source-config.test.js +98 -0
  79. package/test/media-source-webm-accounting.test.js +98 -0
  80. package/test/media-source-webm.test.js +108 -0
  81. package/test/navigation.test.js +1 -0
  82. package/test/network-service.test.js +388 -0
  83. package/test/page-scripts.test.js +1 -1
  84. package/test/paint-frame.test.js +103 -0
  85. package/test/paint-paragraph-cache.test.js +87 -0
  86. package/test/position-audio.test.js +24 -0
  87. package/test/positioning.test.js +22 -0
  88. package/test/prepared-script-cache.test.js +45 -0
  89. package/test/pulse-client.test.js +10 -3
  90. package/test/raster-readback.test.js +40 -0
  91. package/test/render-tree.test.js +28 -0
  92. package/test/renderer-memory.test.js +11 -0
  93. package/test/selector-traits.test.js +24 -0
  94. package/test/shared-frame-pool.test.js +63 -0
  95. package/test/style-sheets.test.js +51 -0
  96. package/test/svg.test.js +29 -0
  97. package/test/text-layout.test.js +106 -0
  98. package/test/video-keyframe.test.js +61 -0
  99. package/test/video-presentation.test.js +69 -0
  100. package/tools/browser-lab.js +78 -0
  101. package/tools/browser-soak.js +75 -0
  102. package/tools/network-reference.js +49 -0
  103. package/tools/process-memory.js +38 -0
  104. package/tools/script-benchmark.js +25 -0
  105. package/ai-debug.log +0 -5537
@@ -0,0 +1,50 @@
1
+ import { escapeSesHtmlComments, rewriteClassicScriptSource } from "./script-rewrite.js";
2
+
3
+ /**
4
+ * Reuse deterministic source preparation across documents and repeated evals.
5
+ * Only strings/parse flags are retained, never a document, AST, or executable
6
+ * closure. Evaluation still happens in the current page's SES compartment.
7
+ */
8
+ export class PreparedScriptCache {
9
+ #entries = new Map();
10
+ #bytes = 0;
11
+ #maxBytes;
12
+ #maxEntries;
13
+ #prepare;
14
+
15
+ constructor({ maxBytes = 4 * 1024 * 1024, maxEntries = 128, prepare = prepareSource } = {}) {
16
+ this.#maxBytes = Math.max(0, maxBytes);
17
+ this.#maxEntries = Math.max(0, maxEntries);
18
+ this.#prepare = prepare;
19
+ }
20
+
21
+ prepare(source) {
22
+ const cached = this.#entries.get(source);
23
+ if (cached) {
24
+ this.#entries.delete(source);
25
+ this.#entries.set(source, cached);
26
+ return cached.result;
27
+ }
28
+ const result = Object.freeze(this.#prepare(source));
29
+ // Conservative UTF-16 accounting, including both the key and rewritten
30
+ // source, even when the engine can share their underlying string storage.
31
+ const bytes = (source.length + result.code.length) * 2;
32
+ if (!this.#maxEntries || bytes > this.#maxBytes) return result;
33
+ while (this.#entries.size && (this.#entries.size >= this.#maxEntries || this.#bytes + bytes > this.#maxBytes)) {
34
+ const key = this.#entries.keys().next().value;
35
+ this.#bytes -= this.#entries.get(key).bytes;
36
+ this.#entries.delete(key);
37
+ }
38
+ this.#entries.set(source, { result, bytes });
39
+ this.#bytes += bytes;
40
+ return result;
41
+ }
42
+ }
43
+
44
+ function prepareSource(source) {
45
+ const classic = rewriteClassicScriptSource(source);
46
+ return { code: escapeSesHtmlComments(classic.code), parsed: classic.parsed, strict: classic.strict };
47
+ }
48
+
49
+ const mainThreadCache = new PreparedScriptCache();
50
+ export const prepareClassicScript = source => mainThreadCache.prepare(source);
@@ -0,0 +1,108 @@
1
+ /** HTML innerText's rendered-text collection using the engine's computed styles. */
2
+ export function renderedText(element, computedStyle) {
3
+ if (!element.isConnected) return element.textContent;
4
+ const styles = new Map();
5
+ const styleOf = (node) => {
6
+ if (!styles.has(node)) styles.set(node, computedStyle(node));
7
+ return styles.get(node);
8
+ };
9
+ // An element outside rendering (including a display:none ancestor) returns
10
+ // raw descendant text. Hidden descendants of a rendered element do not.
11
+ for (let ancestor = element; ancestor; ancestor = ancestor.parentElement) {
12
+ if (styleOf(ancestor)?.display === "none") return element.textContent;
13
+ }
14
+ for (let child = element, ancestor = element.parentElement; ancestor; child = ancestor, ancestor = ancestor.parentElement) {
15
+ if (ancestor.tagName === "DETAILS" && !ancestor.open && child !== firstSummary(ancestor)) return "";
16
+ }
17
+ let output = "";
18
+ let breaks = 0;
19
+ let space = false;
20
+ const boundary = (count) => { breaks = Math.max(breaks, count); space = false; };
21
+ const beforeText = () => {
22
+ if (breaks && output) output += "\n".repeat(breaks);
23
+ else if (space && output && !output.endsWith("\n")) output += " ";
24
+ breaks = 0;
25
+ space = false;
26
+ };
27
+ const text = (value, style) => {
28
+ if (style?.visibility === "hidden" || style?.visibility === "collapse") return;
29
+ value = value.replace(/\r\n?/g, "\n");
30
+ if (style?.textTransform === "uppercase") value = value.toUpperCase();
31
+ else if (style?.textTransform === "lowercase") value = value.toLowerCase();
32
+ else if (style?.textTransform === "capitalize") value = value.replace(/(^|\s)\S/g, (part) => part.toUpperCase());
33
+ if (["pre", "pre-wrap", "break-spaces"].includes(style?.whiteSpace)) {
34
+ if (!value) return;
35
+ beforeText();
36
+ output += value;
37
+ return;
38
+ }
39
+ // Collapse across adjacent text nodes too: separate spans do not create
40
+ // extra spaces, and whitespace at block boundaries is not rendered.
41
+ const pattern = style?.whiteSpace === "pre-line" ? /([\t\f ]+|\n)/g : /([\t\f \n]+)/g;
42
+ for (const token of value.split(pattern)) {
43
+ if (!token) continue;
44
+ if (style?.whiteSpace === "pre-line" && token === "\n") {
45
+ beforeText();
46
+ output += "\n";
47
+ } else if (/^[\t\f \n]+$/.test(token)) space = !breaks;
48
+ else { beforeText(); output += token; }
49
+ }
50
+ };
51
+ const stack = [{ node: element, root: true }];
52
+ while (stack.length) {
53
+ const item = stack.pop();
54
+ if (item.after) {
55
+ if (item.tab) { beforeText(); output += "\t"; }
56
+ if (item.breaks) boundary(item.breaks);
57
+ continue;
58
+ }
59
+ const node = item.node;
60
+ if (node.nodeType === 3) { text(node.data, styleOf(node.parentElement)); continue; }
61
+ if (node.nodeType !== 1) continue;
62
+ // SVG descriptive/resource elements never paint their own text, even
63
+ // though getComputedStyle(title).display is "inline" in browsers.
64
+ if (node.namespaceURI === SVG_NAMESPACE && NON_RENDERED_SVG.has(node.localName)) continue;
65
+ const style = styleOf(node);
66
+ if (style?.display === "none") continue;
67
+ if (node.tagName === "BR") {
68
+ if (style?.visibility !== "hidden" && style?.visibility !== "collapse") {
69
+ beforeText(); output += "\n";
70
+ }
71
+ continue;
72
+ }
73
+ const count = item.root ? 0 : node.tagName === "P" ? 2
74
+ : BLOCK_DISPLAYS.has(style?.display) ? 1 : 0;
75
+ if (count) boundary(count);
76
+ const tab = style?.display === "table-cell" && node.nextElementSibling !== null;
77
+ if (count || tab) stack.push({ after: true, breaks: count, tab });
78
+ if (node.tagName === "DETAILS" && !node.open) {
79
+ const summary = firstSummary(node);
80
+ if (summary) stack.push({ node: summary });
81
+ continue;
82
+ }
83
+ for (let child = node.lastChild; child; child = child.previousSibling) stack.push({ node: child });
84
+ }
85
+ return output;
86
+ }
87
+
88
+ function firstSummary(details) {
89
+ for (const child of details.children) if (child.tagName === "SUMMARY") return child;
90
+ return null;
91
+ }
92
+
93
+ const BLOCK_DISPLAYS = new Set(["block", "flow-root", "flex", "grid", "list-item", "table", "table-caption", "table-row"]);
94
+ const SVG_NAMESPACE = "http://www.w3.org/2000/svg";
95
+ const NON_RENDERED_SVG = new Set(["title", "desc", "metadata", "defs", "symbol", "clipPath", "mask", "pattern",
96
+ "linearGradient", "radialGradient", "filter"]);
97
+
98
+ /** innerText assignment turns CR/LF line boundaries into BR elements. */
99
+ export function setRenderedText(element, value) {
100
+ const document = element.ownerDocument;
101
+ const fragment = document.createDocumentFragment();
102
+ const lines = String(value ?? "").split(/\r\n|[\r\n]/);
103
+ for (let index = 0; index < lines.length; index++) {
104
+ if (index) fragment.appendChild(document.createElement("br"));
105
+ if (lines[index]) fragment.appendChild(document.createTextNode(lines[index]));
106
+ }
107
+ element.replaceChildren(fragment);
108
+ }
@@ -7,13 +7,14 @@
7
7
  * Messages: { id, source } → { id, result: { code, parsed, strict } } or
8
8
  * { id, error }. The worker only transforms strings; page code never runs here.
9
9
  */
10
- import { escapeSesHtmlComments, rewriteClassicScriptSource } from "./script-rewrite.js";
10
+ import { PreparedScriptCache } from "./prepared-script-cache.js";
11
+
12
+ const preparedScripts = new PreparedScriptCache({ maxBytes: 16 * 1024 * 1024, maxEntries: 64 });
11
13
 
12
14
  self.addEventListener("message", ({ data }) => {
13
15
  const { id, source } = data ?? {};
14
16
  try {
15
- const classic = rewriteClassicScriptSource(String(source));
16
- self.postMessage({ id, result: { code: escapeSesHtmlComments(classic.code), parsed: classic.parsed, strict: classic.strict } });
17
+ self.postMessage({ id, result: preparedScripts.prepare(String(source)) });
17
18
  } catch (error) {
18
19
  self.postMessage({ id, error: String(error?.message ?? error) });
19
20
  }
@@ -118,8 +118,19 @@ export function rewriteClassicScript(source, parsed = null) {
118
118
  const text = (start, end) => {
119
119
  let result = "";
120
120
  let at = start;
121
- for (const edit of innerEdits) {
122
- if (edit.start < start || edit.end > end) continue;
121
+ // Function declarations and initializers usually span only a handful
122
+ // of edits. Locate that range instead of rescanning every edit in the
123
+ // bundle for each declaration (quadratic for many top-level functions).
124
+ let low = 0, high = innerEdits.length;
125
+ while (low < high) {
126
+ const middle = (low + high) >>> 1;
127
+ if (innerEdits[middle].start < start) low = middle + 1;
128
+ else high = middle;
129
+ }
130
+ for (let index = low; index < innerEdits.length; index++) {
131
+ const edit = innerEdits[index];
132
+ if (edit.start >= end) break;
133
+ if (edit.end > end) continue;
123
134
  result += `${source.slice(at, edit.start)}${edit.text}`;
124
135
  at = edit.end;
125
136
  }
@@ -0,0 +1,28 @@
1
+ /** CSSOM View's boolean/dictionary overload and logical alignment options. */
2
+ export function scrollIntoViewOptions(value = undefined) {
3
+ const dictionary = value !== null && (typeof value === "object" || typeof value === "function") ? value : {};
4
+ const { block: requestedBlock = "start", inline = "nearest", behavior = "auto", container = "all" } = dictionary;
5
+ const block = value !== null && value !== undefined && typeof value !== "object" && typeof value !== "function"
6
+ ? (Boolean(value) ? "start" : "end") : requestedBlock;
7
+ const options = {
8
+ block: String(block), inline: String(inline), behavior: String(behavior), container: String(container),
9
+ };
10
+ for (const key of ["block", "inline"]) {
11
+ if (!["start", "center", "end", "nearest"].includes(options[key])) throw new TypeError(`Invalid scrollIntoView ${key}`);
12
+ }
13
+ if (!["auto", "instant", "smooth"].includes(options.behavior)) throw new TypeError("Invalid scrollIntoView behavior");
14
+ if (!["all", "nearest"].includes(options.container)) throw new TypeError("Invalid scrollIntoView container");
15
+ return options;
16
+ }
17
+
18
+ /** Signed scroll distance for one axis, with the target relative to the scrollport. */
19
+ export function scrollAlignmentDelta(start, size, viewportSize, alignment) {
20
+ const end = start + size;
21
+ if (alignment === "start") return start;
22
+ if (alignment === "end") return end - viewportSize;
23
+ if (alignment === "center") return start + (size - viewportSize) / 2;
24
+ // Nearest leaves both fully visible and viewport-spanning boxes alone.
25
+ if ((start >= 0 && end <= viewportSize) || (start <= 0 && end >= viewportSize)) return 0;
26
+ const endDelta = end - viewportSize;
27
+ return Math.abs(start) < Math.abs(endDelta) ? start : endDelta;
28
+ }
@@ -0,0 +1,60 @@
1
+ // DOM slot assignment, shared by the page bindings and flattened-tree users.
2
+ // Keep only manual assignments: named slots follow the current tree directly.
3
+ const manualNodes = new WeakMap();
4
+ const manualOwner = new WeakMap();
5
+ const isSlottable = node => node?.nodeType === 1 || node?.nodeType === 3;
6
+
7
+ export function assignedSlot(node) {
8
+ const root = node?.parentElement?.shadowRoot;
9
+ if (!root || !isSlottable(node)) return null;
10
+ if (root.slotAssignment === "manual") {
11
+ const slot = manualOwner.get(node);
12
+ return slot?.getRootNode() === root ? slot : null;
13
+ }
14
+ const name = node.nodeType === 1 ? node.getAttribute("slot") ?? "" : "";
15
+ return [...root.querySelectorAll("slot")].find(slot => (slot.getAttribute("name") ?? "") === name) ?? null;
16
+ }
17
+
18
+ export function slotAssignedNodes(slot, flatten = false, visiting = new Set()) {
19
+ const root = slot.getRootNode();
20
+ if (!root.host || visiting.has(slot)) return [];
21
+ let nodes;
22
+ if (root.slotAssignment === "manual") {
23
+ nodes = (manualNodes.get(slot) ?? []).filter(node => node.parentNode === root.host);
24
+ } else {
25
+ const name = slot.getAttribute("name") ?? "";
26
+ const first = [...root.querySelectorAll("slot")].find(candidate => (candidate.getAttribute("name") ?? "") === name);
27
+ nodes = first !== slot ? [] : [...root.host.childNodes].filter(node => isSlottable(node)
28
+ && (node.nodeType === 1 ? node.getAttribute("slot") ?? "" : "") === name);
29
+ }
30
+ if (!flatten) return nodes;
31
+ visiting.add(slot);
32
+ const flattened = [];
33
+ if (!nodes.length) nodes = [...slot.childNodes].filter(isSlottable);
34
+ for (const node of nodes) {
35
+ if (node.localName === "slot" && node.getRootNode().host) flattened.push(...slotAssignedNodes(node, true, visiting));
36
+ else flattened.push(node);
37
+ }
38
+ visiting.delete(slot);
39
+ return flattened;
40
+ }
41
+
42
+ export function assignSlotNodes(slot, nodes, notify) {
43
+ if (nodes.some(node => !isSlottable(node))) throw new TypeError("Assigned nodes must be elements or text nodes");
44
+ if (slot.getRootNode().slotAssignment !== "manual") return;
45
+ const unique = [...new Set(nodes)];
46
+ const previous = manualNodes.get(slot) ?? [];
47
+ const changed = unique.length !== previous.length || unique.some((node, index) => node !== previous[index]);
48
+ if (!changed) return;
49
+ for (const node of previous) if (manualOwner.get(node) === slot) manualOwner.delete(node);
50
+ for (const node of unique) {
51
+ const old = manualOwner.get(node);
52
+ if (old && old !== slot) {
53
+ manualNodes.set(old, (manualNodes.get(old) ?? []).filter(item => item !== node));
54
+ notify(old);
55
+ }
56
+ manualOwner.set(node, slot);
57
+ }
58
+ manualNodes.set(slot, unique);
59
+ notify(slot);
60
+ }
@@ -20,7 +20,10 @@ export class VideoPresentation {
20
20
  const decoder = await this.engine.decode(config.bytes, config.options);
21
21
  if (epoch !== this.epoch) { decoder.close(); return; }
22
22
  this.decoder = decoder;
23
- this.nextTime = config.time > 0 ? seekVideoKeyframe(decoder, config.time) : 0;
23
+ const startTime = config.startTime ?? 0;
24
+ const endTime = config.endTime ?? Infinity;
25
+ this.nextTime = config.time >= endTime ? endTime
26
+ : config.time > startTime ? seekVideoKeyframe(decoder, config.time, startTime) : startTime;
24
27
  this.width = Math.max(1, Math.min(480, decoder.width));
25
28
  this.height = Math.max(1, Math.round(this.width * decoder.height / Math.max(1, decoder.width)));
26
29
  if (this.height > 480) {
@@ -51,9 +54,17 @@ export class VideoPresentation {
51
54
  // Audio stream identifiers increase monotonically within this renderer.
52
55
  if (this.clocks.size > 32) this.clocks.delete(this.clocks.keys().next().value);
53
56
  }
54
- bind(id, audioId, audioStart, time) {
57
+ bind(id, audioId, audioStart, time, endTime) {
55
58
  if (this.config?.id !== id) return;
56
- Object.assign(this.config, { audioId, audioStart, time });
59
+ if (audioId !== undefined) this.config.audioId = audioId;
60
+ if (audioStart !== undefined) this.config.audioStart = audioStart;
61
+ if (time !== undefined) Object.assign(this.config, { time, started: performance.now() });
62
+ if (endTime !== undefined) {
63
+ this.config.endTime = endTime;
64
+ // These frames may have been queued before the next init's first
65
+ // media segment established where the old configuration stops.
66
+ this.queue = this.queue.filter(frame => frame.time + 1e-6 < endTime);
67
+ }
57
68
  }
58
69
  time() {
59
70
  const c = this.config;
@@ -65,9 +76,11 @@ export class VideoPresentation {
65
76
  step() {
66
77
  if (!this.decoder || !this.config) return;
67
78
  const time = this.time();
79
+ const endTime = this.config.endTime ?? Infinity;
68
80
  const deadline = performance.now() + 8;
69
81
  try {
70
- while (!this.eof && this.queue.length < this.limit && this.nextTime <= time + 0.75) {
82
+ while (!this.eof && this.queue.length < this.limit && this.nextTime <= time + 0.75
83
+ && this.nextTime + 1e-6 < endTime) {
71
84
  const pixels = this.decoder.nextFrame(this.width, this.height);
72
85
  if (!pixels) { this.eof = true; break; }
73
86
  const frame = { pixels, time: this.nextTime, width: this.width, height: this.height };
@@ -82,6 +95,12 @@ export class VideoPresentation {
82
95
  // owns network/refill policy and decides whether this is EOF or an error.
83
96
  this.eof = true;
84
97
  }
98
+ // A later init can replace buffered frames before this snapshot's EOF.
99
+ // Finish at that media boundary; the renderer opens its next snapshot.
100
+ if (this.nextTime + 1e-6 >= endTime) {
101
+ this.eof = true;
102
+ this.nextTime = endTime;
103
+ }
85
104
  let latest = null;
86
105
  while (this.queue.length && this.queue[0].time <= time + 1e-6) latest = this.queue.shift();
87
106
  if (latest) {
@@ -57,7 +57,7 @@ const hostClearTimeout = globalThis.clearTimeout;
57
57
  * @returns {{ Worker: Function, dispose(): void }}
58
58
  */
59
59
  export function createWorkers({
60
- realm, documentUrl, fetchScript, runTask, callPage, reportError, DOMException, globals, constructorHelper, pagePorts,
60
+ realm, documentUrl, documentBaseUrl = documentUrl, fetchScript, runTask, callPage, reportError, DOMException, globals, constructorHelper, pagePorts,
61
61
  }) {
62
62
  const live = new Set();
63
63
 
@@ -94,7 +94,7 @@ export function createWorkers({
94
94
  if (!new.target) throw new TypeError("Constructor Worker requires 'new'");
95
95
  let url;
96
96
  try {
97
- url = new URL(String(scriptURL), documentUrl());
97
+ url = new URL(String(scriptURL), documentBaseUrl());
98
98
  } catch {
99
99
  throw new DOMException(`Failed to construct 'Worker': invalid URL '${scriptURL}'`, "SyntaxError");
100
100
  }