buninu-browser 0.7.0 → 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (142) hide show
  1. package/AGENTS.md +5 -0
  2. package/README.md +90 -1
  3. package/buninu-browser.js +1 -1
  4. package/frontends/wayland/README.md +35 -2
  5. package/frontends/wayland/client.js +8 -6
  6. package/frontends/wayland/clipboard.js +1 -1
  7. package/frontends/wayland/index.js +1 -1
  8. package/frontends/wayland/input.js +1 -1
  9. package/frontends/wayland/resource-io.js +6 -0
  10. package/frontends/wayland/transport-posix.js +120 -0
  11. package/frontends/wayland/transport-win32.js +9 -0
  12. package/frontends/wayland/transport.js +3 -120
  13. package/frontends/wayland/win32-resources.js +80 -0
  14. package/frontends/wayland/win32-wire.js +62 -0
  15. package/lib/audio/biquad.js +25 -0
  16. package/lib/audio/piano.js +6 -3
  17. package/lib/audio/playback-buffer.js +5 -5
  18. package/lib/audio/pulse-client.js +24 -5
  19. package/lib/audio/web-audio.js +570 -0
  20. package/lib/browser-dialog.js +116 -0
  21. package/lib/cdp-server.js +43 -11
  22. package/lib/file-picker.js +48 -0
  23. package/lib/headless-shell.js +34 -12
  24. package/lib/layout/text-layout.js +112 -19
  25. package/lib/media/mp4-frame-times.js +93 -0
  26. package/lib/paint/direct-canvas.ts +39 -0
  27. package/lib/paint/display-list.js +37 -12
  28. package/lib/paint/raster-readback.js +16 -7
  29. package/lib/renderer/animation-frames.js +10 -5
  30. package/lib/renderer/bindings.js +69 -16
  31. package/lib/renderer/canvas-2d.js +265 -15
  32. package/lib/renderer/canvas-round-rect.js +36 -0
  33. package/lib/renderer/css-animations.js +173 -0
  34. package/lib/renderer/dialog-client.js +30 -0
  35. package/lib/renderer/dialog-worker.js +35 -0
  36. package/lib/renderer/eval-environment.js +28 -0
  37. package/lib/renderer/file-input.js +49 -0
  38. package/lib/renderer/host.js +33 -4
  39. package/lib/renderer/html-drag.js +77 -0
  40. package/lib/renderer/image-loading.js +45 -2
  41. package/lib/renderer/main.js +4 -0
  42. package/lib/renderer/module-transform.js +5 -3
  43. package/lib/renderer/page-builtins.js +59 -0
  44. package/lib/renderer/page-realm.js +177 -40
  45. package/lib/renderer/page-renderer.js +397 -49
  46. package/lib/renderer/piano-input.js +3 -1
  47. package/lib/renderer/raster-surface.js +18 -0
  48. package/lib/renderer/script-rewrite.js +165 -9
  49. package/lib/renderer/video-presentation.js +5 -4
  50. package/lib/renderer/webgl/clip.js +24 -0
  51. package/lib/renderer/webgl/context.js +158 -0
  52. package/lib/renderer/webgl/cpu-fragment.js +211 -0
  53. package/lib/renderer/webgl/draw-cache.js +22 -0
  54. package/lib/renderer/webgl/pipeline.js +70 -0
  55. package/lib/renderer/webgl/shaders.js +58 -0
  56. package/lib/renderer/webgl/textures.js +43 -0
  57. package/lib/renderer/window-named-properties.js +46 -0
  58. package/lib/screenshot-cli.js +4 -7
  59. package/lib/style/animation-values.js +29 -0
  60. package/lib/style/computed-style.js +109 -13
  61. package/lib/style/transform.js +44 -0
  62. package/lib/window-size.js +10 -0
  63. package/package.json +1 -1
  64. package/patches/happy-dom/0007-scope-content-models-to-html-namespace.patch +29 -0
  65. package/patches/happy-dom/README.md +16 -0
  66. package/test/animation-frames.test.js +15 -0
  67. package/test/canvas-2d.test.js +252 -4
  68. package/test/canvas-frame-pacing.test.js +7 -0
  69. package/test/cdp-viewers.test.js +36 -0
  70. package/test/classic-games-patches/0001-fix-game-initialization.patch +177 -0
  71. package/test/classic-games-patches/README.md +156 -0
  72. package/test/classic-games-scenarios.js +133 -0
  73. package/test/clipboard.test.js +16 -0
  74. package/test/computed-style.test.js +18 -0
  75. package/test/css-animation-lifecycle.test.js +28 -0
  76. package/test/css-animations.test.js +83 -0
  77. package/test/direct-canvas.test.js +39 -0
  78. package/test/dynamic-inline-handlers.test.js +25 -0
  79. package/test/eval-declarations.test.js +22 -0
  80. package/test/file-picker.test.js +26 -0
  81. package/test/fixtures/canvas-damage.js +23 -0
  82. package/test/fixtures/canvas-frame-pacing.js +35 -0
  83. package/test/fixtures/direct-canvas.js +96 -0
  84. package/test/fixtures/javascript-dialogs.html +8 -0
  85. package/test/fixtures/media-audio-lifetime.js +65 -0
  86. package/test/fixtures/renderer-memory.js +7 -5
  87. package/test/fixtures/video-black-replay.js +8 -6
  88. package/test/fixtures/video-slow-end.js +39 -0
  89. package/test/fixtures/web-audio-renderer.js +45 -0
  90. package/test/flex.test.js +26 -0
  91. package/test/grid.test.js +13 -0
  92. package/test/headless-shell.test.js +9 -0
  93. package/test/headless-window-size.test.js +28 -0
  94. package/test/html-rcdata.test.js +35 -0
  95. package/test/image-decode.test.js +18 -0
  96. package/test/javascript-dialog.test.js +83 -0
  97. package/test/manual-breakout-paddle.js +42 -0
  98. package/test/manual-breakout-static.js +35 -0
  99. package/test/manual-classic-games.js +41 -0
  100. package/test/manual-clumsy-bird-three-pipes.js +81 -0
  101. package/test/manual-clumsy-bird.js +52 -0
  102. package/test/manual-hextris.js +30 -0
  103. package/test/manual-numbered-music.js +24 -0
  104. package/test/manual-t-rex-runner.js +49 -0
  105. package/test/manual-threejs-scene.js +64 -0
  106. package/test/manual-webgl-benchmark.js +27 -0
  107. package/test/manual-webgl-cpu.html +26 -0
  108. package/test/media-audio-lifetime.test.js +8 -0
  109. package/test/mp4-frame-times.test.js +33 -0
  110. package/test/page-scripts.test.js +96 -1
  111. package/test/piano-input.test.js +15 -2
  112. package/test/piano.test.js +11 -2
  113. package/test/playback-buffer.test.js +11 -0
  114. package/test/pointer-drag.test.js +33 -0
  115. package/test/positioning.test.js +82 -0
  116. package/test/pulse-client.test.js +51 -2
  117. package/test/raster-readback.test.js +14 -0
  118. package/test/script-rewrite.test.js +61 -3
  119. package/test/video-slow-end.test.js +6 -0
  120. package/test/viewport-keyboard-overflow.test.js +25 -0
  121. package/test/wayland-win32-resources.test.js +47 -0
  122. package/test/wayland-win32-wire.test.js +59 -0
  123. package/test/web-audio-renderer.test.js +8 -0
  124. package/test/web-audio.test.js +264 -0
  125. package/test/webgl-clip.test.js +9 -0
  126. package/test/webgl-cpu-fragment.test.js +43 -0
  127. package/test/window-named-properties.test.js +41 -0
  128. package/vendor/happy-dom/packages/happy-dom/src/config/HTMLElementConfig.ts +2 -2
  129. package/vendor/happy-dom/packages/happy-dom/src/config/HTMLElementConfigContentModelEnum.ts +1 -0
  130. package/vendor/happy-dom/packages/happy-dom/src/html-parser/HTMLParser.ts +23 -3
  131. package/vendor/happy-dom/packages/happy-dom/src/query-selector/SelectorParser.ts +1 -1
  132. package/vendor/land-renderer/LICENSE +21 -0
  133. package/vendor/land-renderer/README.md +14 -0
  134. package/vendor/land-renderer/direct-compositor.ts +443 -0
  135. package/vendor/land-renderer/frame.js +88 -0
  136. package/vendor/land-renderer/png-fast.js +85 -0
  137. package/vendor/land-renderer/raw-frames.js +66 -0
  138. package/vendor/land-renderer/render-types.ts +7 -0
  139. package/vendor/land-renderer/renderer.ts +134 -0
  140. package/vendor/land-renderer/snapshot.js +37 -0
  141. package/vendor/land-renderer/viewport.js +18 -0
  142. package/vendor/ses/dist/ses.mjs +1 -1
