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
@@ -0,0 +1,66 @@
1
+ import {mkdtempSync,writeFileSync,readdirSync,statSync,unlinkSync,rmSync,realpathSync,openSync,writevSync,closeSync} from 'node:fs';
2
+ import {join} from 'node:path';
3
+ import {tmpdir} from 'node:os';
4
+
5
+ // The producer owns the directory; Kitty t=t consumers unlink individual frames.
6
+ // A worker borrows a directory owned by its parent, so terminating the worker
7
+ // cannot skip cleanup. No pixel data is encoded into the CDP response.
8
+ export class RawFrames {
9
+ constructor({directory,ownsDirectory=true}={}) {
10
+ this.externalDirectory=directory;this.ownsDirectory=ownsDirectory;
11
+ this.sequence=0;this.closed=false;
12
+ this.onExit=()=>this.close();
13
+ }
14
+ get directory() {
15
+ if(this.closed)throw Error('Raw frame store is closed');
16
+ if(!this.path){
17
+ this.path=this.externalDirectory ? this.externalDirectory() : realpathSync(mkdtempSync(join(tmpdir(),'tty-graphics-protocol-land-')));
18
+ if(!this.path)throw Error('Missing raw frame directory');
19
+ if(this.ownsDirectory)process.once('exit',this.onExit);
20
+ }
21
+ return this.path;
22
+ }
23
+ write(bytes,width,height,format,patch) {
24
+ const directory=this.directory;
25
+ let count=0,total=0;
26
+ for(const name of readdirSync(directory)){
27
+ const path=join(directory,name);
28
+ try{
29
+ const stat=statSync(path);
30
+ if(Date.now()-stat.mtimeMs>60000){unlinkSync(path);continue;}
31
+ count++;total+=stat.size;
32
+ }catch(error){if(error.code!=='ENOENT')throw error;}
33
+ }
34
+ if(count>=16||total+bytes.byteLength>128*1024*1024)throw Error('Raw frame consumer stalled');
35
+ const path=join(directory,`${++this.sequence}.${format}`);
36
+ if(!patch)writeFileSync(path,bytes,{flag:'wx',mode:0o600});
37
+ else {
38
+ // Scatter the immutable background and small overlay directly into the
39
+ // file, avoiding both a desktop-sized copy and one write per cursor row.
40
+ const chunks=[];let offset=0;
41
+ for(let y=0;y<patch.height;y++){
42
+ const start=((patch.y+y)*width+patch.x)*4;
43
+ if(start>offset)chunks.push(bytes.subarray(offset,start));
44
+ chunks.push(patch.bytes.subarray(y*patch.width*4,(y+1)*patch.width*4));
45
+ offset=start+patch.width*4;
46
+ }
47
+ if(offset<bytes.length)chunks.push(bytes.subarray(offset));
48
+ const fd=openSync(path,'wx',0o600);
49
+ try{
50
+ let index=0;
51
+ while(index<chunks.length){
52
+ let written=writevSync(fd,chunks.slice(index,index+512));
53
+ if(written<=0)throw Error('Short raw frame write');
54
+ while(index<chunks.length&&written>=chunks[index].length){written-=chunks[index++].length;}
55
+ if(written)chunks[index]=chunks[index].subarray(written);
56
+ }
57
+ }catch(error){try{unlinkSync(path);}catch{}throw error;}finally{closeSync(fd);}
58
+ }
59
+ return JSON.stringify({version:1,transport:'file',path,width,height,stride:width*4,format,bytes:bytes.byteLength});
60
+ }
61
+ close(){
62
+ if(this.closed)return;this.closed=true;
63
+ process.removeListener('exit',this.onExit);
64
+ if(this.ownsDirectory&&this.path)rmSync(this.path,{recursive:true,force:true});
65
+ }
66
+ }
@@ -0,0 +1,7 @@
1
+ // Structural types for the JS compositor/worker boundary; no runtime imports.
2
+ export interface Pixels { bytes: Uint8Array; width: number; height: number; stride: number; format: number }
3
+ export interface ViewportSurface {
4
+ pixels: Pixels | null; scale: number; transform: number;
5
+ viewportSource?: number[] | null; viewportDestination?: number[] | null;
6
+ }
7
+ export type RawFormat = 'rgba' | 'bgra';
@@ -0,0 +1,134 @@
1
+ import {createDirectComposer, downsample, downsampleLinear, type DirectComposer, type DirectScene} from './direct-compositor.ts';
2
+ import {encodePng} from './png-fast.js';
3
+ import {RawFrames} from './raw-frames.js';
4
+ import type {RawFormat} from './render-types.ts';
5
+ import { rgbaBitmap } from './frame.js';
6
+
7
+ /** What the renderer reads: the compositor, or the renderer worker's stand-in for it. */
8
+ export interface RenderScene extends DirectScene {
9
+ width: number; height: number; deviceScaleFactor?: number; fastPng?: boolean;
10
+ measure?: (key: string, ms: number) => void;
11
+ }
12
+ export interface ScreenshotOptions {
13
+ format?: string; quality?: number; maxWidth?: number; maxHeight?: number;
14
+ /** A CSS-pixel crop of the output, rendered at device scale times `scale`. */
15
+ clip?: { x: number; y: number; width: number; height: number; scale: number };
16
+ viewport?: { width?: number; height?: number; deviceScaleFactor?: number };
17
+ }
18
+ export interface Renderer { close(): void; screenshot(options?: ScreenshotOptions): Promise<string> }
19
+
20
+ // Options arrive from CDP or a worker message. Each field is read once here; a
21
+ // value of another type becomes NaN, which the checks below reject as before.
22
+ const optional = (value: unknown): number | undefined => typeof value === 'number' ? value : value === undefined || value === null ? undefined : NaN;
23
+ const required = (value: unknown): number => typeof value === 'number' ? value : NaN;
24
+ const validSize = (v: number): boolean => Number.isInteger(v) && v > 0 && v <= 8192;
25
+ const validLimit = (v: number | undefined): boolean => v === undefined || (Number.isFinite(v) && v > 0);
26
+
27
+ /** One capture: the requested output, checked, and how to compose it. */
28
+ class Capture {
29
+ readonly raw: boolean; readonly label: string;
30
+ constructor(
31
+ readonly format: string, readonly quality: number,
32
+ readonly width: number, readonly height: number, readonly deviceScale: number,
33
+ readonly clipped: boolean, readonly clipX: number, readonly clipY: number,
34
+ readonly factor: number, readonly outWidth: number, readonly outHeight: number,
35
+ ) {
36
+ this.raw = format === 'rgba' || format === 'bgra';
37
+ this.label = `${format}:${outWidth}x${outHeight}`;
38
+ }
39
+ }
40
+
41
+ function readCapture(compositor: RenderScene, options: ScreenshotOptions): Capture {
42
+ const { viewport, clip } = options, format = typeof options.format === 'string' ? options.format : 'png';
43
+ const quality = optional(options.quality) ?? 80, maxWidth = options.maxWidth === undefined ? undefined : required(options.maxWidth), maxHeight = options.maxHeight === undefined ? undefined : required(options.maxHeight);
44
+ const width = optional(viewport?.width) ?? compositor.width, height = optional(viewport?.height) ?? compositor.height;
45
+ if (!validSize(width) || !validSize(height) || width * height > 16777216) throw Error('Invalid capture size');
46
+ if (!validLimit(maxWidth) || !validLimit(maxHeight)) throw Error('Invalid capture limit');
47
+ const deviceScale = optional(viewport?.deviceScaleFactor) ?? compositor.deviceScaleFactor ?? 1;
48
+ if (!Number.isFinite(deviceScale) || deviceScale <= 0 || deviceScale > 8) throw Error('Invalid device scale');
49
+ let clipX = 0, clipY = 0, captureWidth = width, captureHeight = height, clipScale = 1;
50
+ if (clip) {
51
+ clipX = required(clip.x); clipY = required(clip.y); captureWidth = required(clip.width); captureHeight = required(clip.height); clipScale = required(clip.scale);
52
+ if (![clipX, clipY, captureWidth, captureHeight, clipScale].every(Number.isFinite) || captureWidth <= 0 || captureHeight <= 0 || clipScale <= 0 || clipScale > 8) throw Error('Invalid screenshot clip');
53
+ }
54
+ // A supplied clip takes precedence over fallback screencast max limits.
55
+ // Preserve native pixel density: crop, never resize to fit those limits.
56
+ const factor = clip ? deviceScale * clipScale : Math.min(deviceScale, maxWidth ? maxWidth / captureWidth : Infinity, maxHeight ? maxHeight / captureHeight : Infinity);
57
+ const outWidth = Math.max(1, clip ? Math.round(captureWidth * factor) : Math.floor(width * factor)), outHeight = Math.max(1, clip ? Math.round(captureHeight * factor) : Math.floor(height * factor));
58
+ if (outWidth > 8192 || outHeight > 8192 || outWidth * outHeight > 16777216) throw Error('Invalid output size');
59
+ return new Capture(format, quality, width, height, deviceScale, !!clip, clipX, clipY, factor, outWidth, outHeight);
60
+ }
61
+
62
+ class DirectRenderer implements Renderer {
63
+ // SHM snapshots are immutable: each applied buffer commit replaces pixels.
64
+ readonly direct: DirectComposer;
65
+ closed = false;
66
+ constructor(readonly compositor: RenderScene, readonly rawFrames: RawFrames) { this.direct = createDirectComposer(compositor); }
67
+ close(): void {
68
+ if (this.closed) return; this.closed = true;
69
+ this.direct.close();
70
+ this.rawFrames.close();
71
+ }
72
+ /** The output pixels, opaque BGRA words. */
73
+ compose(capture: Capture): Uint32Array {
74
+ const { deviceScale, factor, outWidth, outHeight } = capture;
75
+ // A preview (factor below the device scale) composes the full-size frame
76
+ // and samples it down, as Skia draws a preview from the full image; the
77
+ // full frame stays cached for the next full capture. Below device scale 1
78
+ // downscaled surfaces and previews are filtered.
79
+ const smooth = deviceScale < 1;
80
+ const fullWidth = Math.max(1, Math.floor(capture.width * deviceScale)), fullHeight = Math.max(1, Math.floor(capture.height * deviceScale));
81
+ if (capture.clipped || factor >= deviceScale || fullWidth > 8192 || fullHeight > 8192 || fullWidth * fullHeight > 16777216)
82
+ return this.direct.compose(outWidth, outHeight, factor, capture.clipX, capture.clipY, smooth);
83
+ const full = this.direct.compose(fullWidth, fullHeight, deviceScale, 0, 0, smooth);
84
+ return smooth
85
+ ? downsampleLinear(full, fullWidth, fullHeight, outWidth, outHeight, factor / deviceScale)
86
+ : downsample(full, fullWidth, fullHeight, outWidth, outHeight, factor / deviceScale);
87
+ }
88
+ async screenshot(options: ScreenshotOptions = {}): Promise<string> {
89
+ if (this.closed) throw Error('Renderer is closed');
90
+ const capture = readCapture(this.compositor, options);
91
+ const { format, label, outWidth, outHeight } = capture;
92
+ const measure = this.compositor.measure, started = measure ? performance.now() : 0;
93
+ try {
94
+ const pixels = this.compose(capture);
95
+ measure?.(`${label}:direct-compose`, performance.now() - started);
96
+ const encodeStarted = measure ? performance.now() : 0;
97
+ // Private raw file formats, explicitly negotiated by a cooperating client.
98
+ // Standard screenshot formats and their responses remain unchanged.
99
+ if (capture.raw) {
100
+ const count = pixels.length | 0, rgba = format === 'bgra' ? pixels : new Uint32Array(count);
101
+ if (format === 'rgba')
102
+ for (let i = 0; i < count; i++) {
103
+ const p = pixels[i]! | 0;
104
+ rgba[i] = (p & 0xff00ff00) | ((p & 255) << 16) | ((p >>> 16) & 255);
105
+ }
106
+ measure?.(`${label}:raw-convert`, performance.now() - encodeStarted);
107
+ const writeStarted = measure ? performance.now() : 0;
108
+ const result = this.rawFrames.write(new Uint8Array(rgba.buffer, rgba.byteOffset, rgba.byteLength), outWidth, outHeight, format as RawFormat);
109
+ measure?.(`${label}:raw-file-write`, performance.now() - writeStarted);
110
+ return result;
111
+ }
112
+ let bytes: Uint8Array;
113
+ const kind = format === 'jpeg' || format === 'webp' ? format : 'png';
114
+ // Without Bun.Image, PNG is still available from the built-in encoder.
115
+ if (kind === 'png' && (this.compositor.fastPng || typeof Bun.Image !== 'function')) bytes = encodePng(pixels, outWidth, outHeight, true, true);
116
+ else {
117
+ // One BMP wrapper copy; BGRA masks avoid a channel-conversion pass.
118
+ const bitmap = rgbaBitmap(new Uint8Array(pixels.buffer, pixels.byteOffset, pixels.byteLength), outWidth, outHeight);
119
+ const header = new DataView(bitmap.buffer);
120
+ header.setUint32(54, 0x00ff0000, true); header.setUint32(62, 0x000000ff, true);
121
+ const image = new Bun.Image(bitmap), quality = capture.quality;
122
+ const level = Math.min(100, Math.max(0, Number.isFinite(quality) ? quality : 80));
123
+ bytes = await (kind === 'png' ? image.png().bytes() : image[kind]({quality: level}).bytes());
124
+ }
125
+ const result = Buffer.from(bytes).toString('base64');
126
+ measure?.(`${label}:direct-encode`, performance.now() - encodeStarted);
127
+ return result;
128
+ } finally { measure?.(`${label}:total`,performance.now()-started); }
129
+ }
130
+ }
131
+
132
+ export async function createRenderer(compositor: RenderScene, { rawFrames = new RawFrames() }: { rawFrames?: RawFrames } = {}): Promise<Renderer> {
133
+ return new DirectRenderer(compositor, rawFrames);
134
+ }
@@ -0,0 +1,37 @@
1
+ // A fresh, compositor-owned copy. After filling, snapshots are immutable: workers
2
+ // may share them, but never receive writable client SHM or DMA-BUF mappings.
3
+ export function allocateSnapshot(length) {
4
+ return typeof SharedArrayBuffer === "function"
5
+ ? Buffer.from(new SharedArrayBuffer(length))
6
+ : Buffer.alloc(length);
7
+ }
8
+
9
+ // Snapshot objects are immutable after publication. Share the opacity result
10
+ // between full-frame validation and composition rather than scanning twice.
11
+ const opacity = new WeakMap();
12
+ const littleEndian = new Uint8Array(new Uint32Array([1]).buffer)[0] === 1;
13
+ export function snapshotOpaque(buffer, bytes = buffer.bytes) {
14
+ const cached = opacity.get(buffer);
15
+ if (cached !== undefined) return cached;
16
+ const {width, height, stride} = buffer;
17
+ let opaque = true;
18
+ if (littleEndian && bytes.byteOffset % 4 === 0 && stride % 4 === 0) {
19
+ const words = new Uint32Array(bytes.buffer, bytes.byteOffset, stride * height / 4);
20
+ const rowWords = stride / 4;
21
+ for (let row = 0; row < height && opaque; row++) {
22
+ const end = row * rowWords + width;
23
+ for (let i = row * rowWords; i < end; i++) {
24
+ if ((words[i] >>> 24) !== 255) { opaque = false; break; }
25
+ }
26
+ }
27
+ } else {
28
+ for (let row = 0; row < height && opaque; row++) {
29
+ const end = row * stride + width * 4;
30
+ for (let i = row * stride + 3; i < end; i += 4) {
31
+ if (bytes[i] !== 255) { opaque = false; break; }
32
+ }
33
+ }
34
+ }
35
+ opacity.set(buffer, opaque);
36
+ return opaque;
37
+ }
@@ -0,0 +1,18 @@
1
+ // Viewporter coordinates follow buffer transform, then integer buffer scale.
2
+ export function surfaceViewport(surface) {
3
+ const b=surface.pixels;
4
+ if(!b)return null;
5
+ const rotated=surface.transform&1;
6
+ const width=(rotated?b.height:b.width)/surface.scale;
7
+ const height=(rotated?b.width:b.height)/surface.scale;
8
+ const source=surface.viewportSource??[0,0,width,height];
9
+ const destination=surface.viewportDestination??source.slice(2);
10
+ return {source,width:destination[0],height:destination[1]};
11
+ }
12
+ export function validateViewport(surface) {
13
+ const source=surface.viewportSource,destination=surface.viewportDestination;
14
+ if(source&&!destination&&(!Number.isInteger(source[2])||!Number.isInteger(source[3])))throw Error('Viewport source size must be integral without destination');
15
+ if(!surface.pixels||!source)return;
16
+ const b=surface.pixels,rotated=surface.transform&1;
17
+ if(source[0]+source[2]>(rotated?b.height:b.width)/surface.scale||source[1]+source[3]>(rotated?b.width:b.height)/surface.scale)throw Error('Viewport source outside buffer');
18
+ }
@@ -8109,7 +8109,7 @@ const methods = {
8109
8109
  */
8110
8110
  '%SetPrototype%': '*',
8111
8111
  // Page scripts may assign an own toString method to a RegExp instance.
8112
- '%RegExpPrototype%': { toString: true },
8112
+ '%RegExpPrototype%': '*',
8113
8113
  };$h͏_once.severeEnablements(severeEnablements);
8114
8114
  })()
8115
8115
  ,