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.
- package/lib/cdp-server.js +47 -14
- package/lib/happy-dom/document-base.js +33 -0
- package/lib/happy-dom/parser.js +15 -6
- package/lib/happy-dom/selector-matcher.js +2 -2
- package/lib/happy-dom/selector-pseudo-adapter.js +19 -0
- package/lib/happy-dom/svg-attribute-adapter.js +50 -0
- package/lib/headless-shell.js +9 -6
- package/lib/input/hit-test.js +67 -19
- package/lib/layout/text-layout.js +213 -91
- package/lib/media/byte-pipe.js +2 -2
- package/lib/media/position-audio.js +10 -1
- package/lib/media/source-snapshots.js +126 -0
- package/lib/media/video-keyframe.js +17 -4
- package/lib/media/webm-stream.js +106 -0
- package/lib/network/network-service.js +268 -120
- package/lib/paint/display-list.js +8 -28
- package/lib/paint/frame-cache.js +25 -0
- package/lib/paint/frame.js +42 -13
- package/lib/paint/paragraph-text.js +24 -0
- package/lib/paint/raster-readback.js +41 -0
- package/lib/paint/rounded-rect.js +82 -0
- package/lib/paint/shared-frame-pool.js +105 -0
- package/lib/render-tree/index.js +38 -6
- package/lib/render-tree/svg.js +15 -7
- package/lib/renderer/bindings.js +233 -80
- package/lib/renderer/broadcast-channel.js +76 -0
- package/lib/renderer/canvas-2d.js +257 -14
- package/lib/renderer/canvas-text.js +120 -0
- package/lib/renderer/compositor.js +13 -11
- package/lib/renderer/custom-element-reactions.js +25 -1
- package/lib/renderer/font-loading.js +227 -0
- package/lib/renderer/host.js +28 -4
- package/lib/renderer/image-resources.js +65 -0
- package/lib/renderer/interfaces.js +28 -0
- package/lib/renderer/layout-observers.js +236 -0
- package/lib/renderer/main.js +30 -4
- package/lib/renderer/media-source.js +126 -55
- package/lib/renderer/page-realm.js +270 -98
- package/lib/renderer/page-renderer.js +647 -235
- package/lib/renderer/prepared-script-cache.js +50 -0
- package/lib/renderer/rendered-text.js +108 -0
- package/lib/renderer/rewrite-worker.js +4 -3
- package/lib/renderer/script-rewrite.js +13 -2
- package/lib/renderer/scroll-into-view.js +28 -0
- package/lib/renderer/slot-assignment.js +60 -0
- package/lib/renderer/video-presentation.js +23 -4
- package/lib/renderer/workers.js +2 -2
- package/lib/style/computed-style.js +611 -104
- package/lib/style/selector-traits.js +49 -5
- package/package.json +1 -1
- package/test/broadcast-channel.test.js +26 -0
- package/test/canvas-2d.test.js +83 -2
- package/test/canvas-paint.test.js +9 -0
- package/test/canvas-text.test.js +41 -0
- package/test/cdp-stream.test.js +56 -0
- package/test/computed-style-binding.test.js +151 -0
- package/test/computed-style.test.js +258 -1
- package/test/display-list.test.js +12 -0
- package/test/document-base.test.js +87 -0
- package/test/dom-platform.test.js +461 -0
- package/test/fetch-lifecycle.test.js +161 -0
- package/test/fixtures/canvas-paint.js +117 -0
- package/test/fixtures/canvas-text.js +42 -0
- package/test/fixtures/image-lifetime.js +90 -0
- package/test/fixtures/media-seek-snapshot.js +77 -0
- package/test/fixtures/mse-audio-chirp.webm +0 -0
- package/test/fixtures/mse-switch-large.mp4 +0 -0
- package/test/fixtures/mse-switch-small.mp4 +0 -0
- package/test/fixtures/renderer-memory.js +110 -0
- package/test/fixtures/summary-style.js +28 -0
- package/test/font-loading.test.js +97 -0
- package/test/grid.test.js +58 -0
- package/test/hit-test.test.js +21 -1
- package/test/image-lifetime.test.js +12 -0
- package/test/image-resources.test.js +94 -0
- package/test/local-media.test.js +84 -0
- package/test/media-seek-snapshot.test.js +11 -0
- package/test/media-source-config.test.js +98 -0
- package/test/media-source-webm-accounting.test.js +98 -0
- package/test/media-source-webm.test.js +108 -0
- package/test/navigation.test.js +1 -0
- package/test/network-service.test.js +388 -0
- package/test/page-scripts.test.js +1 -1
- package/test/paint-frame.test.js +103 -0
- package/test/paint-paragraph-cache.test.js +87 -0
- package/test/position-audio.test.js +24 -0
- package/test/positioning.test.js +22 -0
- package/test/prepared-script-cache.test.js +45 -0
- package/test/pulse-client.test.js +10 -3
- package/test/raster-readback.test.js +40 -0
- package/test/render-tree.test.js +28 -0
- package/test/renderer-memory.test.js +11 -0
- package/test/selector-traits.test.js +24 -0
- package/test/shared-frame-pool.test.js +63 -0
- package/test/style-sheets.test.js +51 -0
- package/test/svg.test.js +29 -0
- package/test/text-layout.test.js +106 -0
- package/test/video-keyframe.test.js +61 -0
- package/test/video-presentation.test.js +69 -0
- package/tools/browser-lab.js +78 -0
- package/tools/browser-soak.js +75 -0
- package/tools/network-reference.js +49 -0
- package/tools/process-memory.js +38 -0
- package/tools/script-benchmark.js +25 -0
- package/ai-debug.log +0 -5537
|
@@ -20,16 +20,20 @@ import {
|
|
|
20
20
|
DIRECT_EVAL_SCOPE_HELPER, REFERENCE_HELPER, referenceHelper, restoreSourceText, THIS_HELPER,
|
|
21
21
|
rewriteClassicScriptSource, rewriteConstructorReads, rewriteModuleSource,
|
|
22
22
|
} from "./script-rewrite.js";
|
|
23
|
+
import { prepareClassicScript } from "./prepared-script-cache.js";
|
|
23
24
|
import { mediaQueryMatches, resolveVariables } from "../style/computed-style.js";
|
|
24
25
|
import { asIncumbent, createMessaging } from "./messaging.js";
|
|
25
|
-
import { canvasContext, resetCanvas } from "./canvas-2d.js";
|
|
26
|
+
import { canvasContext, resetCanvas, disposeDocumentCanvases, encodeCanvas } from "./canvas-2d.js";
|
|
26
27
|
import { createWorkers } from "./workers.js";
|
|
27
28
|
import { createMediaSourceScope } from "./media-source.js";
|
|
28
29
|
import { markNative, markNativeApi, setFunctionSourceTextTransform } from "./native-functions.js";
|
|
29
|
-
import { applyGlobalShape, platformObject, storageInterface } from "./interfaces.js";
|
|
30
|
+
import { applyGlobalShape, errorInterface, platformObject, storageInterface } from "./interfaces.js";
|
|
30
31
|
import { trustedTypesApi } from "./trusted-types.js";
|
|
31
32
|
import { onPromiseRejection } from "./rejections.js";
|
|
32
33
|
import { installAttributeReactions } from "./custom-element-reactions.js";
|
|
34
|
+
import { createLayoutObservers } from "./layout-observers.js";
|
|
35
|
+
import { createBroadcastChannels } from "./broadcast-channel.js";
|
|
36
|
+
import { createInertWindow } from "../happy-dom/parser.js";
|
|
33
37
|
import { historyBinding } from "./history.js";
|
|
34
38
|
import { installImageLoadingAttributes } from "./image-loading.js";
|
|
35
39
|
import { AsyncLocalStorage } from "node:async_hooks";
|
|
@@ -64,6 +68,9 @@ const hostCrypto = globalThis.crypto;
|
|
|
64
68
|
const hostStructuredClone = globalThis.structuredClone;
|
|
65
69
|
const hostAtob = globalThis.atob;
|
|
66
70
|
const hostBtoa = globalThis.btoa;
|
|
71
|
+
const HostHeaders = globalThis.Headers;
|
|
72
|
+
const HostRequest = globalThis.Request;
|
|
73
|
+
const HostResponse = globalThis.Response;
|
|
67
74
|
|
|
68
75
|
export const CLASSIC_SCRIPT_TYPES = new Set([
|
|
69
76
|
"", "text/javascript", "application/javascript", "text/ecmascript", "application/ecmascript",
|
|
@@ -153,7 +160,11 @@ const PLAIN_HTML_ELEMENTS = new Set(["ABBR", "ADDRESS", "ARTICLE", "ASIDE", "B",
|
|
|
153
160
|
*/
|
|
154
161
|
const FORM_CONTROLS = ["HTMLInputElement", "HTMLSelectElement", "HTMLTextAreaElement", "HTMLButtonElement"];
|
|
155
162
|
const URL_ELEMENTS = ["HTMLAnchorElement", "HTMLAreaElement"];
|
|
163
|
+
const VALIDATED_ELEMENTS = [...FORM_CONTROLS, "HTMLFieldSetElement", "HTMLObjectElement", "HTMLOutputElement"];
|
|
156
164
|
const INTERFACE_MEMBERS = {
|
|
165
|
+
assignedNodes: ["HTMLSlotElement"], assignedElements: ["HTMLSlotElement"], assign: ["HTMLSlotElement"],
|
|
166
|
+
validity: VALIDATED_ELEMENTS, setCustomValidity: VALIDATED_ELEMENTS,
|
|
167
|
+
checkValidity: [...VALIDATED_ELEMENTS, "HTMLFormElement"], reportValidity: [...VALIDATED_ELEMENTS, "HTMLFormElement"],
|
|
157
168
|
contentWindow: ["HTMLIFrameElement", "HTMLObjectElement", "HTMLFrameElement"],
|
|
158
169
|
contentDocument: ["HTMLIFrameElement", "HTMLObjectElement", "HTMLFrameElement"],
|
|
159
170
|
content: ["HTMLTemplateElement"],
|
|
@@ -202,6 +213,9 @@ const WINDOW_HANDLER_EVENTS = [
|
|
|
202
213
|
"popstate", "beforeunload", "unload", "focus", "blur", "online", "offline", "unhandledrejection", "rejectionhandled",
|
|
203
214
|
];
|
|
204
215
|
const hostStorage = new Map();
|
|
216
|
+
// SES's older intrinsic allow-list removes this standard method during
|
|
217
|
+
// lockdown; retain Bun's native brand check without replacing tamed errors.
|
|
218
|
+
const hostErrorIsError = Error.isError;
|
|
205
219
|
|
|
206
220
|
function cssEscape(value) {
|
|
207
221
|
const input = String(value);
|
|
@@ -265,6 +279,7 @@ export function createPageRealm({ window, document, mode, hooks }) {
|
|
|
265
279
|
const consoleEntries = [];
|
|
266
280
|
let nextTimerId = 1;
|
|
267
281
|
let disposed = false;
|
|
282
|
+
const pendingPageFetches = new Set();
|
|
268
283
|
let readyState = "loading";
|
|
269
284
|
// A document gets a fresh high-resolution time origin. Bun's
|
|
270
285
|
// performance.now() is relative to the renderer process, which is commonly
|
|
@@ -295,8 +310,7 @@ export function createPageRealm({ window, document, mode, hooks }) {
|
|
|
295
310
|
let inlineModuleCount = 0;
|
|
296
311
|
|
|
297
312
|
const reportError = (error, source = currentScript?.src) => {
|
|
298
|
-
|
|
299
|
-
const detail = error?.stack || (error?.name ? `${error.name}: ${error.message}` : String(error?.message ?? error));
|
|
313
|
+
const detail = diagnosticError(error);
|
|
300
314
|
const message = source ? `${source}: ${detail}` : detail;
|
|
301
315
|
consoleEntries.push({ level: "error", text: message });
|
|
302
316
|
hooks.reportError(message);
|
|
@@ -332,7 +346,9 @@ export function createPageRealm({ window, document, mode, hooks }) {
|
|
|
332
346
|
};
|
|
333
347
|
|
|
334
348
|
const realm = new BindingRealm(window, {
|
|
335
|
-
touch:
|
|
349
|
+
touch: (node, features) => {
|
|
350
|
+
if (!node || node === document || node.ownerDocument === document) hooks.touch(node, features);
|
|
351
|
+
},
|
|
336
352
|
boundsOf: hooks.boundsOf,
|
|
337
353
|
computedStyle: hooks.computedStyle,
|
|
338
354
|
// The engine clock's reading when this document's timeline started (see pageNow).
|
|
@@ -376,6 +392,14 @@ export function createPageRealm({ window, document, mode, hooks }) {
|
|
|
376
392
|
(value) => value === pageGlobal || Boolean(nodeOf(value)),
|
|
377
393
|
(value) => value === pageGlobal ? window : nodeOf(value));
|
|
378
394
|
const documentBinding = realm.wrap(document);
|
|
395
|
+
const DOMStringMap = function DOMStringMap() { throw new TypeError("Illegal constructor"); };
|
|
396
|
+
realm.datasetPrototype = DOMStringMap.prototype;
|
|
397
|
+
const validityMembers = {};
|
|
398
|
+
for (const key of ["badInput", "customError", "patternMismatch", "rangeOverflow", "rangeUnderflow", "stepMismatch", "tooLong", "tooShort", "typeMismatch", "valid", "valueMissing"]) {
|
|
399
|
+
Object.defineProperty(validityMembers, key, { get() { return Boolean(this[key]); }, enumerable: true });
|
|
400
|
+
}
|
|
401
|
+
const validity = platformObject("ValidityState", validityMembers);
|
|
402
|
+
realm.wrapValidity = validity.create;
|
|
379
403
|
const NodeInterface = function Node() { throw new TypeError("Illegal constructor"); };
|
|
380
404
|
// Node.prototype inherits EventTarget.prototype (DOM §4.4), as Window.prototype does.
|
|
381
405
|
NodeInterface.prototype = mutableBindingPrototype(INTERFACES.Node.prototype, eventTargets.EventTarget.prototype);
|
|
@@ -557,7 +581,27 @@ export function createPageRealm({ window, document, mode, hooks }) {
|
|
|
557
581
|
enumerable: true,
|
|
558
582
|
writable: true,
|
|
559
583
|
value(type) {
|
|
560
|
-
return canvasContext(nodeOf(this), String(type)
|
|
584
|
+
return canvasContext(nodeOf(this), String(type), {
|
|
585
|
+
canvas: this, resolveImage: source => hooks.canvasImage?.(nodeOf(source)),
|
|
586
|
+
parseFont: value => hooks.canvasFont?.(nodeOf(this), value),
|
|
587
|
+
text: request => hooks.canvasText?.(nodeOf(this), request),
|
|
588
|
+
});
|
|
589
|
+
},
|
|
590
|
+
});
|
|
591
|
+
Object.defineProperty(canvasPrototype, "toDataURL", {
|
|
592
|
+
configurable: true, enumerable: true, writable: true,
|
|
593
|
+
value(type, quality) {
|
|
594
|
+
const encoded = encodeCanvas(nodeOf(this), type, quality);
|
|
595
|
+
return encoded ? `data:${encoded.type};base64,${encoded.bytes.toBase64()}` : "data:,";
|
|
596
|
+
},
|
|
597
|
+
});
|
|
598
|
+
Object.defineProperty(canvasPrototype, "toBlob", {
|
|
599
|
+
configurable: true, enumerable: true, writable: true,
|
|
600
|
+
value(callback, type, quality) {
|
|
601
|
+
if (typeof callback !== "function") throw new TypeError("Canvas toBlob requires a callback");
|
|
602
|
+
const encoded = encodeCanvas(nodeOf(this), type, quality);
|
|
603
|
+
const blob = encoded ? new Blob([encoded.bytes], { type: encoded.type }) : null;
|
|
604
|
+
hostSetTimeout(() => { if (!disposed) runTask(() => callPage(callback, pageGlobal, [blob])); }, 0);
|
|
561
605
|
},
|
|
562
606
|
});
|
|
563
607
|
// HTMLMediaElement. HAVE_* / NETWORK_* / MEDIA_ERR_* live on the prototype
|
|
@@ -634,6 +678,33 @@ export function createPageRealm({ window, document, mode, hooks }) {
|
|
|
634
678
|
DocumentInterface.prototype = withParentNodeMembers(mutableBindingPrototype(INTERFACES.Document.prototype, parentPrototype));
|
|
635
679
|
Object.defineProperty(DocumentInterface.prototype, "constructor", { value: DocumentInterface, writable: true, configurable: true });
|
|
636
680
|
Object.setPrototypeOf(documentBinding, DocumentInterface.prototype);
|
|
681
|
+
realm.documentPrototype = DocumentInterface.prototype;
|
|
682
|
+
let parserWindow = null;
|
|
683
|
+
const parsers = new WeakSet();
|
|
684
|
+
class DOMParser {
|
|
685
|
+
constructor() { parsers.add(this); }
|
|
686
|
+
parseFromString(source, type) {
|
|
687
|
+
if (!parsers.has(this)) throw new TypeError("Illegal invocation");
|
|
688
|
+
const mime = String(type);
|
|
689
|
+
if (!["text/html", "text/xml", "application/xml", "application/xhtml+xml", "image/svg+xml"].includes(mime)) {
|
|
690
|
+
throw new TypeError("Invalid DOMParser MIME type.");
|
|
691
|
+
}
|
|
692
|
+
parserWindow ??= createInertWindow(document.URL);
|
|
693
|
+
const parsed = new parserWindow.DOMParser().parseFromString(String(source), mime);
|
|
694
|
+
Object.defineProperty(parsed, "contentType", { value: mime, configurable: true });
|
|
695
|
+
return realm.wrap(parsed);
|
|
696
|
+
}
|
|
697
|
+
}
|
|
698
|
+
const serializers = new WeakSet();
|
|
699
|
+
class XMLSerializer {
|
|
700
|
+
constructor() { serializers.add(this); }
|
|
701
|
+
serializeToString(root) {
|
|
702
|
+
if (!serializers.has(this)) throw new TypeError("Illegal invocation");
|
|
703
|
+
const node = nodeOf(root);
|
|
704
|
+
if (!node) throw new TypeError("XMLSerializer requires a Node.");
|
|
705
|
+
return new window.XMLSerializer().serializeToString(node);
|
|
706
|
+
}
|
|
707
|
+
}
|
|
637
708
|
|
|
638
709
|
/** In an iframe's document, parent and top are the embedding frames' windows. */
|
|
639
710
|
const defineFrameWindows = (global) => {
|
|
@@ -718,12 +789,12 @@ export function createPageRealm({ window, document, mode, hooks }) {
|
|
|
718
789
|
};
|
|
719
790
|
Object.defineProperty(pageConsole, Symbol.toStringTag, { value: "console", configurable: true });
|
|
720
791
|
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
792
|
+
const layoutObservers = createLayoutObservers({
|
|
793
|
+
nodeOf, boundsOf: hooks.boundsOf, computedStyle: hooks.computedStyle, viewport: hooks.viewport,
|
|
794
|
+
schedule: (callback) => schedule(callback, 0, [], false), cancel, call: callPage, now: pageNow,
|
|
795
|
+
});
|
|
796
|
+
|
|
797
|
+
/** MutationObserver wraps Happy DOM records; layout observers use renderer geometry. */
|
|
727
798
|
function observerConstructors() {
|
|
728
799
|
function MutationObserver(callback) {
|
|
729
800
|
if (!new.target) throw new TypeError("Constructor MutationObserver requires 'new'");
|
|
@@ -759,48 +830,8 @@ export function createPageRealm({ window, document, mode, hooks }) {
|
|
|
759
830
|
return self;
|
|
760
831
|
}
|
|
761
832
|
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
const self = {};
|
|
765
|
-
const observed = new Set();
|
|
766
|
-
self.observe = (target) => {
|
|
767
|
-
const node = nodeOf(target);
|
|
768
|
-
if (!node || observed.has(node)) return;
|
|
769
|
-
observed.add(node);
|
|
770
|
-
schedule(() => {
|
|
771
|
-
if (!observed.has(node)) return;
|
|
772
|
-
const rect = hooks.boundsOf(node) ?? { left: 0, top: 0, width: 0, height: 0 };
|
|
773
|
-
callPage(callback, self, [[entryFor(target, rect)], self]);
|
|
774
|
-
}, 0, [], false);
|
|
775
|
-
};
|
|
776
|
-
self.unobserve = (target) => observed.delete(nodeOf(target));
|
|
777
|
-
self.disconnect = () => observed.clear();
|
|
778
|
-
self.takeRecords = () => [];
|
|
779
|
-
return self;
|
|
780
|
-
};
|
|
781
|
-
const rectangle = ({ left, top, width, height }) => ({
|
|
782
|
-
x: left, y: top, left, top, width, height, right: left + width, bottom: top + height,
|
|
783
|
-
});
|
|
784
|
-
const ResizeObserver = layoutObserver("ResizeObserver", (target, rect) => ({
|
|
785
|
-
target,
|
|
786
|
-
contentRect: rectangle({ ...rect, left: 0, top: 0 }),
|
|
787
|
-
borderBoxSize: [{ inlineSize: rect.width, blockSize: rect.height }],
|
|
788
|
-
contentBoxSize: [{ inlineSize: rect.width, blockSize: rect.height }],
|
|
789
|
-
}));
|
|
790
|
-
const IntersectionObserver = layoutObserver("IntersectionObserver", (target, rect) => {
|
|
791
|
-
const { width, height } = hooks.viewport();
|
|
792
|
-
const visible = rect.top < height && rect.top + rect.height > 0 && rect.left < width && rect.left + rect.width > 0;
|
|
793
|
-
return {
|
|
794
|
-
target,
|
|
795
|
-
time: pageNow(),
|
|
796
|
-
isIntersecting: visible,
|
|
797
|
-
intersectionRatio: visible ? 1 : 0,
|
|
798
|
-
boundingClientRect: rectangle(rect),
|
|
799
|
-
intersectionRect: rectangle(visible ? rect : { left: 0, top: 0, width: 0, height: 0 }),
|
|
800
|
-
rootBounds: rectangle({ left: 0, top: 0, width, height }),
|
|
801
|
-
};
|
|
802
|
-
});
|
|
803
|
-
return { MutationObserver, ResizeObserver, IntersectionObserver };
|
|
833
|
+
return { MutationObserver, ResizeObserver: layoutObservers.ResizeObserver,
|
|
834
|
+
IntersectionObserver: layoutObservers.IntersectionObserver };
|
|
804
835
|
}
|
|
805
836
|
|
|
806
837
|
/**
|
|
@@ -920,33 +951,62 @@ export function createPageRealm({ window, document, mode, hooks }) {
|
|
|
920
951
|
}
|
|
921
952
|
|
|
922
953
|
function networkApi() {
|
|
954
|
+
// Instrumentation libraries wrap Headers.prototype.set/append. Keep
|
|
955
|
+
// those overrides local to this document: the renderer's own Headers
|
|
956
|
+
// intrinsic remains frozen and is never modified by a page.
|
|
957
|
+
const PageHeaders = class Headers extends HostHeaders {};
|
|
958
|
+
for (const key of Reflect.ownKeys(HostHeaders.prototype)) {
|
|
959
|
+
if (key === "constructor") continue;
|
|
960
|
+
const descriptor = Object.getOwnPropertyDescriptor(HostHeaders.prototype, key);
|
|
961
|
+
Object.defineProperty(PageHeaders.prototype, key, {
|
|
962
|
+
...descriptor, configurable: true, ...("value" in descriptor ? { writable: true } : {}),
|
|
963
|
+
});
|
|
964
|
+
}
|
|
965
|
+
const exposeHeaders = (headers) => Object.getPrototypeOf(headers) === PageHeaders.prototype ? headers
|
|
966
|
+
: Object.setPrototypeOf(headers, PageHeaders.prototype);
|
|
967
|
+
const PageResponse = class Response extends HostResponse {
|
|
968
|
+
get headers() { return exposeHeaders(responseHeaders.call(this)); }
|
|
969
|
+
clone() { return Object.setPrototypeOf(HostResponse.prototype.clone.call(this), PageResponse.prototype); }
|
|
970
|
+
static error() { return Object.setPrototypeOf(HostResponse.error(), PageResponse.prototype); }
|
|
971
|
+
static redirect(url, status = 302) { return Object.setPrototypeOf(HostResponse.redirect(url, status), PageResponse.prototype); }
|
|
972
|
+
static json(data, init = undefined) { return Object.setPrototypeOf(HostResponse.json(data, init), PageResponse.prototype); }
|
|
973
|
+
};
|
|
974
|
+
const responseHeaders = Object.getOwnPropertyDescriptor(HostResponse.prototype, "headers").get;
|
|
923
975
|
const abortError = () => new DOMException("The operation was aborted.", "AbortError");
|
|
924
976
|
const baseUrl = () => document.baseURI || document.URL;
|
|
925
977
|
|
|
926
978
|
// new Request("/api") resolves against the document's base URL (Fetch
|
|
927
979
|
// §5.4), which the host's Request has no document to do.
|
|
928
|
-
const PageRequest = class Request extends
|
|
980
|
+
const PageRequest = class Request extends HostRequest {
|
|
929
981
|
constructor(input, init = undefined) {
|
|
930
|
-
super(input instanceof
|
|
982
|
+
super(input instanceof HostRequest ? input : new URL(String(input), baseUrl()).href, init);
|
|
931
983
|
// Fetch §5.4 step 36: a string body gives the request its Content-Type
|
|
932
984
|
// (Bun's Request only does this for the other body kinds).
|
|
933
|
-
if (typeof init?.body === "string"
|
|
934
|
-
|
|
985
|
+
if (typeof init?.body === "string") {
|
|
986
|
+
const headers = requestHeaders.call(this);
|
|
987
|
+
if (!HostHeaders.prototype.has.call(headers, "content-type")) {
|
|
988
|
+
HostHeaders.prototype.set.call(headers, "content-type", "text/plain;charset=UTF-8");
|
|
989
|
+
}
|
|
935
990
|
}
|
|
936
991
|
}
|
|
992
|
+
clone() { return Object.setPrototypeOf(HostRequest.prototype.clone.call(this), PageRequest.prototype); }
|
|
937
993
|
};
|
|
994
|
+
const requestHeaders = Object.getOwnPropertyDescriptor(HostRequest.prototype, "headers").get;
|
|
995
|
+
Object.defineProperty(PageRequest.prototype, "headers", {
|
|
996
|
+
get() { return exposeHeaders(requestHeaders.call(this)); }, enumerable: true, configurable: true,
|
|
997
|
+
});
|
|
938
998
|
|
|
939
999
|
// fetch(request) reads the Request's internal state, as Chromium's
|
|
940
1000
|
// Request::CreateRequestWithRequestOrString does with GetRequest(). Page
|
|
941
1001
|
// script may shadow url, method or bodyUsed on the instance; that changes
|
|
942
1002
|
// what script reads, not what is fetched.
|
|
943
1003
|
const requestSlot = (name) => {
|
|
944
|
-
const get = Object.getOwnPropertyDescriptor(
|
|
1004
|
+
const get = Object.getOwnPropertyDescriptor(HostRequest.prototype, name).get;
|
|
945
1005
|
return (request) => get.call(request);
|
|
946
1006
|
};
|
|
947
1007
|
const slots = Object.fromEntries(["url", "method", "headers", "mode", "credentials", "redirect", "signal", "bodyUsed"]
|
|
948
1008
|
.map((name) => [name, requestSlot(name)]));
|
|
949
|
-
const cloneRequest =
|
|
1009
|
+
const cloneRequest = HostRequest.prototype.clone;
|
|
950
1010
|
/** The input when it is a platform Request (a brand check, not instanceof). */
|
|
951
1011
|
const platformRequest = (input) => {
|
|
952
1012
|
if (input === null || typeof input !== "object") return null;
|
|
@@ -992,10 +1052,13 @@ export function createPageRealm({ window, document, mode, hooks }) {
|
|
|
992
1052
|
}
|
|
993
1053
|
|
|
994
1054
|
function makeResponse(data) {
|
|
995
|
-
const headers = new
|
|
1055
|
+
const headers = new PageHeaders(data.headers ?? []);
|
|
996
1056
|
const body = data.body instanceof ReadableStream ? data.body
|
|
997
1057
|
: data.body instanceof Uint8Array ? data.body : new Uint8Array(0);
|
|
998
|
-
|
|
1058
|
+
// Fetch reads the header list internally; page instrumentation of the
|
|
1059
|
+
// public Headers prototype must not affect response construction.
|
|
1060
|
+
const contentType = HostHeaders.prototype.get.call(headers, "content-type") ?? "";
|
|
1061
|
+
const native = new Response(body, { headers: { "content-type": contentType } });
|
|
999
1062
|
const response = {
|
|
1000
1063
|
type: data.type ?? "basic",
|
|
1001
1064
|
url: data.url ?? "",
|
|
@@ -1027,7 +1090,7 @@ export function createPageRealm({ window, document, mode, hooks }) {
|
|
|
1027
1090
|
async function pageFetch(input, init = {}) {
|
|
1028
1091
|
const request = await describeRequest(input, init ?? {});
|
|
1029
1092
|
const signal = request.signal;
|
|
1030
|
-
if (signal?.aborted) throw abortError();
|
|
1093
|
+
if (disposed || signal?.aborted) throw signal?.reason ?? abortError();
|
|
1031
1094
|
const scheme = new URL(request.url).protocol;
|
|
1032
1095
|
let pending;
|
|
1033
1096
|
let networkPending = null;
|
|
@@ -1050,21 +1113,38 @@ export function createPageRealm({ window, document, mode, hooks }) {
|
|
|
1050
1113
|
} else {
|
|
1051
1114
|
const { signal: _signal, ...plain } = request;
|
|
1052
1115
|
networkPending = hooks.pageFetch(plain);
|
|
1116
|
+
pendingPageFetches.add(networkPending);
|
|
1053
1117
|
// Network and CORS failures reach the page as a bare TypeError, as in
|
|
1054
1118
|
// browsers; the reason is only logged to the console.
|
|
1055
1119
|
pending = networkPending.catch((error) => {
|
|
1056
1120
|
const text = `fetch ${plain.url}: ${error?.message ?? error}`;
|
|
1057
|
-
|
|
1058
|
-
|
|
1121
|
+
if (!disposed && !signal?.aborted) {
|
|
1122
|
+
consoleEntries.push({ level: "error", text });
|
|
1123
|
+
hooks.consoleMessage?.("error", text);
|
|
1124
|
+
}
|
|
1125
|
+
if (disposed || signal?.aborted) throw signal?.reason ?? abortError();
|
|
1059
1126
|
throw new TypeError("Failed to fetch");
|
|
1060
1127
|
});
|
|
1061
1128
|
}
|
|
1129
|
+
let abort;
|
|
1062
1130
|
const aborted = signal
|
|
1063
|
-
? new Promise((_, reject) =>
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1131
|
+
? new Promise((_, reject) => {
|
|
1132
|
+
abort = () => {
|
|
1133
|
+
const reason = signal.reason ?? abortError();
|
|
1134
|
+
networkPending?.abort?.(reason);
|
|
1135
|
+
reject(reason);
|
|
1136
|
+
};
|
|
1137
|
+
signal.addEventListener("abort", abort, { once: true });
|
|
1138
|
+
if (signal.aborted) abort();
|
|
1139
|
+
})
|
|
1067
1140
|
: null;
|
|
1141
|
+
const cleanup = () => {
|
|
1142
|
+
pendingPageFetches.delete(networkPending);
|
|
1143
|
+
if (abort) signal.removeEventListener("abort", abort);
|
|
1144
|
+
};
|
|
1145
|
+
// The fetch promise resolves at headers. Keep cancellation attached
|
|
1146
|
+
// until the streaming body finishes, including across navigation.
|
|
1147
|
+
(networkPending?.finished ?? pending).then(cleanup, cleanup);
|
|
1068
1148
|
const data = await (aborted ? Promise.race([pending, aborted]) : pending);
|
|
1069
1149
|
hooks.afterTask();
|
|
1070
1150
|
return makeResponse(data);
|
|
@@ -1078,7 +1158,6 @@ export function createPageRealm({ window, document, mode, hooks }) {
|
|
|
1078
1158
|
let url = null;
|
|
1079
1159
|
let response = null;
|
|
1080
1160
|
let controller = null;
|
|
1081
|
-
let timer = null;
|
|
1082
1161
|
const xhr = {
|
|
1083
1162
|
UNSENT: 0, OPENED: 1, HEADERS_RECEIVED: 2, LOADING: 3, DONE: 4,
|
|
1084
1163
|
readyState: 0,
|
|
@@ -1104,7 +1183,7 @@ export function createPageRealm({ window, document, mode, hooks }) {
|
|
|
1104
1183
|
requestHeaders.append(String(name), String(value));
|
|
1105
1184
|
},
|
|
1106
1185
|
getResponseHeader(name) {
|
|
1107
|
-
return response
|
|
1186
|
+
return response ? HostHeaders.prototype.get.call(response.headers, String(name)) : null;
|
|
1108
1187
|
},
|
|
1109
1188
|
getAllResponseHeaders() {
|
|
1110
1189
|
if (!response) return "";
|
|
@@ -1116,15 +1195,19 @@ export function createPageRealm({ window, document, mode, hooks }) {
|
|
|
1116
1195
|
},
|
|
1117
1196
|
send(body = null) {
|
|
1118
1197
|
if (xhr.readyState !== 1) throw new DOMException("The object's state must be OPENED.", "InvalidStateError");
|
|
1119
|
-
controller = new AbortController();
|
|
1120
|
-
|
|
1198
|
+
const requestController = controller = new AbortController();
|
|
1199
|
+
let timedOut = false;
|
|
1200
|
+
const timer = xhr.timeout > 0 ? schedule(() => {
|
|
1201
|
+
timedOut = true;
|
|
1202
|
+
requestController.abort();
|
|
1203
|
+
}, xhr.timeout, [], false) : null;
|
|
1121
1204
|
fire("loadstart");
|
|
1122
1205
|
pageFetch(url, {
|
|
1123
1206
|
method,
|
|
1124
1207
|
headers: requestHeaders,
|
|
1125
1208
|
body: ["GET", "HEAD"].includes(method) ? null : body,
|
|
1126
1209
|
credentials: xhr.withCredentials ? "include" : "same-origin",
|
|
1127
|
-
signal:
|
|
1210
|
+
signal: requestController.signal,
|
|
1128
1211
|
}).then(async (result) => {
|
|
1129
1212
|
response = result;
|
|
1130
1213
|
xhr.status = result.status;
|
|
@@ -1133,19 +1216,27 @@ export function createPageRealm({ window, document, mode, hooks }) {
|
|
|
1133
1216
|
setState(2);
|
|
1134
1217
|
setState(3);
|
|
1135
1218
|
const bytes = await result.arrayBuffer();
|
|
1136
|
-
const text =
|
|
1219
|
+
const text = xhr.responseType === "arraybuffer" || xhr.responseType === "blob"
|
|
1220
|
+
? "" : new TextDecoder().decode(bytes);
|
|
1137
1221
|
xhr.responseText = ["", "text"].includes(xhr.responseType) ? text : "";
|
|
1138
1222
|
xhr.response = xhr.responseType === "json" ? safeJson(text)
|
|
1139
1223
|
: xhr.responseType === "arraybuffer" ? bytes
|
|
1140
|
-
: xhr.responseType === "blob" ? new Blob([bytes], { type:
|
|
1224
|
+
: xhr.responseType === "blob" ? new Blob([bytes], { type: HostHeaders.prototype.get.call(result.headers, "content-type") ?? "" })
|
|
1141
1225
|
: text;
|
|
1142
1226
|
setState(4);
|
|
1143
1227
|
fire("load");
|
|
1144
1228
|
fire("loadend");
|
|
1145
|
-
}
|
|
1229
|
+
}).catch(() => {
|
|
1230
|
+
// The response promise resolves at headers; body failures still
|
|
1231
|
+
// run XHR's request-error steps instead of leaving it LOADING.
|
|
1232
|
+
response = null;
|
|
1146
1233
|
xhr.status = 0;
|
|
1234
|
+
xhr.statusText = "";
|
|
1235
|
+
xhr.responseURL = "";
|
|
1236
|
+
xhr.response = null;
|
|
1237
|
+
xhr.responseText = "";
|
|
1147
1238
|
setState(4);
|
|
1148
|
-
fire(
|
|
1239
|
+
fire(timedOut ? "timeout" : requestController.signal.aborted ? "abort" : "error");
|
|
1149
1240
|
fire("loadend");
|
|
1150
1241
|
}).finally(() => {
|
|
1151
1242
|
if (timer !== null) cancel(timer);
|
|
@@ -1311,9 +1402,9 @@ export function createPageRealm({ window, document, mode, hooks }) {
|
|
|
1311
1402
|
fetch: function fetch(input, init = undefined) { return pageFetch(input, init); },
|
|
1312
1403
|
XMLHttpRequest,
|
|
1313
1404
|
WebSocket,
|
|
1314
|
-
Headers,
|
|
1405
|
+
Headers: PageHeaders,
|
|
1315
1406
|
Request: PageRequest,
|
|
1316
|
-
Response,
|
|
1407
|
+
Response: PageResponse,
|
|
1317
1408
|
AbortController,
|
|
1318
1409
|
AbortSignal,
|
|
1319
1410
|
FormData,
|
|
@@ -1491,6 +1582,10 @@ export function createPageRealm({ window, document, mode, hooks }) {
|
|
|
1491
1582
|
const messaging = createMessaging({
|
|
1492
1583
|
realm, window, pageGlobal: () => pageGlobal, runTask, callPage, DOMException, frames: hooks.frames ?? null,
|
|
1493
1584
|
});
|
|
1585
|
+
const broadcasts = createBroadcastChannels({
|
|
1586
|
+
origin: hooks.frames?.self.origin() ?? new URL(document.URL).origin,
|
|
1587
|
+
EventTarget: eventTargets.EventTarget, realm, runTask, DOMException,
|
|
1588
|
+
});
|
|
1494
1589
|
|
|
1495
1590
|
const platform = platformObjects({
|
|
1496
1591
|
eventTargets, userAgent, userAgentDataMembers: chromeVersion ? userAgentDataMembers : null, mobile: mobileUserAgent, viewport: hooks.viewport,
|
|
@@ -1537,12 +1632,13 @@ export function createPageRealm({ window, document, mode, hooks }) {
|
|
|
1537
1632
|
// SES rewrites dynamic import() in evaluated classic scripts to this
|
|
1538
1633
|
// hook. Resolve it through the same module loader and network boundary as
|
|
1539
1634
|
// parser-inserted modules.
|
|
1540
|
-
__import__: (specifier) => importModule(String(specifier), currentScript?.src || document.URL),
|
|
1635
|
+
__import__: (specifier) => importModule(String(specifier), currentScript?.src || document.baseURI || document.URL),
|
|
1541
1636
|
performance: platform.performance,
|
|
1542
1637
|
PerformanceObserver: userTiming.PerformanceObserver,
|
|
1543
1638
|
crypto: platform.crypto,
|
|
1544
1639
|
...platform.interfaces,
|
|
1545
1640
|
CSSStyleDeclaration: cssStyleDeclarationInterface(realm),
|
|
1641
|
+
ValidityState: validity.Interface,
|
|
1546
1642
|
...trustedTypes.interfaces,
|
|
1547
1643
|
trustedTypes: trustedTypes.trustedTypes,
|
|
1548
1644
|
Event,
|
|
@@ -1595,15 +1691,19 @@ export function createPageRealm({ window, document, mode, hooks }) {
|
|
|
1595
1691
|
const tag = String(name).toLowerCase();
|
|
1596
1692
|
if (typeof constructor !== "function") throw new TypeError("Custom element constructor must be a function");
|
|
1597
1693
|
if (customElementConstructors.has(tag)) throw new DOMException("Custom element already defined", "NotSupportedError");
|
|
1694
|
+
let captureInitialAttributes;
|
|
1598
1695
|
const Adapter = class extends window.HTMLElement {
|
|
1599
1696
|
constructor() {
|
|
1600
1697
|
super();
|
|
1698
|
+
const initialAttributes = captureInitialAttributes?.(this);
|
|
1601
1699
|
const wrapper = realm.wrap(this);
|
|
1602
1700
|
Object.setPrototypeOf(wrapper, constructor.prototype);
|
|
1603
1701
|
const previous = constructingElement;
|
|
1604
1702
|
constructingElement = this;
|
|
1605
1703
|
try { Reflect.construct(constructor, []); }
|
|
1704
|
+
catch (error) { initialAttributes?.cancel(); throw error; }
|
|
1606
1705
|
finally { constructingElement = previous; }
|
|
1706
|
+
initialAttributes?.finish();
|
|
1607
1707
|
}
|
|
1608
1708
|
connectedCallback() {
|
|
1609
1709
|
const wrapper = realm.wrap(this);
|
|
@@ -1614,9 +1714,16 @@ export function createPageRealm({ window, document, mode, hooks }) {
|
|
|
1614
1714
|
if (typeof wrapper.disconnectedCallback === "function") callPage(wrapper.disconnectedCallback, wrapper, []);
|
|
1615
1715
|
}
|
|
1616
1716
|
};
|
|
1617
|
-
installAttributeReactions(Adapter, constructor, realm, callPage);
|
|
1618
|
-
window.customElements.define(tag, Adapter);
|
|
1717
|
+
captureInitialAttributes = installAttributeReactions(Adapter, constructor, realm, callPage);
|
|
1619
1718
|
customElementConstructors.set(tag, constructor);
|
|
1719
|
+
try { window.customElements.define(tag, Adapter); }
|
|
1720
|
+
catch (error) {
|
|
1721
|
+
// The definition is observable while existing elements upgrade.
|
|
1722
|
+
// Validation failures happen before registration; constructor errors
|
|
1723
|
+
// can happen afterward and must not erase that registered identity.
|
|
1724
|
+
if (window.customElements.get(tag) !== Adapter) customElementConstructors.delete(tag);
|
|
1725
|
+
throw error;
|
|
1726
|
+
}
|
|
1620
1727
|
// Upgrades change :defined without a DOM mutation.
|
|
1621
1728
|
hooks.definedChanged?.(tag);
|
|
1622
1729
|
},
|
|
@@ -1631,6 +1738,7 @@ export function createPageRealm({ window, document, mode, hooks }) {
|
|
|
1631
1738
|
},
|
|
1632
1739
|
},
|
|
1633
1740
|
...INTERFACES,
|
|
1741
|
+
DOMStringMap,
|
|
1634
1742
|
Node: NodeInterface,
|
|
1635
1743
|
Text: TextInterface,
|
|
1636
1744
|
CharacterData: CharacterDataInterface,
|
|
@@ -1639,6 +1747,8 @@ export function createPageRealm({ window, document, mode, hooks }) {
|
|
|
1639
1747
|
Document: DocumentInterface,
|
|
1640
1748
|
HTMLDocument: DocumentInterface,
|
|
1641
1749
|
DocumentFragment: DocumentFragmentInterface,
|
|
1750
|
+
DOMParser,
|
|
1751
|
+
XMLSerializer,
|
|
1642
1752
|
...typeCheckInterfaces(),
|
|
1643
1753
|
addEventListener: windowEvents.addEventListener,
|
|
1644
1754
|
removeEventListener: windowEvents.removeEventListener,
|
|
@@ -1687,6 +1797,7 @@ export function createPageRealm({ window, document, mode, hooks }) {
|
|
|
1687
1797
|
postMessage: messaging.postMessage,
|
|
1688
1798
|
MessageChannel: messaging.MessageChannel,
|
|
1689
1799
|
MessagePort: messaging.MessagePort,
|
|
1800
|
+
BroadcastChannel: broadcasts.BroadcastChannel,
|
|
1690
1801
|
};
|
|
1691
1802
|
const accessors = {
|
|
1692
1803
|
innerWidth: viewportValue("width"),
|
|
@@ -1707,7 +1818,8 @@ export function createPageRealm({ window, document, mode, hooks }) {
|
|
|
1707
1818
|
hardenBindings();
|
|
1708
1819
|
// Dedicated workers get a compartment each, with these of the page's utilities.
|
|
1709
1820
|
workers = createWorkers({
|
|
1710
|
-
realm, documentUrl: () => document.URL,
|
|
1821
|
+
realm, documentUrl: () => document.URL, documentBaseUrl: () => document.baseURI || document.URL,
|
|
1822
|
+
fetchScript: hooks.fetchScript, runTask, callPage, reportError, DOMException,
|
|
1711
1823
|
constructorHelper,
|
|
1712
1824
|
pagePorts: messaging.ports,
|
|
1713
1825
|
globals: Object.fromEntries(["console", "atob", "btoa", "TextEncoder", "TextDecoder", "URL", "URLSearchParams",
|
|
@@ -1763,6 +1875,7 @@ export function createPageRealm({ window, document, mode, hooks }) {
|
|
|
1763
1875
|
});
|
|
1764
1876
|
Object.defineProperty(PagePromise, "length", { value: 1, configurable: true });
|
|
1765
1877
|
pageGlobal.Promise = PagePromise;
|
|
1878
|
+
pageGlobal.Error = errorInterface(pageGlobal.Error, hostErrorIsError);
|
|
1766
1879
|
// Namespace objects that libraries extend (reflect-metadata adds to
|
|
1767
1880
|
// Reflect, polyfills to JSON and Math): each page gets its own mutable
|
|
1768
1881
|
// copy with the same functions; the shared frozen intrinsics stay as they are.
|
|
@@ -1814,8 +1927,7 @@ export function createPageRealm({ window, document, mode, hooks }) {
|
|
|
1814
1927
|
__rejectSomeDirectEvalExpressions__: false,
|
|
1815
1928
|
});
|
|
1816
1929
|
evaluate = (source) => {
|
|
1817
|
-
|
|
1818
|
-
return evaluatePrepared({ code: escapeSesHtmlComments(classic.code), parsed: classic.parsed, strict: classic.strict });
|
|
1930
|
+
return evaluatePrepared(prepareClassicScript(source));
|
|
1819
1931
|
};
|
|
1820
1932
|
// Module code is already strict with its own scope; no global lifting.
|
|
1821
1933
|
evaluateModuleCode = (code) => compartment.evaluate(escapeSesHtmlComments(code, "module"), {
|
|
@@ -1882,7 +1994,7 @@ export function createPageRealm({ window, document, mode, hooks }) {
|
|
|
1882
1994
|
if (node.nodeType !== 1) continue;
|
|
1883
1995
|
for (const script of [node, ...node.querySelectorAll("script")]) {
|
|
1884
1996
|
if (script.tagName !== "SCRIPT" || !createdScripts.has(script) || startedScripts.has(script)) continue;
|
|
1885
|
-
if (!script.isConnected) continue;
|
|
1997
|
+
if (!script.isConnected || script.ownerDocument !== document) continue;
|
|
1886
1998
|
runInsertedScript(script);
|
|
1887
1999
|
}
|
|
1888
2000
|
}
|
|
@@ -1908,7 +2020,7 @@ export function createPageRealm({ window, document, mode, hooks }) {
|
|
|
1908
2020
|
runClassic(script, script.textContent);
|
|
1909
2021
|
return;
|
|
1910
2022
|
}
|
|
1911
|
-
const pending = hooks.fetchScript(new URL(src, document.URL).href).then(
|
|
2023
|
+
const pending = hooks.fetchScript(new URL(src, script.baseURI || document.URL).href).then(
|
|
1912
2024
|
async (source) => {
|
|
1913
2025
|
runClassic(script, source, await preparedFor(prepareOffThread(source), source));
|
|
1914
2026
|
runTask(() => script.dispatchEvent(new window.Event("load")));
|
|
@@ -1933,7 +2045,9 @@ export function createPageRealm({ window, document, mode, hooks }) {
|
|
|
1933
2045
|
// phases, see module-transform.js.
|
|
1934
2046
|
|
|
1935
2047
|
function ensureFetched(url, type, sourceOverride = null) {
|
|
1936
|
-
|
|
2048
|
+
// Inline modules are distinct scripts sharing a real base URL. Their
|
|
2049
|
+
// identity must not leak into import.meta.url as an invented fragment.
|
|
2050
|
+
const key = sourceOverride === null ? `${type ?? "js"} ${url}` : `inline:${++inlineModuleCount}`;
|
|
1937
2051
|
let record = modules.get(key);
|
|
1938
2052
|
if (record) return record.fetched;
|
|
1939
2053
|
record = { url, type, state: "fetching", namespace: Object.create(null), deps: [], error: null };
|
|
@@ -2053,11 +2167,12 @@ export function createPageRealm({ window, document, mode, hooks }) {
|
|
|
2053
2167
|
async function runModuleScript(script) {
|
|
2054
2168
|
const src = script.getAttribute("src");
|
|
2055
2169
|
try {
|
|
2056
|
-
|
|
2057
|
-
|
|
2170
|
+
const baseURL = script.baseURI || document.URL;
|
|
2171
|
+
if (src) await importModule(new URL(src, baseURL).href, baseURL);
|
|
2172
|
+
else await importModule(baseURL, baseURL, null, script.textContent ?? "");
|
|
2058
2173
|
if (src) runTask(() => script.dispatchEvent(new window.Event("load")));
|
|
2059
2174
|
} catch (error) {
|
|
2060
|
-
reportError(error, src ? new URL(src, document.URL).href : document.URL);
|
|
2175
|
+
reportError(error, src ? new URL(src, script.baseURI || document.URL).href : document.URL);
|
|
2061
2176
|
if (src) runTask(() => script.dispatchEvent(new window.Event("error")));
|
|
2062
2177
|
}
|
|
2063
2178
|
}
|
|
@@ -2183,7 +2298,7 @@ export function createPageRealm({ window, document, mode, hooks }) {
|
|
|
2183
2298
|
continue;
|
|
2184
2299
|
}
|
|
2185
2300
|
try {
|
|
2186
|
-
const url = new URL(src, document.URL).href;
|
|
2301
|
+
const url = new URL(src, script.baseURI || document.URL).href;
|
|
2187
2302
|
if ((script.getAttribute("type") ?? "").trim().toLowerCase() === "module") {
|
|
2188
2303
|
ensureFetched(resolveModuleSpecifier(url, document.URL, importMap), null).then(loadGraph).catch(() => {});
|
|
2189
2304
|
} else {
|
|
@@ -2213,7 +2328,7 @@ export function createPageRealm({ window, document, mode, hooks }) {
|
|
|
2213
2328
|
let source;
|
|
2214
2329
|
try {
|
|
2215
2330
|
source = src
|
|
2216
|
-
? await (preloaded.get(script) ?? hooks.fetchScript(new URL(src, document.URL).href))
|
|
2331
|
+
? await (preloaded.get(script) ?? hooks.fetchScript(new URL(src, script.baseURI || document.URL).href))
|
|
2217
2332
|
: script.textContent ?? "";
|
|
2218
2333
|
} catch (error) {
|
|
2219
2334
|
reportError(error);
|
|
@@ -2289,6 +2404,12 @@ export function createPageRealm({ window, document, mode, hooks }) {
|
|
|
2289
2404
|
messaging.receive(message);
|
|
2290
2405
|
},
|
|
2291
2406
|
|
|
2407
|
+
/** The renderer completed layout or changed a scroll offset. */
|
|
2408
|
+
layoutChanged(version) {
|
|
2409
|
+
const viewport = hooks.viewport();
|
|
2410
|
+
layoutObservers.update(version, viewport.scrollX, viewport.scrollY, viewport.width, viewport.height, viewport.devicePixelRatio);
|
|
2411
|
+
},
|
|
2412
|
+
|
|
2292
2413
|
/** A MediaSource object, or the one registered at a blob URL. */
|
|
2293
2414
|
isMediaSource(value) {
|
|
2294
2415
|
return mediaScope.isMediaSource(value);
|
|
@@ -2302,9 +2423,15 @@ export function createPageRealm({ window, document, mode, hooks }) {
|
|
|
2302
2423
|
|
|
2303
2424
|
dispose() {
|
|
2304
2425
|
disposed = true;
|
|
2426
|
+
for (const pending of pendingPageFetches) pending.abort?.();
|
|
2427
|
+
pendingPageFetches.clear();
|
|
2428
|
+
disposeDocumentCanvases(document);
|
|
2305
2429
|
stopRejectionEvents();
|
|
2430
|
+
layoutObservers.dispose();
|
|
2431
|
+
parserWindow?.happyDOM?.close?.();
|
|
2306
2432
|
if (blobUrlFallback === mediaScope) blobUrlFallback = null;
|
|
2307
2433
|
messaging.dispose();
|
|
2434
|
+
broadcasts.dispose();
|
|
2308
2435
|
workers?.dispose();
|
|
2309
2436
|
// Leaving the document closes its WebSockets (1001 "going away").
|
|
2310
2437
|
for (const close of openSockets) close();
|
|
@@ -2728,7 +2855,18 @@ function computedStyleBinding(style, { custom = null, bounds = null, element = n
|
|
|
2728
2855
|
const horizontal = (style.padding[1] ?? 0) + (style.padding[3] ?? 0) + (style.borderWidths[1] ?? 0) + (style.borderWidths[3] ?? 0);
|
|
2729
2856
|
const vertical = (style.padding[0] ?? 0) + (style.padding[2] ?? 0) + (style.borderWidths[0] ?? 0) + (style.borderWidths[2] ?? 0);
|
|
2730
2857
|
const transform = style.transform;
|
|
2731
|
-
const translate = (value, size) =>
|
|
2858
|
+
const translate = (value, size) => {
|
|
2859
|
+
if (typeof value === "number") return value;
|
|
2860
|
+
if (value?.unit === "%") return value.value / 100 * size;
|
|
2861
|
+
if (value?.unit !== "math") return 0;
|
|
2862
|
+
if (value.kind === "calc") return value.px + value.percent / 100 * size;
|
|
2863
|
+
if (value.kind === "scale") return value.factor * translate(value.value, size);
|
|
2864
|
+
const parts = value.values.map(part => translate(part, size));
|
|
2865
|
+
if (value.kind === "sum") return parts.reduce((sum, part) => sum + part, 0);
|
|
2866
|
+
if (value.kind === "min") return Math.min(...parts);
|
|
2867
|
+
if (value.kind === "max") return Math.max(...parts);
|
|
2868
|
+
return Math.max(parts[0], Math.min(parts[1], parts[2]));
|
|
2869
|
+
};
|
|
2732
2870
|
Object.assign(values, {
|
|
2733
2871
|
display: style.display,
|
|
2734
2872
|
position: style.position,
|
|
@@ -2738,7 +2876,11 @@ function computedStyleBinding(style, { custom = null, bounds = null, element = n
|
|
|
2738
2876
|
backgroundImage: style.backgroundImage == null ? "none" : String(style.backgroundImage),
|
|
2739
2877
|
fontSize: px(style.fontSize),
|
|
2740
2878
|
fontWeight: String(style.fontWeight),
|
|
2879
|
+
fontStyle: style.fontStyle ?? "normal",
|
|
2880
|
+
fontStretch: `${style.fontStretch ?? 100}%`,
|
|
2741
2881
|
fontFamily: (style.fontFamily ?? []).join(", "),
|
|
2882
|
+
translate: style.translate ? (style.translate.y === 0 ? length(style.translate.x)
|
|
2883
|
+
: `${length(style.translate.x)} ${length(style.translate.y)}`) : "none",
|
|
2742
2884
|
lineHeight: style.lineHeight == null ? "normal" : px(style.lineHeight),
|
|
2743
2885
|
textAlign: style.textAlign,
|
|
2744
2886
|
whiteSpace: style.whiteSpace,
|
|
@@ -2768,9 +2910,8 @@ function computedStyleBinding(style, { custom = null, bounds = null, element = n
|
|
|
2768
2910
|
alignContent: style.alignContent,
|
|
2769
2911
|
alignItems: style.alignItems,
|
|
2770
2912
|
alignSelf: style.alignSelf,
|
|
2771
|
-
rowGap: style.
|
|
2772
|
-
columnGap: style.
|
|
2773
|
-
borderRadius: length(style.borderRadius),
|
|
2913
|
+
rowGap: style.rowGapNormal ? "normal" : length(style.rowGap),
|
|
2914
|
+
columnGap: style.columnGapNormal ? "normal" : length(style.columnGap),
|
|
2774
2915
|
transform: !transform ? "none"
|
|
2775
2916
|
: `matrix(${transform.scaleX ?? 1}, 0, 0, ${transform.scaleY ?? 1}, ${translate(transform.x, bounds?.width ?? 0)}, ${translate(transform.y, bounds?.height ?? 0)})`,
|
|
2776
2917
|
visibility: style.visibility ?? "visible",
|
|
@@ -2780,6 +2921,23 @@ function computedStyleBinding(style, { custom = null, bounds = null, element = n
|
|
|
2780
2921
|
overflowX: style.overflowScrollX ?? (style.overflowX === "clip" ? "clip" : "visible"),
|
|
2781
2922
|
overflowY: style.overflowScrollY ?? (style.overflowY === "clip" ? "clip" : "visible"),
|
|
2782
2923
|
});
|
|
2924
|
+
const cornerNames = ["TopLeft", "TopRight", "BottomRight", "BottomLeft"];
|
|
2925
|
+
const cornerLengths = cornerNames.map(corner => (style[`border${corner}Radius`]
|
|
2926
|
+
?? [style.borderRadius, style.borderRadius]).map(length));
|
|
2927
|
+
cornerNames.forEach((corner, index) => {
|
|
2928
|
+
const [x, y] = cornerLengths[index];
|
|
2929
|
+
values[`border${corner}Radius`] = x === y ? x : `${x} ${y}`;
|
|
2930
|
+
});
|
|
2931
|
+
const compress = ([a, b, c, d]) => a === c && b === d ? a === b ? a : `${a} ${b}`
|
|
2932
|
+
: b === d ? `${a} ${b} ${c}` : `${a} ${b} ${c} ${d}`;
|
|
2933
|
+
const horizontalRadius = compress(cornerLengths.map(pair => pair[0]));
|
|
2934
|
+
const verticalRadius = compress(cornerLengths.map(pair => pair[1]));
|
|
2935
|
+
values.borderRadius = horizontalRadius === verticalRadius ? horizontalRadius : `${horizontalRadius} / ${verticalRadius}`;
|
|
2936
|
+
const rtl = style.direction === "rtl";
|
|
2937
|
+
values.borderStartStartRadius = values[rtl ? "borderTopRightRadius" : "borderTopLeftRadius"];
|
|
2938
|
+
values.borderStartEndRadius = values[rtl ? "borderTopLeftRadius" : "borderTopRightRadius"];
|
|
2939
|
+
values.borderEndStartRadius = values[rtl ? "borderBottomRightRadius" : "borderBottomLeftRadius"];
|
|
2940
|
+
values.borderEndEndRadius = values[rtl ? "borderBottomLeftRadius" : "borderBottomRightRadius"];
|
|
2783
2941
|
values.overflow = values.overflowX === values.overflowY ? values.overflowX : `${values.overflowX} ${values.overflowY}`;
|
|
2784
2942
|
sides.forEach((side, index) => {
|
|
2785
2943
|
values[`margin${side}`] = length(style.margin[index]);
|
|
@@ -2817,6 +2975,7 @@ function computedStyleBinding(style, { custom = null, bounds = null, element = n
|
|
|
2817
2975
|
|
|
2818
2976
|
function formatConsoleValue(value) {
|
|
2819
2977
|
if (typeof value === "string") return value;
|
|
2978
|
+
if (hostErrorIsError?.(value)) return diagnosticError(value);
|
|
2820
2979
|
try {
|
|
2821
2980
|
return JSON.stringify(value) ?? String(value);
|
|
2822
2981
|
} catch {
|
|
@@ -2824,6 +2983,19 @@ function formatConsoleValue(value) {
|
|
|
2824
2983
|
}
|
|
2825
2984
|
}
|
|
2826
2985
|
|
|
2986
|
+
// SES retains a privileged stack reader in the host compartment. Use it only
|
|
2987
|
+
// when formatting developer diagnostics: pages still see tamed Error.stack
|
|
2988
|
+
// and never receive the reader itself.
|
|
2989
|
+
function diagnosticError(error) {
|
|
2990
|
+
const message = error?.name ? `${error.name}: ${error.message}` : String(error?.message ?? error);
|
|
2991
|
+
try {
|
|
2992
|
+
const stack = error?.stack || globalThis.getStackString?.(error);
|
|
2993
|
+
return stack ? (stack.startsWith(message) ? stack : `${message}\n${stack}`) : message;
|
|
2994
|
+
} catch {
|
|
2995
|
+
return message;
|
|
2996
|
+
}
|
|
2997
|
+
}
|
|
2998
|
+
|
|
2827
2999
|
function safeJson(text) {
|
|
2828
3000
|
try {
|
|
2829
3001
|
return JSON.parse(text);
|