buninu-browser 0.2.2 → 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/audio/playback-buffer.js +27 -0
- package/lib/audio/pulse-client.js +18 -8
- package/lib/cdp-server.js +53 -14
- package/lib/happy-dom/document-base.js +33 -0
- package/lib/happy-dom/parser.js +20 -6
- package/lib/happy-dom/selector-matcher.js +2 -2
- package/lib/happy-dom/selector-pseudo-adapter.js +33 -0
- package/lib/happy-dom/svg-attribute-adapter.js +50 -0
- package/lib/headless-shell.js +12 -6
- package/lib/input/hit-test.js +67 -19
- package/lib/layout/text-layout.js +289 -131
- 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 +42 -0
- package/lib/media/webm-stream.js +106 -0
- package/lib/network/network-service.js +268 -120
- package/lib/paint/display-list.js +22 -27
- 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 +40 -6
- package/lib/render-tree/svg.js +15 -7
- package/lib/renderer/bindings.js +244 -82
- 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 +27 -10
- package/lib/renderer/custom-element-reactions.js +42 -0
- package/lib/renderer/font-loading.js +227 -0
- package/lib/renderer/history.js +42 -0
- package/lib/renderer/host.js +36 -5
- package/lib/renderer/image-loading.js +27 -0
- package/lib/renderer/image-resources.js +65 -0
- package/lib/renderer/interfaces.js +28 -0
- package/lib/renderer/keyboard-input.js +12 -0
- package/lib/renderer/layout-observers.js +236 -0
- package/lib/renderer/main.js +33 -5
- package/lib/renderer/media-playback-events.js +13 -0
- package/lib/renderer/media-source.js +126 -55
- package/lib/renderer/page-realm.js +278 -147
- package/lib/renderer/page-renderer.js +840 -301
- package/lib/renderer/piano-input.js +22 -0
- package/lib/renderer/pointer-move.js +29 -0
- 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 +145 -0
- package/lib/renderer/workers.js +2 -2
- package/lib/style/computed-style.js +632 -108
- 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/compositor.test.js +38 -0
- package/test/computed-style-binding.test.js +151 -0
- package/test/computed-style.test.js +279 -1
- package/test/display-list.test.js +26 -0
- package/test/document-base.test.js +87 -0
- package/test/dom-platform.test.js +461 -0
- package/test/download.test.js +8 -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/headless-shell.test.js +22 -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-playback-events.test.js +22 -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 +160 -2
- package/test/paint-frame.test.js +103 -0
- package/test/paint-paragraph-cache.test.js +87 -0
- package/test/piano-input.test.js +44 -0
- package/test/playback-buffer.test.js +31 -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 +27 -5
- 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 +168 -0
- package/test/video-keyframe.test.js +61 -0
- package/test/video-presentation.test.js +136 -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
|
@@ -20,15 +20,22 @@ 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";
|
|
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";
|
|
37
|
+
import { historyBinding } from "./history.js";
|
|
38
|
+
import { installImageLoadingAttributes } from "./image-loading.js";
|
|
32
39
|
import { AsyncLocalStorage } from "node:async_hooks";
|
|
33
40
|
|
|
34
41
|
// URL is one shared intrinsic, hardened before any page sees it. Blob URL
|
|
@@ -61,6 +68,9 @@ const hostCrypto = globalThis.crypto;
|
|
|
61
68
|
const hostStructuredClone = globalThis.structuredClone;
|
|
62
69
|
const hostAtob = globalThis.atob;
|
|
63
70
|
const hostBtoa = globalThis.btoa;
|
|
71
|
+
const HostHeaders = globalThis.Headers;
|
|
72
|
+
const HostRequest = globalThis.Request;
|
|
73
|
+
const HostResponse = globalThis.Response;
|
|
64
74
|
|
|
65
75
|
export const CLASSIC_SCRIPT_TYPES = new Set([
|
|
66
76
|
"", "text/javascript", "application/javascript", "text/ecmascript", "application/ecmascript",
|
|
@@ -150,7 +160,11 @@ const PLAIN_HTML_ELEMENTS = new Set(["ABBR", "ADDRESS", "ARTICLE", "ASIDE", "B",
|
|
|
150
160
|
*/
|
|
151
161
|
const FORM_CONTROLS = ["HTMLInputElement", "HTMLSelectElement", "HTMLTextAreaElement", "HTMLButtonElement"];
|
|
152
162
|
const URL_ELEMENTS = ["HTMLAnchorElement", "HTMLAreaElement"];
|
|
163
|
+
const VALIDATED_ELEMENTS = [...FORM_CONTROLS, "HTMLFieldSetElement", "HTMLObjectElement", "HTMLOutputElement"];
|
|
153
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"],
|
|
154
168
|
contentWindow: ["HTMLIFrameElement", "HTMLObjectElement", "HTMLFrameElement"],
|
|
155
169
|
contentDocument: ["HTMLIFrameElement", "HTMLObjectElement", "HTMLFrameElement"],
|
|
156
170
|
content: ["HTMLTemplateElement"],
|
|
@@ -199,6 +213,9 @@ const WINDOW_HANDLER_EVENTS = [
|
|
|
199
213
|
"popstate", "beforeunload", "unload", "focus", "blur", "online", "offline", "unhandledrejection", "rejectionhandled",
|
|
200
214
|
];
|
|
201
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;
|
|
202
219
|
|
|
203
220
|
function cssEscape(value) {
|
|
204
221
|
const input = String(value);
|
|
@@ -262,6 +279,7 @@ export function createPageRealm({ window, document, mode, hooks }) {
|
|
|
262
279
|
const consoleEntries = [];
|
|
263
280
|
let nextTimerId = 1;
|
|
264
281
|
let disposed = false;
|
|
282
|
+
const pendingPageFetches = new Set();
|
|
265
283
|
let readyState = "loading";
|
|
266
284
|
// A document gets a fresh high-resolution time origin. Bun's
|
|
267
285
|
// performance.now() is relative to the renderer process, which is commonly
|
|
@@ -292,8 +310,7 @@ export function createPageRealm({ window, document, mode, hooks }) {
|
|
|
292
310
|
let inlineModuleCount = 0;
|
|
293
311
|
|
|
294
312
|
const reportError = (error, source = currentScript?.src) => {
|
|
295
|
-
|
|
296
|
-
const detail = error?.stack || (error?.name ? `${error.name}: ${error.message}` : String(error?.message ?? error));
|
|
313
|
+
const detail = diagnosticError(error);
|
|
297
314
|
const message = source ? `${source}: ${detail}` : detail;
|
|
298
315
|
consoleEntries.push({ level: "error", text: message });
|
|
299
316
|
hooks.reportError(message);
|
|
@@ -329,7 +346,9 @@ export function createPageRealm({ window, document, mode, hooks }) {
|
|
|
329
346
|
};
|
|
330
347
|
|
|
331
348
|
const realm = new BindingRealm(window, {
|
|
332
|
-
touch:
|
|
349
|
+
touch: (node, features) => {
|
|
350
|
+
if (!node || node === document || node.ownerDocument === document) hooks.touch(node, features);
|
|
351
|
+
},
|
|
333
352
|
boundsOf: hooks.boundsOf,
|
|
334
353
|
computedStyle: hooks.computedStyle,
|
|
335
354
|
// The engine clock's reading when this document's timeline started (see pageNow).
|
|
@@ -373,6 +392,14 @@ export function createPageRealm({ window, document, mode, hooks }) {
|
|
|
373
392
|
(value) => value === pageGlobal || Boolean(nodeOf(value)),
|
|
374
393
|
(value) => value === pageGlobal ? window : nodeOf(value));
|
|
375
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;
|
|
376
403
|
const NodeInterface = function Node() { throw new TypeError("Illegal constructor"); };
|
|
377
404
|
// Node.prototype inherits EventTarget.prototype (DOM §4.4), as Window.prototype does.
|
|
378
405
|
NodeInterface.prototype = mutableBindingPrototype(INTERFACES.Node.prototype, eventTargets.EventTarget.prototype);
|
|
@@ -554,7 +581,27 @@ export function createPageRealm({ window, document, mode, hooks }) {
|
|
|
554
581
|
enumerable: true,
|
|
555
582
|
writable: true,
|
|
556
583
|
value(type) {
|
|
557
|
-
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);
|
|
558
605
|
},
|
|
559
606
|
});
|
|
560
607
|
// HTMLMediaElement. HAVE_* / NETWORK_* / MEDIA_ERR_* live on the prototype
|
|
@@ -631,6 +678,33 @@ export function createPageRealm({ window, document, mode, hooks }) {
|
|
|
631
678
|
DocumentInterface.prototype = withParentNodeMembers(mutableBindingPrototype(INTERFACES.Document.prototype, parentPrototype));
|
|
632
679
|
Object.defineProperty(DocumentInterface.prototype, "constructor", { value: DocumentInterface, writable: true, configurable: true });
|
|
633
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
|
+
}
|
|
634
708
|
|
|
635
709
|
/** In an iframe's document, parent and top are the embedding frames' windows. */
|
|
636
710
|
const defineFrameWindows = (global) => {
|
|
@@ -715,12 +789,12 @@ export function createPageRealm({ window, document, mode, hooks }) {
|
|
|
715
789
|
};
|
|
716
790
|
Object.defineProperty(pageConsole, Symbol.toStringTag, { value: "console", configurable: true });
|
|
717
791
|
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
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. */
|
|
724
798
|
function observerConstructors() {
|
|
725
799
|
function MutationObserver(callback) {
|
|
726
800
|
if (!new.target) throw new TypeError("Constructor MutationObserver requires 'new'");
|
|
@@ -756,48 +830,8 @@ export function createPageRealm({ window, document, mode, hooks }) {
|
|
|
756
830
|
return self;
|
|
757
831
|
}
|
|
758
832
|
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
const self = {};
|
|
762
|
-
const observed = new Set();
|
|
763
|
-
self.observe = (target) => {
|
|
764
|
-
const node = nodeOf(target);
|
|
765
|
-
if (!node || observed.has(node)) return;
|
|
766
|
-
observed.add(node);
|
|
767
|
-
schedule(() => {
|
|
768
|
-
if (!observed.has(node)) return;
|
|
769
|
-
const rect = hooks.boundsOf(node) ?? { left: 0, top: 0, width: 0, height: 0 };
|
|
770
|
-
callPage(callback, self, [[entryFor(target, rect)], self]);
|
|
771
|
-
}, 0, [], false);
|
|
772
|
-
};
|
|
773
|
-
self.unobserve = (target) => observed.delete(nodeOf(target));
|
|
774
|
-
self.disconnect = () => observed.clear();
|
|
775
|
-
self.takeRecords = () => [];
|
|
776
|
-
return self;
|
|
777
|
-
};
|
|
778
|
-
const rectangle = ({ left, top, width, height }) => ({
|
|
779
|
-
x: left, y: top, left, top, width, height, right: left + width, bottom: top + height,
|
|
780
|
-
});
|
|
781
|
-
const ResizeObserver = layoutObserver("ResizeObserver", (target, rect) => ({
|
|
782
|
-
target,
|
|
783
|
-
contentRect: rectangle({ ...rect, left: 0, top: 0 }),
|
|
784
|
-
borderBoxSize: [{ inlineSize: rect.width, blockSize: rect.height }],
|
|
785
|
-
contentBoxSize: [{ inlineSize: rect.width, blockSize: rect.height }],
|
|
786
|
-
}));
|
|
787
|
-
const IntersectionObserver = layoutObserver("IntersectionObserver", (target, rect) => {
|
|
788
|
-
const { width, height } = hooks.viewport();
|
|
789
|
-
const visible = rect.top < height && rect.top + rect.height > 0 && rect.left < width && rect.left + rect.width > 0;
|
|
790
|
-
return {
|
|
791
|
-
target,
|
|
792
|
-
time: pageNow(),
|
|
793
|
-
isIntersecting: visible,
|
|
794
|
-
intersectionRatio: visible ? 1 : 0,
|
|
795
|
-
boundingClientRect: rectangle(rect),
|
|
796
|
-
intersectionRect: rectangle(visible ? rect : { left: 0, top: 0, width: 0, height: 0 }),
|
|
797
|
-
rootBounds: rectangle({ left: 0, top: 0, width, height }),
|
|
798
|
-
};
|
|
799
|
-
});
|
|
800
|
-
return { MutationObserver, ResizeObserver, IntersectionObserver };
|
|
833
|
+
return { MutationObserver, ResizeObserver: layoutObservers.ResizeObserver,
|
|
834
|
+
IntersectionObserver: layoutObservers.IntersectionObserver };
|
|
801
835
|
}
|
|
802
836
|
|
|
803
837
|
/**
|
|
@@ -917,33 +951,62 @@ export function createPageRealm({ window, document, mode, hooks }) {
|
|
|
917
951
|
}
|
|
918
952
|
|
|
919
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;
|
|
920
975
|
const abortError = () => new DOMException("The operation was aborted.", "AbortError");
|
|
921
976
|
const baseUrl = () => document.baseURI || document.URL;
|
|
922
977
|
|
|
923
978
|
// new Request("/api") resolves against the document's base URL (Fetch
|
|
924
979
|
// §5.4), which the host's Request has no document to do.
|
|
925
|
-
const PageRequest = class Request extends
|
|
980
|
+
const PageRequest = class Request extends HostRequest {
|
|
926
981
|
constructor(input, init = undefined) {
|
|
927
|
-
super(input instanceof
|
|
982
|
+
super(input instanceof HostRequest ? input : new URL(String(input), baseUrl()).href, init);
|
|
928
983
|
// Fetch §5.4 step 36: a string body gives the request its Content-Type
|
|
929
984
|
// (Bun's Request only does this for the other body kinds).
|
|
930
|
-
if (typeof init?.body === "string"
|
|
931
|
-
|
|
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
|
+
}
|
|
932
990
|
}
|
|
933
991
|
}
|
|
992
|
+
clone() { return Object.setPrototypeOf(HostRequest.prototype.clone.call(this), PageRequest.prototype); }
|
|
934
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
|
+
});
|
|
935
998
|
|
|
936
999
|
// fetch(request) reads the Request's internal state, as Chromium's
|
|
937
1000
|
// Request::CreateRequestWithRequestOrString does with GetRequest(). Page
|
|
938
1001
|
// script may shadow url, method or bodyUsed on the instance; that changes
|
|
939
1002
|
// what script reads, not what is fetched.
|
|
940
1003
|
const requestSlot = (name) => {
|
|
941
|
-
const get = Object.getOwnPropertyDescriptor(
|
|
1004
|
+
const get = Object.getOwnPropertyDescriptor(HostRequest.prototype, name).get;
|
|
942
1005
|
return (request) => get.call(request);
|
|
943
1006
|
};
|
|
944
1007
|
const slots = Object.fromEntries(["url", "method", "headers", "mode", "credentials", "redirect", "signal", "bodyUsed"]
|
|
945
1008
|
.map((name) => [name, requestSlot(name)]));
|
|
946
|
-
const cloneRequest =
|
|
1009
|
+
const cloneRequest = HostRequest.prototype.clone;
|
|
947
1010
|
/** The input when it is a platform Request (a brand check, not instanceof). */
|
|
948
1011
|
const platformRequest = (input) => {
|
|
949
1012
|
if (input === null || typeof input !== "object") return null;
|
|
@@ -989,10 +1052,13 @@ export function createPageRealm({ window, document, mode, hooks }) {
|
|
|
989
1052
|
}
|
|
990
1053
|
|
|
991
1054
|
function makeResponse(data) {
|
|
992
|
-
const headers = new
|
|
1055
|
+
const headers = new PageHeaders(data.headers ?? []);
|
|
993
1056
|
const body = data.body instanceof ReadableStream ? data.body
|
|
994
1057
|
: data.body instanceof Uint8Array ? data.body : new Uint8Array(0);
|
|
995
|
-
|
|
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 } });
|
|
996
1062
|
const response = {
|
|
997
1063
|
type: data.type ?? "basic",
|
|
998
1064
|
url: data.url ?? "",
|
|
@@ -1024,7 +1090,7 @@ export function createPageRealm({ window, document, mode, hooks }) {
|
|
|
1024
1090
|
async function pageFetch(input, init = {}) {
|
|
1025
1091
|
const request = await describeRequest(input, init ?? {});
|
|
1026
1092
|
const signal = request.signal;
|
|
1027
|
-
if (signal?.aborted) throw abortError();
|
|
1093
|
+
if (disposed || signal?.aborted) throw signal?.reason ?? abortError();
|
|
1028
1094
|
const scheme = new URL(request.url).protocol;
|
|
1029
1095
|
let pending;
|
|
1030
1096
|
let networkPending = null;
|
|
@@ -1047,21 +1113,38 @@ export function createPageRealm({ window, document, mode, hooks }) {
|
|
|
1047
1113
|
} else {
|
|
1048
1114
|
const { signal: _signal, ...plain } = request;
|
|
1049
1115
|
networkPending = hooks.pageFetch(plain);
|
|
1116
|
+
pendingPageFetches.add(networkPending);
|
|
1050
1117
|
// Network and CORS failures reach the page as a bare TypeError, as in
|
|
1051
1118
|
// browsers; the reason is only logged to the console.
|
|
1052
1119
|
pending = networkPending.catch((error) => {
|
|
1053
1120
|
const text = `fetch ${plain.url}: ${error?.message ?? error}`;
|
|
1054
|
-
|
|
1055
|
-
|
|
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();
|
|
1056
1126
|
throw new TypeError("Failed to fetch");
|
|
1057
1127
|
});
|
|
1058
1128
|
}
|
|
1129
|
+
let abort;
|
|
1059
1130
|
const aborted = signal
|
|
1060
|
-
? new Promise((_, reject) =>
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
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
|
+
})
|
|
1064
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);
|
|
1065
1148
|
const data = await (aborted ? Promise.race([pending, aborted]) : pending);
|
|
1066
1149
|
hooks.afterTask();
|
|
1067
1150
|
return makeResponse(data);
|
|
@@ -1075,7 +1158,6 @@ export function createPageRealm({ window, document, mode, hooks }) {
|
|
|
1075
1158
|
let url = null;
|
|
1076
1159
|
let response = null;
|
|
1077
1160
|
let controller = null;
|
|
1078
|
-
let timer = null;
|
|
1079
1161
|
const xhr = {
|
|
1080
1162
|
UNSENT: 0, OPENED: 1, HEADERS_RECEIVED: 2, LOADING: 3, DONE: 4,
|
|
1081
1163
|
readyState: 0,
|
|
@@ -1101,7 +1183,7 @@ export function createPageRealm({ window, document, mode, hooks }) {
|
|
|
1101
1183
|
requestHeaders.append(String(name), String(value));
|
|
1102
1184
|
},
|
|
1103
1185
|
getResponseHeader(name) {
|
|
1104
|
-
return response
|
|
1186
|
+
return response ? HostHeaders.prototype.get.call(response.headers, String(name)) : null;
|
|
1105
1187
|
},
|
|
1106
1188
|
getAllResponseHeaders() {
|
|
1107
1189
|
if (!response) return "";
|
|
@@ -1113,15 +1195,19 @@ export function createPageRealm({ window, document, mode, hooks }) {
|
|
|
1113
1195
|
},
|
|
1114
1196
|
send(body = null) {
|
|
1115
1197
|
if (xhr.readyState !== 1) throw new DOMException("The object's state must be OPENED.", "InvalidStateError");
|
|
1116
|
-
controller = new AbortController();
|
|
1117
|
-
|
|
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;
|
|
1118
1204
|
fire("loadstart");
|
|
1119
1205
|
pageFetch(url, {
|
|
1120
1206
|
method,
|
|
1121
1207
|
headers: requestHeaders,
|
|
1122
1208
|
body: ["GET", "HEAD"].includes(method) ? null : body,
|
|
1123
1209
|
credentials: xhr.withCredentials ? "include" : "same-origin",
|
|
1124
|
-
signal:
|
|
1210
|
+
signal: requestController.signal,
|
|
1125
1211
|
}).then(async (result) => {
|
|
1126
1212
|
response = result;
|
|
1127
1213
|
xhr.status = result.status;
|
|
@@ -1130,19 +1216,27 @@ export function createPageRealm({ window, document, mode, hooks }) {
|
|
|
1130
1216
|
setState(2);
|
|
1131
1217
|
setState(3);
|
|
1132
1218
|
const bytes = await result.arrayBuffer();
|
|
1133
|
-
const text =
|
|
1219
|
+
const text = xhr.responseType === "arraybuffer" || xhr.responseType === "blob"
|
|
1220
|
+
? "" : new TextDecoder().decode(bytes);
|
|
1134
1221
|
xhr.responseText = ["", "text"].includes(xhr.responseType) ? text : "";
|
|
1135
1222
|
xhr.response = xhr.responseType === "json" ? safeJson(text)
|
|
1136
1223
|
: xhr.responseType === "arraybuffer" ? bytes
|
|
1137
|
-
: xhr.responseType === "blob" ? new Blob([bytes], { type:
|
|
1224
|
+
: xhr.responseType === "blob" ? new Blob([bytes], { type: HostHeaders.prototype.get.call(result.headers, "content-type") ?? "" })
|
|
1138
1225
|
: text;
|
|
1139
1226
|
setState(4);
|
|
1140
1227
|
fire("load");
|
|
1141
1228
|
fire("loadend");
|
|
1142
|
-
}
|
|
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;
|
|
1143
1233
|
xhr.status = 0;
|
|
1234
|
+
xhr.statusText = "";
|
|
1235
|
+
xhr.responseURL = "";
|
|
1236
|
+
xhr.response = null;
|
|
1237
|
+
xhr.responseText = "";
|
|
1144
1238
|
setState(4);
|
|
1145
|
-
fire(
|
|
1239
|
+
fire(timedOut ? "timeout" : requestController.signal.aborted ? "abort" : "error");
|
|
1146
1240
|
fire("loadend");
|
|
1147
1241
|
}).finally(() => {
|
|
1148
1242
|
if (timer !== null) cancel(timer);
|
|
@@ -1308,9 +1402,9 @@ export function createPageRealm({ window, document, mode, hooks }) {
|
|
|
1308
1402
|
fetch: function fetch(input, init = undefined) { return pageFetch(input, init); },
|
|
1309
1403
|
XMLHttpRequest,
|
|
1310
1404
|
WebSocket,
|
|
1311
|
-
Headers,
|
|
1405
|
+
Headers: PageHeaders,
|
|
1312
1406
|
Request: PageRequest,
|
|
1313
|
-
Response,
|
|
1407
|
+
Response: PageResponse,
|
|
1314
1408
|
AbortController,
|
|
1315
1409
|
AbortSignal,
|
|
1316
1410
|
FormData,
|
|
@@ -1476,6 +1570,7 @@ export function createPageRealm({ window, document, mode, hooks }) {
|
|
|
1476
1570
|
return Promise.resolve(values);
|
|
1477
1571
|
},
|
|
1478
1572
|
};
|
|
1573
|
+
installImageLoadingAttributes(elementInterfacePrototypes.get("HTMLImageElement"), nodeOf);
|
|
1479
1574
|
const { location, setHref } = locationBinding(document.URL, hooks.navigate);
|
|
1480
1575
|
const { History, history, pushEntry } = historyBinding(location, setHref);
|
|
1481
1576
|
const { Event, CustomEvent, ...uiEvents } = eventConstructors(realm);
|
|
@@ -1487,6 +1582,10 @@ export function createPageRealm({ window, document, mode, hooks }) {
|
|
|
1487
1582
|
const messaging = createMessaging({
|
|
1488
1583
|
realm, window, pageGlobal: () => pageGlobal, runTask, callPage, DOMException, frames: hooks.frames ?? null,
|
|
1489
1584
|
});
|
|
1585
|
+
const broadcasts = createBroadcastChannels({
|
|
1586
|
+
origin: hooks.frames?.self.origin() ?? new URL(document.URL).origin,
|
|
1587
|
+
EventTarget: eventTargets.EventTarget, realm, runTask, DOMException,
|
|
1588
|
+
});
|
|
1490
1589
|
|
|
1491
1590
|
const platform = platformObjects({
|
|
1492
1591
|
eventTargets, userAgent, userAgentDataMembers: chromeVersion ? userAgentDataMembers : null, mobile: mobileUserAgent, viewport: hooks.viewport,
|
|
@@ -1533,12 +1632,13 @@ export function createPageRealm({ window, document, mode, hooks }) {
|
|
|
1533
1632
|
// SES rewrites dynamic import() in evaluated classic scripts to this
|
|
1534
1633
|
// hook. Resolve it through the same module loader and network boundary as
|
|
1535
1634
|
// parser-inserted modules.
|
|
1536
|
-
__import__: (specifier) => importModule(String(specifier), currentScript?.src || document.URL),
|
|
1635
|
+
__import__: (specifier) => importModule(String(specifier), currentScript?.src || document.baseURI || document.URL),
|
|
1537
1636
|
performance: platform.performance,
|
|
1538
1637
|
PerformanceObserver: userTiming.PerformanceObserver,
|
|
1539
1638
|
crypto: platform.crypto,
|
|
1540
1639
|
...platform.interfaces,
|
|
1541
1640
|
CSSStyleDeclaration: cssStyleDeclarationInterface(realm),
|
|
1641
|
+
ValidityState: validity.Interface,
|
|
1542
1642
|
...trustedTypes.interfaces,
|
|
1543
1643
|
trustedTypes: trustedTypes.trustedTypes,
|
|
1544
1644
|
Event,
|
|
@@ -1591,15 +1691,19 @@ export function createPageRealm({ window, document, mode, hooks }) {
|
|
|
1591
1691
|
const tag = String(name).toLowerCase();
|
|
1592
1692
|
if (typeof constructor !== "function") throw new TypeError("Custom element constructor must be a function");
|
|
1593
1693
|
if (customElementConstructors.has(tag)) throw new DOMException("Custom element already defined", "NotSupportedError");
|
|
1694
|
+
let captureInitialAttributes;
|
|
1594
1695
|
const Adapter = class extends window.HTMLElement {
|
|
1595
1696
|
constructor() {
|
|
1596
1697
|
super();
|
|
1698
|
+
const initialAttributes = captureInitialAttributes?.(this);
|
|
1597
1699
|
const wrapper = realm.wrap(this);
|
|
1598
1700
|
Object.setPrototypeOf(wrapper, constructor.prototype);
|
|
1599
1701
|
const previous = constructingElement;
|
|
1600
1702
|
constructingElement = this;
|
|
1601
1703
|
try { Reflect.construct(constructor, []); }
|
|
1704
|
+
catch (error) { initialAttributes?.cancel(); throw error; }
|
|
1602
1705
|
finally { constructingElement = previous; }
|
|
1706
|
+
initialAttributes?.finish();
|
|
1603
1707
|
}
|
|
1604
1708
|
connectedCallback() {
|
|
1605
1709
|
const wrapper = realm.wrap(this);
|
|
@@ -1610,8 +1714,16 @@ export function createPageRealm({ window, document, mode, hooks }) {
|
|
|
1610
1714
|
if (typeof wrapper.disconnectedCallback === "function") callPage(wrapper.disconnectedCallback, wrapper, []);
|
|
1611
1715
|
}
|
|
1612
1716
|
};
|
|
1613
|
-
|
|
1717
|
+
captureInitialAttributes = installAttributeReactions(Adapter, constructor, realm, callPage);
|
|
1614
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
|
+
}
|
|
1615
1727
|
// Upgrades change :defined without a DOM mutation.
|
|
1616
1728
|
hooks.definedChanged?.(tag);
|
|
1617
1729
|
},
|
|
@@ -1626,6 +1738,7 @@ export function createPageRealm({ window, document, mode, hooks }) {
|
|
|
1626
1738
|
},
|
|
1627
1739
|
},
|
|
1628
1740
|
...INTERFACES,
|
|
1741
|
+
DOMStringMap,
|
|
1629
1742
|
Node: NodeInterface,
|
|
1630
1743
|
Text: TextInterface,
|
|
1631
1744
|
CharacterData: CharacterDataInterface,
|
|
@@ -1634,6 +1747,8 @@ export function createPageRealm({ window, document, mode, hooks }) {
|
|
|
1634
1747
|
Document: DocumentInterface,
|
|
1635
1748
|
HTMLDocument: DocumentInterface,
|
|
1636
1749
|
DocumentFragment: DocumentFragmentInterface,
|
|
1750
|
+
DOMParser,
|
|
1751
|
+
XMLSerializer,
|
|
1637
1752
|
...typeCheckInterfaces(),
|
|
1638
1753
|
addEventListener: windowEvents.addEventListener,
|
|
1639
1754
|
removeEventListener: windowEvents.removeEventListener,
|
|
@@ -1682,6 +1797,7 @@ export function createPageRealm({ window, document, mode, hooks }) {
|
|
|
1682
1797
|
postMessage: messaging.postMessage,
|
|
1683
1798
|
MessageChannel: messaging.MessageChannel,
|
|
1684
1799
|
MessagePort: messaging.MessagePort,
|
|
1800
|
+
BroadcastChannel: broadcasts.BroadcastChannel,
|
|
1685
1801
|
};
|
|
1686
1802
|
const accessors = {
|
|
1687
1803
|
innerWidth: viewportValue("width"),
|
|
@@ -1702,7 +1818,8 @@ export function createPageRealm({ window, document, mode, hooks }) {
|
|
|
1702
1818
|
hardenBindings();
|
|
1703
1819
|
// Dedicated workers get a compartment each, with these of the page's utilities.
|
|
1704
1820
|
workers = createWorkers({
|
|
1705
|
-
realm, documentUrl: () => document.URL,
|
|
1821
|
+
realm, documentUrl: () => document.URL, documentBaseUrl: () => document.baseURI || document.URL,
|
|
1822
|
+
fetchScript: hooks.fetchScript, runTask, callPage, reportError, DOMException,
|
|
1706
1823
|
constructorHelper,
|
|
1707
1824
|
pagePorts: messaging.ports,
|
|
1708
1825
|
globals: Object.fromEntries(["console", "atob", "btoa", "TextEncoder", "TextDecoder", "URL", "URLSearchParams",
|
|
@@ -1758,6 +1875,7 @@ export function createPageRealm({ window, document, mode, hooks }) {
|
|
|
1758
1875
|
});
|
|
1759
1876
|
Object.defineProperty(PagePromise, "length", { value: 1, configurable: true });
|
|
1760
1877
|
pageGlobal.Promise = PagePromise;
|
|
1878
|
+
pageGlobal.Error = errorInterface(pageGlobal.Error, hostErrorIsError);
|
|
1761
1879
|
// Namespace objects that libraries extend (reflect-metadata adds to
|
|
1762
1880
|
// Reflect, polyfills to JSON and Math): each page gets its own mutable
|
|
1763
1881
|
// copy with the same functions; the shared frozen intrinsics stay as they are.
|
|
@@ -1809,8 +1927,7 @@ export function createPageRealm({ window, document, mode, hooks }) {
|
|
|
1809
1927
|
__rejectSomeDirectEvalExpressions__: false,
|
|
1810
1928
|
});
|
|
1811
1929
|
evaluate = (source) => {
|
|
1812
|
-
|
|
1813
|
-
return evaluatePrepared({ code: escapeSesHtmlComments(classic.code), parsed: classic.parsed, strict: classic.strict });
|
|
1930
|
+
return evaluatePrepared(prepareClassicScript(source));
|
|
1814
1931
|
};
|
|
1815
1932
|
// Module code is already strict with its own scope; no global lifting.
|
|
1816
1933
|
evaluateModuleCode = (code) => compartment.evaluate(escapeSesHtmlComments(code, "module"), {
|
|
@@ -1877,7 +1994,7 @@ export function createPageRealm({ window, document, mode, hooks }) {
|
|
|
1877
1994
|
if (node.nodeType !== 1) continue;
|
|
1878
1995
|
for (const script of [node, ...node.querySelectorAll("script")]) {
|
|
1879
1996
|
if (script.tagName !== "SCRIPT" || !createdScripts.has(script) || startedScripts.has(script)) continue;
|
|
1880
|
-
if (!script.isConnected) continue;
|
|
1997
|
+
if (!script.isConnected || script.ownerDocument !== document) continue;
|
|
1881
1998
|
runInsertedScript(script);
|
|
1882
1999
|
}
|
|
1883
2000
|
}
|
|
@@ -1903,7 +2020,7 @@ export function createPageRealm({ window, document, mode, hooks }) {
|
|
|
1903
2020
|
runClassic(script, script.textContent);
|
|
1904
2021
|
return;
|
|
1905
2022
|
}
|
|
1906
|
-
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(
|
|
1907
2024
|
async (source) => {
|
|
1908
2025
|
runClassic(script, source, await preparedFor(prepareOffThread(source), source));
|
|
1909
2026
|
runTask(() => script.dispatchEvent(new window.Event("load")));
|
|
@@ -1928,7 +2045,9 @@ export function createPageRealm({ window, document, mode, hooks }) {
|
|
|
1928
2045
|
// phases, see module-transform.js.
|
|
1929
2046
|
|
|
1930
2047
|
function ensureFetched(url, type, sourceOverride = null) {
|
|
1931
|
-
|
|
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}`;
|
|
1932
2051
|
let record = modules.get(key);
|
|
1933
2052
|
if (record) return record.fetched;
|
|
1934
2053
|
record = { url, type, state: "fetching", namespace: Object.create(null), deps: [], error: null };
|
|
@@ -2048,11 +2167,12 @@ export function createPageRealm({ window, document, mode, hooks }) {
|
|
|
2048
2167
|
async function runModuleScript(script) {
|
|
2049
2168
|
const src = script.getAttribute("src");
|
|
2050
2169
|
try {
|
|
2051
|
-
|
|
2052
|
-
|
|
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 ?? "");
|
|
2053
2173
|
if (src) runTask(() => script.dispatchEvent(new window.Event("load")));
|
|
2054
2174
|
} catch (error) {
|
|
2055
|
-
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);
|
|
2056
2176
|
if (src) runTask(() => script.dispatchEvent(new window.Event("error")));
|
|
2057
2177
|
}
|
|
2058
2178
|
}
|
|
@@ -2178,7 +2298,7 @@ export function createPageRealm({ window, document, mode, hooks }) {
|
|
|
2178
2298
|
continue;
|
|
2179
2299
|
}
|
|
2180
2300
|
try {
|
|
2181
|
-
const url = new URL(src, document.URL).href;
|
|
2301
|
+
const url = new URL(src, script.baseURI || document.URL).href;
|
|
2182
2302
|
if ((script.getAttribute("type") ?? "").trim().toLowerCase() === "module") {
|
|
2183
2303
|
ensureFetched(resolveModuleSpecifier(url, document.URL, importMap), null).then(loadGraph).catch(() => {});
|
|
2184
2304
|
} else {
|
|
@@ -2208,7 +2328,7 @@ export function createPageRealm({ window, document, mode, hooks }) {
|
|
|
2208
2328
|
let source;
|
|
2209
2329
|
try {
|
|
2210
2330
|
source = src
|
|
2211
|
-
? 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))
|
|
2212
2332
|
: script.textContent ?? "";
|
|
2213
2333
|
} catch (error) {
|
|
2214
2334
|
reportError(error);
|
|
@@ -2284,6 +2404,12 @@ export function createPageRealm({ window, document, mode, hooks }) {
|
|
|
2284
2404
|
messaging.receive(message);
|
|
2285
2405
|
},
|
|
2286
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
|
+
|
|
2287
2413
|
/** A MediaSource object, or the one registered at a blob URL. */
|
|
2288
2414
|
isMediaSource(value) {
|
|
2289
2415
|
return mediaScope.isMediaSource(value);
|
|
@@ -2297,9 +2423,15 @@ export function createPageRealm({ window, document, mode, hooks }) {
|
|
|
2297
2423
|
|
|
2298
2424
|
dispose() {
|
|
2299
2425
|
disposed = true;
|
|
2426
|
+
for (const pending of pendingPageFetches) pending.abort?.();
|
|
2427
|
+
pendingPageFetches.clear();
|
|
2428
|
+
disposeDocumentCanvases(document);
|
|
2300
2429
|
stopRejectionEvents();
|
|
2430
|
+
layoutObservers.dispose();
|
|
2431
|
+
parserWindow?.happyDOM?.close?.();
|
|
2301
2432
|
if (blobUrlFallback === mediaScope) blobUrlFallback = null;
|
|
2302
2433
|
messaging.dispose();
|
|
2434
|
+
broadcasts.dispose();
|
|
2303
2435
|
workers?.dispose();
|
|
2304
2436
|
// Leaving the document closes its WebSockets (1001 "going away").
|
|
2305
2437
|
for (const close of openSockets) close();
|
|
@@ -2478,56 +2610,6 @@ function directEvalHelper(compartment, pageGlobal, scriptText) {
|
|
|
2478
2610
|
};
|
|
2479
2611
|
}
|
|
2480
2612
|
|
|
2481
|
-
/**
|
|
2482
|
-
* window.History and window.history for one document. The methods live on a
|
|
2483
|
-
* per-document History.prototype that pages may wrap (dev servers do, to see
|
|
2484
|
-
* client-side routing). pushState/replaceState change the URL and state
|
|
2485
|
-
* without navigating or firing events; traversal is not modelled.
|
|
2486
|
-
*/
|
|
2487
|
-
function historyBinding(location, setHref) {
|
|
2488
|
-
let state = null;
|
|
2489
|
-
let length = 1;
|
|
2490
|
-
const change = (data, url) => {
|
|
2491
|
-
if (url === undefined || url === null) return;
|
|
2492
|
-
const target = new URL(String(url), location.href);
|
|
2493
|
-
if (target.origin !== new URL(location.href).origin) {
|
|
2494
|
-
throw new DOMException(`A history state object with URL '${target.href}' cannot be created in a document with origin '${new URL(location.href).origin}'.`, "SecurityError");
|
|
2495
|
-
}
|
|
2496
|
-
setHref(target.href);
|
|
2497
|
-
};
|
|
2498
|
-
function History() {
|
|
2499
|
-
throw new TypeError("Illegal constructor");
|
|
2500
|
-
}
|
|
2501
|
-
History.prototype = {
|
|
2502
|
-
constructor: History,
|
|
2503
|
-
get length() {
|
|
2504
|
-
return length;
|
|
2505
|
-
},
|
|
2506
|
-
get state() {
|
|
2507
|
-
return state;
|
|
2508
|
-
},
|
|
2509
|
-
scrollRestoration: "auto",
|
|
2510
|
-
back() {},
|
|
2511
|
-
forward() {},
|
|
2512
|
-
go() {},
|
|
2513
|
-
pushState(data, _title, url = undefined) {
|
|
2514
|
-
change(data, url);
|
|
2515
|
-
state = data ?? null;
|
|
2516
|
-
length++;
|
|
2517
|
-
},
|
|
2518
|
-
replaceState(data, _title, url = undefined) {
|
|
2519
|
-
change(data, url);
|
|
2520
|
-
state = data ?? null;
|
|
2521
|
-
},
|
|
2522
|
-
};
|
|
2523
|
-
// A fragment navigation adds an entry with no state (a same-URL one replaces it).
|
|
2524
|
-
const pushEntry = (replace) => {
|
|
2525
|
-
state = null;
|
|
2526
|
-
if (!replace) length++;
|
|
2527
|
-
};
|
|
2528
|
-
return { History, history: Object.create(History.prototype), pushEntry };
|
|
2529
|
-
}
|
|
2530
|
-
|
|
2531
2613
|
/** window.location; `setHref` moves it for same-document (fragment) navigations. */
|
|
2532
2614
|
function locationBinding(href, navigate) {
|
|
2533
2615
|
const url = () => new URL(href);
|
|
@@ -2773,7 +2855,18 @@ function computedStyleBinding(style, { custom = null, bounds = null, element = n
|
|
|
2773
2855
|
const horizontal = (style.padding[1] ?? 0) + (style.padding[3] ?? 0) + (style.borderWidths[1] ?? 0) + (style.borderWidths[3] ?? 0);
|
|
2774
2856
|
const vertical = (style.padding[0] ?? 0) + (style.padding[2] ?? 0) + (style.borderWidths[0] ?? 0) + (style.borderWidths[2] ?? 0);
|
|
2775
2857
|
const transform = style.transform;
|
|
2776
|
-
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
|
+
};
|
|
2777
2870
|
Object.assign(values, {
|
|
2778
2871
|
display: style.display,
|
|
2779
2872
|
position: style.position,
|
|
@@ -2783,10 +2876,16 @@ function computedStyleBinding(style, { custom = null, bounds = null, element = n
|
|
|
2783
2876
|
backgroundImage: style.backgroundImage == null ? "none" : String(style.backgroundImage),
|
|
2784
2877
|
fontSize: px(style.fontSize),
|
|
2785
2878
|
fontWeight: String(style.fontWeight),
|
|
2879
|
+
fontStyle: style.fontStyle ?? "normal",
|
|
2880
|
+
fontStretch: `${style.fontStretch ?? 100}%`,
|
|
2786
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",
|
|
2787
2884
|
lineHeight: style.lineHeight == null ? "normal" : px(style.lineHeight),
|
|
2788
2885
|
textAlign: style.textAlign,
|
|
2789
2886
|
whiteSpace: style.whiteSpace,
|
|
2887
|
+
WebkitLineClamp: style.webkitLineClamp == null ? "none" : String(style.webkitLineClamp),
|
|
2888
|
+
WebkitBoxOrient: style.webkitBoxOrient,
|
|
2790
2889
|
verticalAlign: typeof style.verticalAlign === "number" ? px(style.verticalAlign) : String(style.verticalAlign),
|
|
2791
2890
|
textOverflow: style.textOverflow,
|
|
2792
2891
|
boxSizing: style.boxSizing,
|
|
@@ -2811,9 +2910,8 @@ function computedStyleBinding(style, { custom = null, bounds = null, element = n
|
|
|
2811
2910
|
alignContent: style.alignContent,
|
|
2812
2911
|
alignItems: style.alignItems,
|
|
2813
2912
|
alignSelf: style.alignSelf,
|
|
2814
|
-
rowGap: style.
|
|
2815
|
-
columnGap: style.
|
|
2816
|
-
borderRadius: length(style.borderRadius),
|
|
2913
|
+
rowGap: style.rowGapNormal ? "normal" : length(style.rowGap),
|
|
2914
|
+
columnGap: style.columnGapNormal ? "normal" : length(style.columnGap),
|
|
2817
2915
|
transform: !transform ? "none"
|
|
2818
2916
|
: `matrix(${transform.scaleX ?? 1}, 0, 0, ${transform.scaleY ?? 1}, ${translate(transform.x, bounds?.width ?? 0)}, ${translate(transform.y, bounds?.height ?? 0)})`,
|
|
2819
2917
|
visibility: style.visibility ?? "visible",
|
|
@@ -2823,6 +2921,23 @@ function computedStyleBinding(style, { custom = null, bounds = null, element = n
|
|
|
2823
2921
|
overflowX: style.overflowScrollX ?? (style.overflowX === "clip" ? "clip" : "visible"),
|
|
2824
2922
|
overflowY: style.overflowScrollY ?? (style.overflowY === "clip" ? "clip" : "visible"),
|
|
2825
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"];
|
|
2826
2941
|
values.overflow = values.overflowX === values.overflowY ? values.overflowX : `${values.overflowX} ${values.overflowY}`;
|
|
2827
2942
|
sides.forEach((side, index) => {
|
|
2828
2943
|
values[`margin${side}`] = length(style.margin[index]);
|
|
@@ -2843,6 +2958,8 @@ function computedStyleBinding(style, { custom = null, bounds = null, element = n
|
|
|
2843
2958
|
};
|
|
2844
2959
|
return {
|
|
2845
2960
|
...values,
|
|
2961
|
+
webkitLineClamp: values.WebkitLineClamp,
|
|
2962
|
+
webkitBoxOrient: values.WebkitBoxOrient,
|
|
2846
2963
|
length: names.length,
|
|
2847
2964
|
item: (index) => names[Number(index)] ?? "",
|
|
2848
2965
|
getPropertyValue: (name) => {
|
|
@@ -2858,6 +2975,7 @@ function computedStyleBinding(style, { custom = null, bounds = null, element = n
|
|
|
2858
2975
|
|
|
2859
2976
|
function formatConsoleValue(value) {
|
|
2860
2977
|
if (typeof value === "string") return value;
|
|
2978
|
+
if (hostErrorIsError?.(value)) return diagnosticError(value);
|
|
2861
2979
|
try {
|
|
2862
2980
|
return JSON.stringify(value) ?? String(value);
|
|
2863
2981
|
} catch {
|
|
@@ -2865,6 +2983,19 @@ function formatConsoleValue(value) {
|
|
|
2865
2983
|
}
|
|
2866
2984
|
}
|
|
2867
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
|
+
|
|
2868
2999
|
function safeJson(text) {
|
|
2869
3000
|
try {
|
|
2870
3001
|
return JSON.parse(text);
|