@@ -1,3 +1,9 @@
1
+ import {installPagePrimitiveBuiltins} from './page-builtins.js';
2
+ import {resolveAffine} from '../style/transform.js';
3
+ import { drainMicrotasks } from "bun:jsc";
4
+ import {fileReaderInterface} from './file-input.js';
5
+ import { evalEnvironment, declareEvalBinding } from "./eval-environment.js";
6
+ import { windowNamedProperties } from "./window-named-properties.js";
1
7
  import {selectionBinding} from './document-selection.js';
2
8
  /**
3
9
  * The JavaScript environment of one document.
@@ -17,14 +23,14 @@ import {
17
23
  } from "./bindings.js";
18
24
  import { resolveModuleSpecifier, transformModule } from "./module-transform.js";
19
25
  import {
20
- CONSTRUCTOR_HELPER, DIRECT_EVAL_HELPER, DIRECT_EVAL_STRICT, escapeSesHtmlComments, prepareDirectEvalSource,
26
+ MEMBER_HELPER, CONSTRUCTOR_HELPER, DIRECT_EVAL_HELPER, DIRECT_EVAL_ENV, MAKE_EVAL_ENV, evalDeclarations, DIRECT_EVAL_STRICT, escapeSesHtmlComments, prepareDirectEvalSource,
21
27
  DIRECT_EVAL_SCOPE_HELPER, REFERENCE_HELPER, referenceHelper, restoreSourceText, THIS_HELPER,
22
- rewriteClassicScriptSource, rewriteConstructorReads, rewriteModuleSource,
28
+ rewriteClassicScriptSource, rewriteConstructorReads, rewriteMemberReads, rewriteModuleSource,
23
29
  } from "./script-rewrite.js";
24
30
  import { prepareClassicScript } from "./prepared-script-cache.js";
25
31
  import { computeElementStyle, mediaQueryMatches, resolveVariables } from "../style/computed-style.js";
26
32
  import { asIncumbent, createMessaging } from "./messaging.js";
27
- import { canvasContext, resetCanvas, disposeDocumentCanvases, encodeCanvas } from "./canvas-2d.js";
33
+ import { canvasRenderingContextInterface, canvasContext, resetCanvas, disposeDocumentCanvases, encodeCanvas } from "./canvas-2d.js";
28
34
  import { createWorkers } from "./workers.js";
29
35
  import { createTimers } from "./timers.js";
30
36
  import { createAnimationFrames, supportsFrameCheckpoint } from "./animation-frames.js";
@@ -37,6 +43,7 @@ import { installAttributeReactions } from "./custom-element-reactions.js";
37
43
  import { createLayoutObservers } from "./layout-observers.js";
38
44
  import { createIdleCallbacks } from "./idle-callbacks.js";
39
45
  import { createBroadcastChannels } from "./broadcast-channel.js";
46
+ import { createWebAudioScope } from "../audio/web-audio.js";
40
47
  import { createInertWindow } from "../happy-dom/parser.js";
41
48
  import { historyBinding } from "./history.js";
42
49
  import { installImageLoadingAttributes } from "./image-loading.js";
@@ -182,6 +189,7 @@ const INTERFACE_MEMBERS = {
182
189
  options: ["HTMLSelectElement", "HTMLDataListElement"], selectedOptions: ["HTMLSelectElement"], selectedIndex: ["HTMLSelectElement"],
183
190
  value: [...FORM_CONTROLS, "HTMLOptionElement", "HTMLOutputElement", "HTMLDataElement", "HTMLLIElement", "HTMLMeterElement",
184
191
  "HTMLProgressElement", "HTMLParamElement"],
192
+ files: ["HTMLInputElement"], showPicker: ["HTMLInputElement"],
185
193
  checked: ["HTMLInputElement"], selected: ["HTMLOptionElement"],
186
194
  disabled: [...FORM_CONTROLS, "HTMLOptionElement", "HTMLOptGroupElement", "HTMLFieldSetElement", "HTMLLinkElement", "HTMLStyleElement"],
187
195
  type: [...FORM_CONTROLS, "HTMLScriptElement", "HTMLLinkElement", "HTMLOListElement", "HTMLUListElement", "HTMLLIElement",
@@ -308,6 +316,7 @@ export function createPageRealm({ window, document, mode, hooks }) {
308
316
  };
309
317
  let parsingHead = false;
310
318
  let currentScript = null;
319
+ let taskDepth = 0, checkingMicrotasks = false;
311
320
  let pageGlobal = null;
312
321
  let evaluate;
313
322
  // Runs a classic script already prepared off the main thread (SES mode only).
@@ -333,6 +342,7 @@ export function createPageRealm({ window, document, mode, hooks }) {
333
342
  /** Runs page code as a task: errors are reported, then the renderer may update. */
334
343
  const runTask = (task) => {
335
344
  if (disposed) return undefined;
345
+ taskDepth++;
336
346
  if (mediaScope) blobUrlFallback = mediaScope;
337
347
  try {
338
348
  return blobUrlContext.run(mediaScope, () => asIncumbent(hooks.frames?.self, task));
@@ -340,6 +350,14 @@ export function createPageRealm({ window, document, mode, hooks }) {
340
350
  reportError(error);
341
351
  return undefined;
342
352
  } finally {
353
+ // HTML's clean-up-after-running-script checkpoint happens before
354
+ // restoring currentScript. Nested script calls must not drain early.
355
+ taskDepth--;
356
+ if (!taskDepth && !checkingMicrotasks && supportsFrameCheckpoint()) {
357
+ checkingMicrotasks = true;
358
+ try { drainMicrotasks(); }
359
+ finally { checkingMicrotasks = false; }
360
+ }
343
361
  hooks.afterTask();
344
362
  }
345
363
  };
@@ -355,12 +373,14 @@ export function createPageRealm({ window, document, mode, hooks }) {
355
373
  }
356
374
  };
357
375
 
376
+ const inlineHandlerSources = new WeakMap();
358
377
  let documentSelection;
359
378
  const realm = new BindingRealm(window, {
360
379
  touch: (node, features) => {
361
380
  if (!node || node === document || node.ownerDocument === document) hooks.touch(node, features);
362
381
  },
363
382
  boundsOf: hooks.boundsOf,
383
+ mouseOffset: hooks.mouseOffset,
364
384
  computedStyle: hooks.computedStyle,
365
385
  // The engine clock's reading when this document's timeline started (see pageNow).
366
386
  timeOriginOffset: () => documentStarted,
@@ -381,6 +401,8 @@ export function createPageRealm({ window, document, mode, hooks }) {
381
401
  mediaError: (element) => hooks.mediaError?.(element) ?? null,
382
402
  mediaBuffered: (element) => hooks.mediaBuffered?.(element) ?? { length: 0, start: 0, end: 0 },
383
403
  call: callPage,
404
+ prepareEvent: prepareInlineEvent,
405
+ prepareHandler: prepareInlineHandler,
384
406
  readyState: () => readyState,
385
407
  body: () => parsingHead ? null : document.body,
386
408
  documentCookie: () => hooks.documentCookie?.() ?? "",
@@ -392,6 +414,9 @@ export function createPageRealm({ window, document, mode, hooks }) {
392
414
  if (element.tagName === "SCRIPT") createdScripts.add(element);
393
415
  },
394
416
  inserted: (nodes) => startInsertedScripts(nodes),
417
+ execCommand: hooks.execCommand,
418
+ copyEnabled: hooks.copyEnabled,
419
+ pickFile: (element)=>hooks.pickFile?.(element),
395
420
  activateLink: (anchor) => hooks.activateLink?.(anchor),
396
421
  submitForm: (form, submitter) => hooks.submitForm?.(form, submitter),
397
422
  contentWindow: (node) => messaging.contentWindow(node),
@@ -406,6 +431,10 @@ export function createPageRealm({ window, document, mode, hooks }) {
406
431
  const documentBinding = realm.wrap(document);
407
432
  const DOMStringMap = function DOMStringMap() { throw new TypeError("Illegal constructor"); };
408
433
  realm.datasetPrototype = DOMStringMap.prototype;
434
+ const DOMTokenList = function DOMTokenList() { throw new TypeError('Illegal constructor'); };
435
+ DOMTokenList.prototype=mutableBindingPrototype(INTERFACES.DOMTokenList.prototype,Object.prototype);
436
+ Object.defineProperty(DOMTokenList.prototype,'constructor',{value:DOMTokenList,writable:true,configurable:true});
437
+ realm.tokenListPrototype=DOMTokenList.prototype;
409
438
  const validityMembers = {};
410
439
  for (const key of ["badInput", "customError", "patternMismatch", "rangeOverflow", "rangeUnderflow", "stepMismatch", "tooLong", "tooShort", "typeMismatch", "valid", "valueMissing"]) {
411
440
  Object.defineProperty(validityMembers, key, { get() { return Boolean(this[key]); }, enumerable: true });
@@ -416,7 +445,7 @@ export function createPageRealm({ window, document, mode, hooks }) {
416
445
  // Node.prototype inherits EventTarget.prototype (DOM §4.4), as Window.prototype does.
417
446
  NodeInterface.prototype = mutableBindingPrototype(INTERFACES.Node.prototype, eventTargets.EventTarget.prototype);
418
447
  const WindowInterface = function Window() { throw new TypeError("Illegal constructor"); };
419
- WindowInterface.prototype = Object.create(eventTargets.EventTarget.prototype);
448
+ WindowInterface.prototype = Object.create(windowNamedProperties(document, realm, eventTargets.EventTarget.prototype));
420
449
  Object.defineProperty(WindowInterface.prototype, "constructor", { value: WindowInterface, writable: true, configurable: true });
421
450
  Object.defineProperty(WindowInterface, Symbol.hasInstance, { value: (value) => value === pageGlobal });
422
451
  Object.defineProperty(NodeInterface.prototype, "constructor", { value: NodeInterface, writable: true, configurable: true });
@@ -426,6 +455,11 @@ export function createPageRealm({ window, document, mode, hooks }) {
426
455
  }
427
456
  realm.nodePrototype = NodeInterface.prototype;
428
457
  const parentPrototype = mutableBindingPrototype(Object.getPrototypeOf(INTERFACES.Element.prototype), NodeInterface.prototype);
458
+ const elementDocumentCollections={};
459
+ for(const name of ['getElementsByTagName','getElementsByTagNameNS','getElementsByClassName']) {
460
+ elementDocumentCollections[name]=Object.getOwnPropertyDescriptor(parentPrototype,name);
461
+ delete parentPrototype[name];
462
+ }
429
463
  // The ParentNode mixin's members (children, querySelector, append, ...) are
430
464
  // own properties of Element.prototype, Document.prototype and
431
465
  // DocumentFragment.prototype, as WebIDL places them: ShadyDOM saves the
@@ -440,6 +474,7 @@ export function createPageRealm({ window, document, mode, hooks }) {
440
474
  };
441
475
  const ElementInterface = function Element() { throw new TypeError("Illegal constructor"); };
442
476
  ElementInterface.prototype = withParentNodeMembers(mutableBindingPrototype(INTERFACES.Element.prototype, parentPrototype));
477
+ Object.defineProperties(ElementInterface.prototype,elementDocumentCollections);
443
478
  Object.defineProperty(ElementInterface.prototype, "constructor", { value: ElementInterface, writable: true, configurable: true });
444
479
  const HTMLElementInterface = function HTMLElement() {
445
480
  if (!new.target || !constructingElement) throw new TypeError("Illegal constructor");
@@ -573,6 +608,7 @@ export function createPageRealm({ window, document, mode, hooks }) {
573
608
  // getContext("2d") is how pages sample colours and draw. Other elements do
574
609
  // not inherit these.
575
610
  realm.selection=()=>documentSelection ??= selectionBinding(window,realm,nodeOf,()=>hooks.selectionChanged?.());
611
+ const CanvasRenderingContext2D=canvasRenderingContextInterface();
576
612
  const canvasPrototype = elementInterfacePrototypes.get("HTMLCanvasElement");
577
613
  for (const name of ["width", "height"]) {
578
614
  Object.defineProperty(canvasPrototype, name, {
@@ -595,6 +631,7 @@ export function createPageRealm({ window, document, mode, hooks }) {
595
631
  writable: true,
596
632
  value(type) {
597
633
  return canvasContext(nodeOf(this), String(type), {
634
+ prototype:CanvasRenderingContext2D.prototype,
598
635
  canvas: this, resolveImage: source => hooks.canvasImage?.(nodeOf(source)),
599
636
  parseFont: value => hooks.canvasFont?.(nodeOf(this), value),
600
637
  text: request => hooks.canvasText?.(nodeOf(this), request),
@@ -689,6 +726,7 @@ export function createPageRealm({ window, document, mode, hooks }) {
689
726
  realm.shadowRootPrototype = mutableBindingPrototype(INTERFACES.ShadowRoot.prototype, DocumentFragmentInterface.prototype);
690
727
  const DocumentInterface = function Document() { throw new TypeError("Illegal constructor"); };
691
728
  DocumentInterface.prototype = withParentNodeMembers(mutableBindingPrototype(INTERFACES.Document.prototype, parentPrototype));
729
+ Object.defineProperties(DocumentInterface.prototype,elementDocumentCollections);
692
730
  Object.defineProperty(DocumentInterface.prototype, "constructor", { value: DocumentInterface, writable: true, configurable: true });
693
731
  Object.setPrototypeOf(documentBinding, DocumentInterface.prototype);
694
732
  realm.documentPrototype = DocumentInterface.prototype;
@@ -757,6 +795,7 @@ export function createPageRealm({ window, document, mode, hooks }) {
757
795
  const animationFrames = supportsFrameCheckpoint() ? createAnimationFrames({
758
796
  invoke: (callback, timestamp) => runTask(() => callPage(callback, pageGlobal, [timestamp])),
759
797
  now: pageNow,
798
+ afterFrame: () => hooks.afterAnimationFrame?.(),
760
799
  }) : null;
761
800
  const idleCallbacks = createIdleCallbacks({
762
801
  now: () => hostPerformance.now(), setTimer: hostSetTimeout, clearTimer: hostClearTimeout,
@@ -1420,6 +1459,7 @@ export function createPageRealm({ window, document, mode, hooks }) {
1420
1459
  // A plain function: Web IDL operations are not async functions.
1421
1460
  fetch: function fetch(input, init = undefined) { return pageFetch(input, init); },
1422
1461
  XMLHttpRequest,
1462
+ FileReader:fileReaderInterface({EventTarget:eventTargets.EventTarget,realm,window,runTask}),
1423
1463
  WebSocket,
1424
1464
  Headers: PageHeaders,
1425
1465
  Request: PageRequest,
@@ -1589,7 +1629,11 @@ export function createPageRealm({ window, document, mode, hooks }) {
1589
1629
  return Promise.resolve(values);
1590
1630
  },
1591
1631
  };
1592
- installImageLoadingAttributes(elementInterfacePrototypes.get("HTMLImageElement"), nodeOf);
1632
+ installImageLoadingAttributes(elementInterfacePrototypes.get("HTMLImageElement"), nodeOf, node=>hooks.touch(node), node=>{
1633
+ // Inline replaced images can live in text fragments rather than boxes.
1634
+ const rect=hooks.layoutBounds?.(node)??hooks.boundsOf(node);if(!rect)return null;
1635
+ const style=hooks.computedStyle?.(node);return style?.display==='none'?null:rect;
1636
+ });
1593
1637
  const { location, setHref } = locationBinding(document.URL, hooks.navigate);
1594
1638
  const { History, history, pushEntry } = historyBinding(location, setHref);
1595
1639
  const { Event, CustomEvent, ...uiEvents } = eventConstructors(realm);
@@ -1606,6 +1650,11 @@ export function createPageRealm({ window, document, mode, hooks }) {
1606
1650
  EventTarget: eventTargets.EventTarget, realm, runTask, DOMException,
1607
1651
  });
1608
1652
 
1653
+ const webAudio = createWebAudioScope({
1654
+ audio: hooks.audio, decodeAudio: hooks.decodeAudio, EventTarget: eventTargets.EventTarget, Event, DOMException,
1655
+ schedule: fn => hostSetTimeout(() => runTask(fn), 0), reportError,
1656
+ });
1657
+
1609
1658
  const platform = platformObjects({
1610
1659
  eventTargets, userAgent, userAgentDataMembers: chromeVersion ? userAgentDataMembers : null, mobile: mobileUserAgent, viewport: hooks.viewport,
1611
1660
  performance: userTiming.performance, storages: { local: hostStorage.get(origin), session: new Map() },
@@ -1636,6 +1685,7 @@ export function createPageRealm({ window, document, mode, hooks }) {
1636
1685
 
1637
1686
  /** Web APIs placed on the page's global object (bindings plus hardened host utilities). */
1638
1687
  const api = {
1688
+ ...webAudio.api,
1639
1689
  document: documentBinding,
1640
1690
  location,
1641
1691
  navigator: platform.navigator,
@@ -1666,6 +1716,7 @@ export function createPageRealm({ window, document, mode, hooks }) {
1666
1716
  return idleCallbacks.request(nextTimerId++, callback, timeout);
1667
1717
  },
1668
1718
  cancelIdleCallback: (handle) => idleCallbacks.cancel(Number(handle)),
1719
+ CanvasRenderingContext2D,
1669
1720
  queueMicrotask: (callback) => hostQueueMicrotask(() => runTask(() => callPage(callback, pageGlobal, []))),
1670
1721
  structuredClone: (value) => hostStructuredClone(value),
1671
1722
  atob: (value) => hostAtob(String(value)),
@@ -1783,6 +1834,7 @@ export function createPageRealm({ window, document, mode, hooks }) {
1783
1834
  },
1784
1835
  },
1785
1836
  ...INTERFACES,
1837
+ DOMTokenList,
1786
1838
  DOMStringMap,
1787
1839
  Node: NodeInterface,
1788
1840
  Text: TextInterface,
@@ -1808,7 +1860,7 @@ export function createPageRealm({ window, document, mode, hooks }) {
1808
1860
  const style = hooks.computedStyle(node, needsLayout);
1809
1861
  return {
1810
1862
  style, custom: hooks.customProperties?.(node, false) ?? null,
1811
- bounds: needsLayout && style && style.display !== "none" && node?.isConnected ? hooks.boundsOf(node) : null,
1863
+ bounds: needsLayout && style && style.display !== "none" && node?.isConnected ? (hooks.layoutBounds?.(node) ?? hooks.boundsOf(node)) : null,
1812
1864
  element: node,
1813
1865
  };
1814
1866
  });
@@ -1829,9 +1881,9 @@ export function createPageRealm({ window, document, mode, hooks }) {
1829
1881
  scrollTo: (x = undefined, y = undefined) => scrollArguments(hooks, x, y, false),
1830
1882
  scroll: (x = undefined, y = undefined) => scrollArguments(hooks, x, y, false),
1831
1883
  scrollBy: (x = undefined, y = undefined) => scrollArguments(hooks, x, y, true),
1832
- alert: () => {},
1833
- confirm: () => false,
1834
- prompt: () => null,
1884
+ alert: (message = "") => { hooks.dialog?.("alert", String(message), ""); },
1885
+ confirm: (message = "") => Boolean(hooks.dialog?.("confirm", String(message), "")),
1886
+ prompt: (message = "", value = "") => hooks.dialog?.("prompt", String(message), String(value)) ?? null,
1835
1887
  print: () => {},
1836
1888
  focus: () => {},
1837
1889
  blur: () => {},
@@ -1918,7 +1970,58 @@ export function createPageRealm({ window, document, mode, hooks }) {
1918
1970
  });
1919
1971
  Object.defineProperty(PagePromise, "length", { value: 1, configurable: true });
1920
1972
  pageGlobal.Promise = PagePromise;
1921
- pageGlobal.Error = errorInterface(pageGlobal.Error, hostErrorIsError);
1973
+ // Collection polyfills may cache inherited methods on individual instances.
1974
+ // Mutable page-local prototypes avoid SES's frozen-inheritance assignment
1975
+ // trap without changing shared intrinsics or their internal slot checks.
1976
+ for (const name of ['Map', 'Set', 'WeakMap', 'WeakSet']) {
1977
+ const Base = pageGlobal[name];
1978
+ const Local = compartment.evaluate(`(class ${name} extends globalThis.${name} {})`);
1979
+ for (const key of Reflect.ownKeys(Base.prototype)) {
1980
+ if (key === 'constructor') continue;
1981
+ const descriptor = {...Object.getOwnPropertyDescriptor(Base.prototype, key), configurable:true};
1982
+ if ('value' in descriptor) descriptor.writable = true;
1983
+ Object.defineProperty(Local.prototype, key, descriptor);
1984
+ }
1985
+ Object.defineProperty(Local,'length',{value:0,configurable:true});
1986
+ pageGlobal[name] = Local;
1987
+ }
1988
+
1989
+ // Polyfills extend Array's unscopables table even when methods already exist.
1990
+ // Keep this mutable metadata local to the page, not SES's shared prototype.
1991
+ const BaseArray = pageGlobal.Array;
1992
+ const PageArray = compartment.evaluate('((Base) => function Array(...args) { return Reflect.construct(Base, args, new.target || Array); })(Array)');
1993
+ const arrayPrototype=[];
1994
+ for(const key of Reflect.ownKeys(BaseArray.prototype)) {
1995
+ if(key==='constructor'||key==='length')continue;
1996
+ const original=Object.getOwnPropertyDescriptor(BaseArray.prototype,key);
1997
+ const value=key===Symbol.unscopables?{...BaseArray.prototype[key]}:Reflect.get(BaseArray.prototype,key);
1998
+ Object.defineProperty(arrayPrototype,key,{value,writable:true,configurable:true,enumerable:original.enumerable});
1999
+ }
2000
+ Object.defineProperty(arrayPrototype,'constructor',{value:PageArray,writable:true,configurable:true});
2001
+ Object.defineProperty(PageArray,'prototype',{value:arrayPrototype});
2002
+ for(const key of Reflect.ownKeys(BaseArray))if(!['name','length','prototype'].includes(key))
2003
+ Object.defineProperty(PageArray,key,{...Object.getOwnPropertyDescriptor(BaseArray,key),configurable:true});
2004
+ Object.defineProperty(PageArray,Symbol.hasInstance,{value:value=>Array.isArray(value),configurable:true});
2005
+ pageGlobal.Array=PageArray;
2006
+ const BaseRegExp = pageGlobal.RegExp;
2007
+ const PageRegExp = function RegExp(pattern, flags) {
2008
+ const object=pattern!==null&&(typeof pattern==='object'||typeof pattern==='function');
2009
+ const matches=object?pattern[Symbol.match]:undefined;
2010
+ const isRegExp=matches!==undefined?Boolean(matches):object&&Object.prototype.toString.call(pattern)==='[object RegExp]';
2011
+ if(!new.target&&flags===undefined&&isRegExp&&(pattern.constructor===BaseRegExp||pattern.constructor===PageRegExp))return pattern;
2012
+ return Reflect.construct(BaseRegExp,[pattern,flags],new.target||BaseRegExp);
2013
+ };
2014
+ Object.defineProperty(PageRegExp,'prototype',{value:BaseRegExp.prototype});
2015
+ for(const key of Reflect.ownKeys(BaseRegExp))if(!['name','length','prototype'].includes(key))
2016
+ Object.defineProperty(PageRegExp,key,Object.getOwnPropertyDescriptor(BaseRegExp,key));
2017
+ pageGlobal.RegExp=PageRegExp;
2018
+ const BaseSymbol=pageGlobal.Symbol;
2019
+ const PageSymbol={Symbol(description){return arguments.length?BaseSymbol(description):BaseSymbol();}}.Symbol;
2020
+ for(const key of Reflect.ownKeys(BaseSymbol))if(!['name','length'].includes(key))
2021
+ Object.defineProperty(PageSymbol,key,{...Object.getOwnPropertyDescriptor(BaseSymbol,key),configurable:key!=='prototype'});
2022
+ pageGlobal.Symbol=PageSymbol;
2023
+ for(const name of ['Error','EvalError','RangeError','ReferenceError','SyntaxError','TypeError','URIError'])
2024
+ pageGlobal[name] = errorInterface(pageGlobal[name], hostErrorIsError);
1922
2025
  // Namespace objects that libraries extend (reflect-metadata adds to
1923
2026
  // Reflect, polyfills to JSON and Math): each page gets its own mutable
1924
2027
  // copy with the same functions; the shared frozen intrinsics stay as they are.
@@ -1933,7 +2036,9 @@ export function createPageRealm({ window, document, mode, hooks }) {
1933
2036
  value: Object.create(Object.getPrototypeOf(shared), descriptors), writable: true, configurable: true,
1934
2037
  });
1935
2038
  }
1936
- const helper = constructorHelper(compartment);
2039
+ const member = installPagePrimitiveBuiltins(compartment, pageGlobal);
2040
+ Object.defineProperty(pageGlobal, MEMBER_HELPER, {value: member});
2041
+ const helper = constructorHelper(compartment, member);
1937
2042
  Object.defineProperty(pageGlobal, CONSTRUCTOR_HELPER, { value: helper });
1938
2043
  // Indirect eval runs code as a classic script does: in the global scope,
1939
2044
  // its var and function declarations becoming globals, and its own direct
@@ -1943,6 +2048,7 @@ export function createPageRealm({ window, document, mode, hooks }) {
1943
2048
  return code === undefined ? source : evaluate(code);
1944
2049
  } }.eval;
1945
2050
  Object.defineProperty(pageGlobal, "eval", { value: markNative(pageEval, "eval"), writable: true, configurable: true });
2051
+ Object.defineProperty(pageGlobal, MAKE_EVAL_ENV, { value: parent => evalEnvironment(pageGlobal,parent) });
1946
2052
  Object.defineProperty(pageGlobal, DIRECT_EVAL_HELPER, { value: directEvalHelper(compartment, pageGlobal, trustedTypes.scriptText) });
1947
2053
  // A sloppy-mode function called without a receiver must see the page
1948
2054
  // global, never the engine's: the rewrite routes every such `this` here.
@@ -1973,7 +2079,8 @@ export function createPageRealm({ window, document, mode, hooks }) {
1973
2079
  return evaluatePrepared(prepareClassicScript(source));
1974
2080
  };
1975
2081
  // Module code is already strict with its own scope; no global lifting.
1976
- evaluateModuleCode = (code) => compartment.evaluate(escapeSesHtmlComments(code, "module"), {
2082
+ evaluateModuleCode = (code) => compartment.evaluate(escapeSesHtmlComments(code, "script"), {
2083
+ __evadeHtmlCommentTest__: true,
1977
2084
  __evadeImportExpressionTest__: true,
1978
2085
  __rejectSomeDirectEvalExpressions__: false,
1979
2086
  });
@@ -2029,6 +2136,8 @@ export function createPageRealm({ window, document, mode, hooks }) {
2029
2136
  // Before any page script: the Web APIs print and name themselves as built-ins,
2030
2137
  // and the global has a browser's tags and enumerability.
2031
2138
  markNativeApi(pageGlobal);
2139
+ // The legacy Web Audio alias keeps the standard constructor's name.
2140
+ markNative(webAudio.api.AudioContext, "AudioContext");
2032
2141
  applyGlobalShape(pageGlobal, [Object.prototype, Function.prototype, Array.prototype, Error.prototype,
2033
2142
  Promise.prototype, pageGlobal.Promise?.prototype, RegExp.prototype, Date.prototype, Map.prototype, Set.prototype]);
2034
2143
 
@@ -2246,30 +2355,31 @@ export function createPageRealm({ window, document, mode, hooks }) {
2246
2355
  }
2247
2356
  }
2248
2357
 
2249
- /** Compiles on* attributes (e.g. onclick="...") present when the document loads. */
2250
- function compileInlineHandlers() {
2251
- for (const element of document.querySelectorAll("*")) {
2252
- for (const type of INLINE_HANDLER_EVENTS) {
2253
- const code = element.getAttribute(`on${type}`);
2254
- if (code === null) continue;
2255
- let handler;
2256
- try {
2257
- handler = evaluate(`(function (event) {\n${code}\n})`);
2258
- } catch (error) {
2259
- reportError(error);
2260
- continue;
2261
- }
2262
- const binding = type === "load" && element === document.body ? pageGlobal : realm.wrap(element);
2263
- if (binding !== pageGlobal) {
2264
- binding[`on${type}`] = handler;
2265
- continue;
2266
- }
2267
- window.addEventListener(type, (event) => {
2268
- const result = callPage(handler, binding, [realm.wrapEvent(event)]);
2269
- if (result === false) event.preventDefault();
2270
- });
2271
- }
2358
+ // Compile lazily so innerHTML, cloned nodes and changed attributes work too.
2359
+ // Remember source rather than overwriting a later element.onclick assignment.
2360
+ function prepareInlineHandler(element, type) {
2361
+ if (!element?.getAttribute || !INLINE_HANDLER_EVENTS.includes(type)) return;
2362
+ const code = element.getAttribute(`on${type}`);
2363
+ let sources = inlineHandlerSources.get(element);
2364
+ if (sources?.has(type) && sources.get(type) === code) return;
2365
+ if (!sources) inlineHandlerSources.set(element, sources = new Map());
2366
+ sources.set(type, code);
2367
+ if (code === null && !sources.has(`installed:${type}`)) return;
2368
+ sources.set(`installed:${type}`, true);
2369
+ let handler = null;
2370
+ if (code !== null) {
2371
+ try { handler = evaluate(`(function (event) {\n${code}\n})`); }
2372
+ catch (error) { reportError(error); }
2272
2373
  }
2374
+ const binding = type === 'load' && element === document.body ? pageGlobal : realm.wrap(element);
2375
+ binding[`on${type}`] = handler;
2376
+ }
2377
+ function prepareInlineEvent(target, type) {
2378
+ for (let node = target; node; node = node.parentNode) prepareInlineHandler(node, type);
2379
+ }
2380
+ function compileInlineHandlers() {
2381
+ for (const element of document.querySelectorAll('*'))
2382
+ for (const type of INLINE_HANDLER_EVENTS) prepareInlineHandler(element, type);
2273
2383
  }
2274
2384
 
2275
2385
  return {
@@ -2410,6 +2520,7 @@ export function createPageRealm({ window, document, mode, hooks }) {
2410
2520
  dispatch(target, event) {
2411
2521
  let prevented = false;
2412
2522
  runTask(() => {
2523
+ prepareInlineEvent(target, event.type);
2413
2524
  realm.markTrusted(event);
2414
2525
  target.dispatchEvent(event);
2415
2526
  prevented = event.defaultPrevented;
@@ -2475,6 +2586,7 @@ export function createPageRealm({ window, document, mode, hooks }) {
2475
2586
  if (blobUrlFallback === mediaScope) blobUrlFallback = null;
2476
2587
  messaging.dispose();
2477
2588
  broadcasts.dispose();
2589
+ webAudio.dispose();
2478
2590
  workers?.dispose();
2479
2591
  // Leaving the document closes its WebSockets (1001 "going away").
2480
2592
  for (const close of openSockets) close();
@@ -2572,7 +2684,7 @@ function scrollArguments(hooks, x, y, relative) {
2572
2684
  * these stand-ins compile the same function text in the page's compartment
2573
2685
  * and share the real prototypes, so instanceof and prototype checks hold.
2574
2686
  */
2575
- function constructorHelper(compartment) {
2687
+ function constructorHelper(compartment, member) {
2576
2688
  const kinds = [
2577
2689
  [function () {}, "function"],
2578
2690
  [async function () {}, "async function"],
@@ -2597,14 +2709,27 @@ function constructorHelper(compartment) {
2597
2709
  __rejectSomeDirectEvalExpressions__: false,
2598
2710
  });
2599
2711
  };
2600
- Object.defineProperty(Constructor, "prototype", { value: Object.getPrototypeOf(sample) });
2712
+ const sharedPrototype = Object.getPrototypeOf(sample);
2713
+ const prototype = keyword === 'function' ? function () {} : sharedPrototype;
2714
+ if (prototype !== sharedPrototype) {
2715
+ for (const key of Reflect.ownKeys(sharedPrototype)) {
2716
+ if (key === 'constructor' || key === 'arguments' || key === 'caller') continue;
2717
+ const original=Object.getOwnPropertyDescriptor(sharedPrototype,key);
2718
+ const descriptor={value:Reflect.get(sharedPrototype,key),enumerable:original.enumerable,writable:true,configurable:true};
2719
+ Object.defineProperty(prototype,key,descriptor);
2720
+ }
2721
+ Object.defineProperty(prototype,'constructor',{value:Constructor,writable:true,configurable:true});
2722
+ Object.defineProperty(Constructor,Symbol.hasInstance,{value(value){return typeof value==='function';}});
2723
+ }
2724
+ Object.defineProperty(Constructor, "prototype", { value: prototype });
2601
2725
  Object.defineProperty(Constructor, "name", { value: inert.name });
2602
2726
  Object.defineProperty(Constructor, "length", { value: 1 });
2603
2727
  replacements.set(inert, Constructor);
2604
2728
  }
2605
2729
  // call: `value.constructor(...)`, where an ordinary constructor keeps `value` as `this`.
2606
2730
  const helper = (value, call = false) => {
2607
- const constructor = value.constructor;
2731
+ const primitiveName={number:"Number",string:"String",boolean:"Boolean"}[typeof value];
2732
+ const constructor = primitiveName ? member(value,"constructor") : value.constructor;
2608
2733
  const replacement = replacements.get(constructor);
2609
2734
  if (replacement) return replacement;
2610
2735
  return call && typeof constructor === "function" ? (...args) => Reflect.apply(constructor, value, args) : constructor;
@@ -2633,9 +2758,19 @@ function directEvalHelper(compartment, pageGlobal, scriptText) {
2633
2758
  const source = typeof args[0] === "string" ? args[0] : scriptText(args[0]);
2634
2759
  // Anything but a string or a TrustedScript is returned unchanged.
2635
2760
  if (source === undefined) return args[0];
2761
+ const environment = scope[DIRECT_EVAL_ENV];
2762
+ const lowered = environment ? evalDeclarations(source, scope[DIRECT_EVAL_STRICT] === true) : {source,names:[]};
2763
+ if (environment) {
2764
+ for (const name of lowered.names) if (!Object.hasOwn(scope,name)) declareEvalBinding(environment,name);
2765
+ for (const name of new Set([...Object.keys(environment),...lowered.names])) {
2766
+ if (!Object.hasOwn(scope,name)) Object.defineProperty(scope,name,{
2767
+ get:()=>environment[name],set:value=>{environment[name]=value;},enumerable:true,configurable:true,
2768
+ });
2769
+ }
2770
+ }
2636
2771
  const names = Object.keys(scope);
2637
- const prepared = prepareDirectEvalSource(source, names, scope[DIRECT_EVAL_STRICT] === true);
2638
- const code = escapeSesHtmlComments(rewriteConstructorReads(prepared.code).code);
2772
+ const prepared = prepareDirectEvalSource(lowered.source, names, scope[DIRECT_EVAL_STRICT] === true);
2773
+ const code = escapeSesHtmlComments(rewriteMemberReads(rewriteConstructorReads(prepared.code).code).code);
2639
2774
  // The prepared code reaches the caller's variables only through the scope
2640
2775
  // object, so every direct eval shares the compartment's sloppy evaluator.
2641
2776
  const previous = currentScope;
@@ -3015,6 +3150,7 @@ function computedStyleSnapshot(style, { custom = null, bounds = null, element =
3015
3150
  verticalAlign: typeof style.verticalAlign === "number" ? px(style.verticalAlign) : String(style.verticalAlign),
3016
3151
  textOverflow: style.textOverflow,
3017
3152
  boxSizing: style.boxSizing,
3153
+ aspectRatio: style.aspectRatio ? `${style.aspectRatio.auto ? "auto " : ""}${style.aspectRatio.numerator} / ${style.aspectRatio.denominator}` : "auto",
3018
3154
  // A rendered box reads its used size; one that is not reads as computed.
3019
3155
  width: bounds ? px(Math.max(0, bounds.width - (borderBox ? 0 : horizontal))) : length(style.width),
3020
3156
  height: bounds ? px(Math.max(0, bounds.height - (borderBox ? 0 : vertical))) : length(style.height),
@@ -3039,6 +3175,7 @@ function computedStyleSnapshot(style, { custom = null, bounds = null, element =
3039
3175
  rowGap: style.rowGapNormal ? "normal" : length(style.rowGap),
3040
3176
  columnGap: style.columnGapNormal ? "normal" : length(style.columnGap),
3041
3177
  transform: !transform ? "none"
3178
+ : transform.affine ? `matrix(${Object.values(resolveAffine(transform.affine,bounds?.width??0,bounds?.height??0)).join(", ")})`
3042
3179
  : `matrix(${transform.scaleX ?? 1}, 0, 0, ${transform.scaleY ?? 1}, ${translate(transform.x, bounds?.width ?? 0)}, ${translate(transform.y, bounds?.height ?? 0)})`,
3043
3180
  visibility: style.visibility ?? "visible",
3044
3181
  opacity: String(style.opacity ?? 1),