buninu-browser 0.6.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.
- package/AGENTS.md +5 -0
- package/README.md +94 -4
- package/buninu-browser.js +1 -1
- package/frontends/wayland/README.md +35 -2
- package/frontends/wayland/client.js +8 -6
- package/frontends/wayland/clipboard.js +1 -1
- package/frontends/wayland/index.js +1 -1
- package/frontends/wayland/input.js +1 -1
- package/frontends/wayland/resource-io.js +6 -0
- package/frontends/wayland/transport-posix.js +120 -0
- package/frontends/wayland/transport-win32.js +9 -0
- package/frontends/wayland/transport.js +3 -120
- package/frontends/wayland/win32-resources.js +80 -0
- package/frontends/wayland/win32-wire.js +62 -0
- package/index.js +10 -6
- package/lib/audio/biquad.js +25 -0
- package/lib/audio/piano.js +6 -3
- package/lib/audio/playback-buffer.js +5 -5
- package/lib/audio/pulse-client.js +24 -5
- package/lib/audio/web-audio.js +570 -0
- package/lib/browser-dialog.js +116 -0
- package/lib/cdp-server.js +128 -72
- package/lib/file-picker.js +48 -0
- package/lib/headless-shell.js +34 -12
- package/lib/layout/text-layout.js +112 -19
- package/lib/media/mp4-frame-times.js +93 -0
- package/lib/paint/direct-canvas.ts +39 -0
- package/lib/paint/display-list.js +37 -12
- package/lib/paint/raster-readback.js +16 -7
- package/lib/renderer/animation-frames.js +10 -5
- package/lib/renderer/bindings.js +69 -16
- package/lib/renderer/canvas-2d.js +265 -15
- package/lib/renderer/canvas-round-rect.js +36 -0
- package/lib/renderer/css-animations.js +173 -0
- package/lib/renderer/dialog-client.js +30 -0
- package/lib/renderer/dialog-worker.js +35 -0
- package/lib/renderer/eval-environment.js +28 -0
- package/lib/renderer/file-input.js +49 -0
- package/lib/renderer/host.js +33 -4
- package/lib/renderer/html-drag.js +77 -0
- package/lib/renderer/image-loading.js +45 -2
- package/lib/renderer/main.js +4 -0
- package/lib/renderer/module-transform.js +5 -3
- package/lib/renderer/page-builtins.js +59 -0
- package/lib/renderer/page-realm.js +177 -40
- package/lib/renderer/page-renderer.js +397 -49
- package/lib/renderer/piano-input.js +3 -1
- package/lib/renderer/raster-surface.js +18 -0
- package/lib/renderer/script-rewrite.js +165 -9
- package/lib/renderer/video-presentation.js +5 -4
- package/lib/renderer/webgl/clip.js +24 -0
- package/lib/renderer/webgl/context.js +158 -0
- package/lib/renderer/webgl/cpu-fragment.js +211 -0
- package/lib/renderer/webgl/draw-cache.js +22 -0
- package/lib/renderer/webgl/pipeline.js +70 -0
- package/lib/renderer/webgl/shaders.js +58 -0
- package/lib/renderer/webgl/textures.js +43 -0
- package/lib/renderer/window-named-properties.js +46 -0
- package/lib/screenshot-cli.js +4 -7
- package/lib/style/animation-values.js +29 -0
- package/lib/style/computed-style.js +109 -13
- package/lib/style/transform.js +44 -0
- package/lib/window-size.js +10 -0
- package/package.json +1 -1
- package/patches/happy-dom/0007-scope-content-models-to-html-namespace.patch +29 -0
- package/patches/happy-dom/README.md +16 -0
- package/test/animation-frames.test.js +15 -0
- package/test/canvas-2d.test.js +252 -4
- package/test/canvas-frame-pacing.test.js +7 -0
- package/test/cdp-viewers.test.js +79 -0
- package/test/classic-games-patches/0001-fix-game-initialization.patch +177 -0
- package/test/classic-games-patches/README.md +156 -0
- package/test/classic-games-scenarios.js +133 -0
- package/test/clipboard.test.js +16 -0
- package/test/computed-style.test.js +18 -0
- package/test/css-animation-lifecycle.test.js +28 -0
- package/test/css-animations.test.js +83 -0
- package/test/direct-canvas.test.js +39 -0
- package/test/dynamic-inline-handlers.test.js +25 -0
- package/test/eval-declarations.test.js +22 -0
- package/test/file-picker.test.js +26 -0
- package/test/fixtures/canvas-damage.js +23 -0
- package/test/fixtures/canvas-frame-pacing.js +35 -0
- package/test/fixtures/direct-canvas.js +96 -0
- package/test/fixtures/javascript-dialogs.html +8 -0
- package/test/fixtures/media-audio-lifetime.js +65 -0
- package/test/fixtures/renderer-memory.js +7 -5
- package/test/fixtures/video-black-replay.js +8 -6
- package/test/fixtures/video-slow-end.js +39 -0
- package/test/fixtures/web-audio-renderer.js +45 -0
- package/test/flex.test.js +26 -0
- package/test/grid.test.js +13 -0
- package/test/headless-shell.test.js +9 -0
- package/test/headless-window-size.test.js +28 -0
- package/test/html-rcdata.test.js +35 -0
- package/test/image-decode.test.js +18 -0
- package/test/index.test.js +1 -0
- package/test/javascript-dialog.test.js +83 -0
- package/test/manual-breakout-paddle.js +42 -0
- package/test/manual-breakout-static.js +35 -0
- package/test/manual-classic-games.js +41 -0
- package/test/manual-clumsy-bird-three-pipes.js +81 -0
- package/test/manual-clumsy-bird.js +52 -0
- package/test/manual-hextris.js +30 -0
- package/test/manual-numbered-music.js +24 -0
- package/test/manual-t-rex-runner.js +49 -0
- package/test/manual-threejs-scene.js +64 -0
- package/test/manual-webgl-benchmark.js +27 -0
- package/test/manual-webgl-cpu.html +26 -0
- package/test/media-audio-lifetime.test.js +8 -0
- package/test/mp4-frame-times.test.js +33 -0
- package/test/page-scripts.test.js +96 -1
- package/test/piano-input.test.js +15 -2
- package/test/piano.test.js +11 -2
- package/test/playback-buffer.test.js +11 -0
- package/test/pointer-drag.test.js +33 -0
- package/test/positioning.test.js +82 -0
- package/test/pulse-client.test.js +51 -2
- package/test/raster-readback.test.js +14 -0
- package/test/script-rewrite.test.js +61 -3
- package/test/video-slow-end.test.js +6 -0
- package/test/viewport-keyboard-overflow.test.js +25 -0
- package/test/wayland-win32-resources.test.js +47 -0
- package/test/wayland-win32-wire.test.js +59 -0
- package/test/web-audio-renderer.test.js +8 -0
- package/test/web-audio.test.js +264 -0
- package/test/webgl-clip.test.js +9 -0
- package/test/webgl-cpu-fragment.test.js +43 -0
- package/test/window-named-properties.test.js +41 -0
- package/vendor/happy-dom/packages/happy-dom/src/config/HTMLElementConfig.ts +2 -2
- package/vendor/happy-dom/packages/happy-dom/src/config/HTMLElementConfigContentModelEnum.ts +1 -0
- package/vendor/happy-dom/packages/happy-dom/src/html-parser/HTMLParser.ts +23 -3
- package/vendor/happy-dom/packages/happy-dom/src/query-selector/SelectorParser.ts +1 -1
- package/vendor/land-renderer/LICENSE +21 -0
- package/vendor/land-renderer/README.md +14 -0
- package/vendor/land-renderer/direct-compositor.ts +443 -0
- package/vendor/land-renderer/frame.js +88 -0
- package/vendor/land-renderer/png-fast.js +85 -0
- package/vendor/land-renderer/raw-frames.js +66 -0
- package/vendor/land-renderer/render-types.ts +7 -0
- package/vendor/land-renderer/renderer.ts +134 -0
- package/vendor/land-renderer/snapshot.js +37 -0
- package/vendor/land-renderer/viewport.js +18 -0
- package/vendor/ses/dist/ses.mjs +1 -1
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import {rotationMatrix3D,multiplyRotation3D} from '../style/transform.js';
|
|
2
|
+
import {resolveAffine,multiplyTransform as composeTransforms} from '../style/transform.js';
|
|
1
3
|
/**
|
|
2
4
|
* Converts layout output into an immutable, serializable display list.
|
|
3
5
|
*
|
|
@@ -94,8 +96,13 @@ export function buildDisplayList(layout, tree = null, resources = null, backgrou
|
|
|
94
96
|
for (const box of boxesByLayer.get(layer.order) ?? []) paintBox(box);
|
|
95
97
|
for (const fragment of fragmentsByLayer.get(layer.order) ?? []) paintFragment(fragment);
|
|
96
98
|
};
|
|
99
|
+
// Absolute layout is deferred until its containing block's dimensions are
|
|
100
|
+
// known. Its allocation order is therefore not CSS tree/paint order.
|
|
101
|
+
const treeOrder = new Map();
|
|
102
|
+
const indexTree = node => { treeOrder.set(node.id, treeOrder.size); for (const child of node.children ?? []) indexTree(child); };
|
|
103
|
+
if (tree?.root) indexTree(tree.root);
|
|
97
104
|
const paintContext = (context) => {
|
|
98
|
-
const members = (children.get(context.order) ?? []).sort((left, right) => left.z - right.z || left.order - right.order);
|
|
105
|
+
const members = (children.get(context.order) ?? []).sort((left, right) => left.z - right.z || (treeOrder.get(left.nodeId) ?? left.order) - (treeOrder.get(right.nodeId) ?? right.order));
|
|
99
106
|
for (const layer of members) if (layer.z < 0) paintMember(layer);
|
|
100
107
|
paintLayerContent(context);
|
|
101
108
|
for (const layer of members) if (layer.z >= 0) paintMember(layer);
|
|
@@ -111,7 +118,7 @@ export function buildDisplayList(layout, tree = null, resources = null, backgrou
|
|
|
111
118
|
const start = items.length;
|
|
112
119
|
const rect = { x: box.x, y: box.y, width: box.width, height: box.height };
|
|
113
120
|
hits.push({
|
|
114
|
-
nodeId: box.nodeId, rect: effect.transform ? transformRect(effect.transform, rect) : rect, clipRect: effect.clipRect, fixed: effect.fixed,
|
|
121
|
+
nodeId: box.nodeId, localRect:rect, transform:effect.transform, rect: effect.transform ? transformRect(effect.transform, rect) : rect, clipRect: effect.clipRect, fixed: effect.fixed,
|
|
115
122
|
});
|
|
116
123
|
reach(effect, rect);
|
|
117
124
|
const radii = cornerRadii(style, rect);
|
|
@@ -189,7 +196,7 @@ export function buildDisplayList(layout, tree = null, resources = null, backgrou
|
|
|
189
196
|
height: run.type === "image" ? run.height : fragment.height,
|
|
190
197
|
};
|
|
191
198
|
hits.push({
|
|
192
|
-
nodeId: run.nodeId,
|
|
199
|
+
nodeId: run.nodeId, localRect:hitRect, transform:effect.transform,
|
|
193
200
|
rect: effect.transform ? transformRect(effect.transform, hitRect) : hitRect,
|
|
194
201
|
clipRect: effect.clipRect,
|
|
195
202
|
fixed: effect.fixed,
|
|
@@ -296,6 +303,8 @@ export function hitTarget(displayList, x, y, scroll, skip = () => false) {
|
|
|
296
303
|
if (pointX < rect.x || pointX >= rect.x + rect.width || pointY < rect.y || pointY >= rect.y + rect.height) continue;
|
|
297
304
|
if (clipRect && (pointX < clipRect.x || pointX >= clipRect.x + clipRect.width
|
|
298
305
|
|| pointY < clipRect.y || pointY >= clipRect.y + clipRect.height)) continue;
|
|
306
|
+
const {transform,localRect}=hits[index];
|
|
307
|
+
if(transform&&localRect){const p=inverseTransformPoint(transform,pointX,pointY);if(p.x<localRect.x||p.x>=localRect.x+localRect.width||p.y<localRect.y||p.y>=localRect.y+localRect.height)continue;}
|
|
299
308
|
if (skip(nodeId)) continue;
|
|
300
309
|
return nodeId;
|
|
301
310
|
}
|
|
@@ -366,7 +375,11 @@ function paintEffects(layout, tree, scrollOffsetOf = null) {
|
|
|
366
375
|
const ownTransform = memo((node) => {
|
|
367
376
|
const transform = node.style?.transform;
|
|
368
377
|
const box = boxes.get(node.id);
|
|
369
|
-
if (!box || !transform
|
|
378
|
+
if (!box || !transform || (!transform.affine && (transform.scaleX == null || (transform.scaleX === 1 && transform.scaleY === 1)))) return null;
|
|
379
|
+
if (transform.affine) {
|
|
380
|
+
const {a,b,c,d,e,f}=resolveAffine(transform.affine,box.width,box.height), origin=transformOrigin(node.style.transformOrigin,box);
|
|
381
|
+
return {a,b,c,d,e:origin.x*(1-a)-c*origin.y+e,f:origin.y*(1-d)-b*origin.x+f};
|
|
382
|
+
}
|
|
370
383
|
const origin = transformOrigin(node.style.transformOrigin, box);
|
|
371
384
|
return {
|
|
372
385
|
a: transform.scaleX,
|
|
@@ -443,6 +456,17 @@ function paintEffects(layout, tree, scrollOffsetOf = null) {
|
|
|
443
456
|
transform = transform ? composeTransforms(transform, scrolled) : scrolled;
|
|
444
457
|
}
|
|
445
458
|
}
|
|
459
|
+
// Backface-hidden descendants share their ancestor's rotation only
|
|
460
|
+
// inside preserve-3d. Flattened groups begin a new local 3D context.
|
|
461
|
+
if(chain.some(node=>node.style?.backfaceVisibility==='hidden')){
|
|
462
|
+
const identity=[1,0,0,0,1,0,0,0,1];let rotation=identity;
|
|
463
|
+
for(let index=chain.length-1;index>=0;index--){
|
|
464
|
+
const style=chain[index].style;
|
|
465
|
+
rotation=multiplyRotation3D(rotation,rotationMatrix3D(style?.transform));
|
|
466
|
+
if(style?.backfaceVisibility==='hidden'&&rotation[8]<-1e-10){cache.set(key,null);return null;}
|
|
467
|
+
if(style?.transformStyle!=='preserve-3d')rotation=identity;
|
|
468
|
+
}
|
|
469
|
+
}
|
|
446
470
|
const roundedClips = [];
|
|
447
471
|
for (const [node, clip, radii] of clips) {
|
|
448
472
|
const matrix = transforms.get(node);
|
|
@@ -495,16 +519,17 @@ function transformOrigin(value, box) {
|
|
|
495
519
|
}
|
|
496
520
|
|
|
497
521
|
/** `outer` after `inner`: x -> outer(inner(x)), for scales with translation. */
|
|
498
|
-
function
|
|
499
|
-
|
|
522
|
+
export function inverseTransformPoint(matrix,x,y) {
|
|
523
|
+
const {a,b=0,c=0,d,e,f}=matrix,det=a*d-b*c;
|
|
524
|
+
if(!det)return {x:Infinity,y:Infinity};
|
|
525
|
+
x-=e;y-=f;return {x:(d*x-c*y)/det,y:(a*y-b*x)/det};
|
|
500
526
|
}
|
|
501
527
|
|
|
502
|
-
function transformRect(matrix, rect) {
|
|
503
|
-
const
|
|
504
|
-
const
|
|
505
|
-
const
|
|
506
|
-
|
|
507
|
-
return { x: Math.min(x0, x1), y: Math.min(y0, y1), width: Math.abs(x1 - x0), height: Math.abs(y1 - y0) };
|
|
528
|
+
export function transformRect(matrix, rect) {
|
|
529
|
+
const {a,b=0,c=0,d,e,f}=matrix;
|
|
530
|
+
const points=[[rect.x,rect.y],[rect.x+rect.width,rect.y],[rect.x,rect.y+rect.height],[rect.x+rect.width,rect.y+rect.height]].map(([x,y])=>({x:a*x+c*y+e,y:b*x+d*y+f}));
|
|
531
|
+
const x=Math.min(...points.map(p=>p.x)),y=Math.min(...points.map(p=>p.y));
|
|
532
|
+
return {x,y,width:Math.max(...points.map(p=>p.x))-x,height:Math.max(...points.map(p=>p.y))-y};
|
|
508
533
|
}
|
|
509
534
|
|
|
510
535
|
function applyEffect(items, start, effect) {
|
|
@@ -16,21 +16,30 @@ export class RasterReadback {
|
|
|
16
16
|
this.#allocate = allocate;
|
|
17
17
|
}
|
|
18
18
|
|
|
19
|
-
read(image, width, height) {
|
|
19
|
+
read(image, width, height, { premultiplied = false, previous = null, region = null, allocate = this.#allocate } = {}) {
|
|
20
20
|
const kit = this.#kit;
|
|
21
21
|
const bytes = width * height * 4;
|
|
22
22
|
if (!this.#scratch || this.#scratch.length < bytes) {
|
|
23
23
|
this.dispose();
|
|
24
24
|
this.#scratch = kit.Malloc(Uint8Array, bytes);
|
|
25
25
|
}
|
|
26
|
-
const
|
|
27
|
-
width,
|
|
28
|
-
|
|
29
|
-
|
|
26
|
+
const partial = previous?.length === bytes && region
|
|
27
|
+
&& [region.x,region.y,region.width,region.height].every(Number.isInteger)
|
|
28
|
+
&& region.x >= 0 && region.y >= 0 && region.width > 0 && region.height > 0
|
|
29
|
+
&& region.x + region.width <= width && region.y + region.height <= height;
|
|
30
|
+
const x=partial?region.x:0,y=partial?region.y:0;
|
|
31
|
+
const rw=partial?region.width:width,rh=partial?region.height:height;
|
|
32
|
+
const native = image.readPixels(x, y, {
|
|
33
|
+
width:rw, height:rh, colorType: kit.ColorType.RGBA_8888,
|
|
34
|
+
alphaType: premultiplied ? kit.AlphaType.Premul : kit.AlphaType.Unpremul, colorSpace: kit.ColorSpace.SRGB,
|
|
35
|
+
}, this.#scratch, rw * 4);
|
|
30
36
|
if (!native) throw new Error("Unable to read raster pixels");
|
|
31
|
-
const pixels =
|
|
37
|
+
const pixels = allocate ? allocate(bytes)
|
|
32
38
|
: new Uint8Array(this.#shared ? new SharedArrayBuffer(bytes) : new ArrayBuffer(bytes));
|
|
33
|
-
|
|
39
|
+
if(partial){
|
|
40
|
+
pixels.set(previous);
|
|
41
|
+
for(let row=0;row<rh;row++)pixels.set(native.subarray(row*rw*4,(row+1)*rw*4),((y+row)*width+x)*4);
|
|
42
|
+
}else pixels.set(native.subarray(0, bytes));
|
|
34
43
|
return pixels;
|
|
35
44
|
}
|
|
36
45
|
|
|
@@ -12,7 +12,7 @@ export function supportsFrameCheckpoint(version = Bun.version) {
|
|
|
12
12
|
|
|
13
13
|
/** One callback collection per document, with a checkpoint between callbacks. */
|
|
14
14
|
export function createAnimationFrames({ invoke, now, checkpoint = drainMicrotasks,
|
|
15
|
-
frameTimers = null }) {
|
|
15
|
+
frameTimers = null, afterFrame = null }) {
|
|
16
16
|
let nextWakeId = 1;
|
|
17
17
|
// A private timer collection keeps frame handles separate from Window timers.
|
|
18
18
|
// timerTask=false also prevents a frame inheriting a timer's nesting clamp.
|
|
@@ -42,10 +42,15 @@ export function createAnimationFrames({ invoke, now, checkpoint = drainMicrotask
|
|
|
42
42
|
finally { checkpoint(); }
|
|
43
43
|
}
|
|
44
44
|
} finally {
|
|
45
|
-
|
|
46
|
-
//
|
|
47
|
-
|
|
48
|
-
|
|
45
|
+
// Paint belongs after this complete callback batch and its microtasks,
|
|
46
|
+
// before the next animation opportunity can overtake presentation.
|
|
47
|
+
try { if(!disposed)afterFrame?.(); }
|
|
48
|
+
finally {
|
|
49
|
+
running = false;
|
|
50
|
+
// Callbacks registered during delivery, including its microtasks, belong
|
|
51
|
+
// to the next frame. Long callbacks do not add another fixed 16 ms delay.
|
|
52
|
+
arm(Math.max(0, Math.ceil(16 - (now() - timestamp))));
|
|
53
|
+
}
|
|
49
54
|
}
|
|
50
55
|
}
|
|
51
56
|
return {
|
package/lib/renderer/bindings.js
CHANGED
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
import {animationProperty} from '../style/animation-values.js';
|
|
2
|
+
import {inputFiles} from './file-input.js';
|
|
3
|
+
import { DataTransfer, dragData, dragEvent, isDraggable } from './html-drag.js';
|
|
1
4
|
/**
|
|
2
5
|
* DOM bindings: the only view page scripts get of the document.
|
|
3
6
|
*
|
|
@@ -227,7 +230,7 @@ export class BindingRealm {
|
|
|
227
230
|
if (!wrapper) {
|
|
228
231
|
wrapper = new EventBinding(CONSTRUCT, event, this, detail);
|
|
229
232
|
if (this.eventPrototype) {
|
|
230
|
-
const prototype = this.eventPrototypes?.get(this.#messages.has(event) ? "MessageEvent" : event.constructor?.name) ?? this.eventPrototype;
|
|
233
|
+
const prototype = this.eventPrototypes?.get(this.#messages.has(event) ? "MessageEvent" : dragData(event) !== undefined ? "DragEvent" : event.constructor?.name) ?? this.eventPrototype;
|
|
231
234
|
Object.setPrototypeOf(wrapper, prototype);
|
|
232
235
|
// isTrusted is [LegacyUnforgeable]: an own accessor of every event.
|
|
233
236
|
const isTrusted = Object.getOwnPropertyDescriptor(EventBinding.prototype, "isTrusted");
|
|
@@ -525,6 +528,7 @@ class NodeBinding {
|
|
|
525
528
|
dispatchEvent(event) {
|
|
526
529
|
const real = EventBinding.unwrap(event);
|
|
527
530
|
if (!real) throw new TypeError("dispatchEvent requires an Event");
|
|
531
|
+
this.#realm.hooks.prepareEvent?.(this.#node, real.type);
|
|
528
532
|
return this.#node.dispatchEvent(real);
|
|
529
533
|
}
|
|
530
534
|
}
|
|
@@ -821,7 +825,11 @@ class ElementBinding extends ParentBinding {
|
|
|
821
825
|
}
|
|
822
826
|
|
|
823
827
|
get classList() {
|
|
824
|
-
this.#classList
|
|
828
|
+
if (!this.#classList) {
|
|
829
|
+
const realm=realmOf(this);
|
|
830
|
+
this.#classList=new ClassListBinding(CONSTRUCT,nodeOf(this),realm);
|
|
831
|
+
if(realm.tokenListPrototype)Object.setPrototypeOf(this.#classList,realm.tokenListPrototype);
|
|
832
|
+
}
|
|
825
833
|
return this.#classList;
|
|
826
834
|
}
|
|
827
835
|
|
|
@@ -1061,6 +1069,8 @@ class ElementBinding extends ParentBinding {
|
|
|
1061
1069
|
inserted(this, list);
|
|
1062
1070
|
}
|
|
1063
1071
|
|
|
1072
|
+
get files(){return inputFiles(nodeOf(this));}
|
|
1073
|
+
showPicker(){const e=nodeOf(this);if(e.disabled)throw new DOMException('The input is disabled','InvalidStateError');realmOf(this).hooks.pickFile?.(e);}
|
|
1064
1074
|
click() {
|
|
1065
1075
|
const realm = realmOf(this);
|
|
1066
1076
|
const element = nodeOf(this);
|
|
@@ -1072,6 +1082,7 @@ class ElementBinding extends ParentBinding {
|
|
|
1072
1082
|
this.#clickInProgress = true;
|
|
1073
1083
|
try {
|
|
1074
1084
|
element.dispatchEvent(event);
|
|
1085
|
+
if (!event.defaultPrevented) realm.hooks.pickFile?.(element);
|
|
1075
1086
|
if (!event.defaultPrevented && anchor?.hasAttribute("href")) realm.hooks.activateLink?.(anchor);
|
|
1076
1087
|
} finally {
|
|
1077
1088
|
this.#clickInProgress = false;
|
|
@@ -1112,7 +1123,7 @@ class ElementBinding extends ParentBinding {
|
|
|
1112
1123
|
if(!arguments.length)throw new TypeError('setRangeText requires replacement');
|
|
1113
1124
|
nodeOf(this).setRangeText(String(replacement),...args.map((v,i)=>i<2?Number(v):String(v)));touch(this);
|
|
1114
1125
|
}
|
|
1115
|
-
select() {nodeOf(this).select();touch(this);}
|
|
1126
|
+
select() {const node=nodeOf(this);node.focus();node.select();touch(this);}
|
|
1116
1127
|
|
|
1117
1128
|
setCustomValidity(message) {
|
|
1118
1129
|
if (!arguments.length) throw new TypeError("setCustomValidity requires a message");
|
|
@@ -1137,6 +1148,9 @@ class ElementBinding extends ParentBinding {
|
|
|
1137
1148
|
if (node.ownerDocument.activeElement !== previous) touch(this, [":focus", ":focus-visible"]);
|
|
1138
1149
|
}
|
|
1139
1150
|
|
|
1151
|
+
get draggable() { return isDraggable(nodeOf(this)); }
|
|
1152
|
+
set draggable(value) { nodeOf(this).setAttribute('draggable',Boolean(value) ? 'true' : 'false'); touch(this); }
|
|
1153
|
+
|
|
1140
1154
|
getBoundingClientRect() {
|
|
1141
1155
|
const rect = realmOf(this).hooks.boundsOf(nodeOf(this)) ?? { left: 0, top: 0, width: 0, height: 0 };
|
|
1142
1156
|
return domRect(rect);
|
|
@@ -1378,6 +1392,7 @@ function isMedia(wrapper) {
|
|
|
1378
1392
|
|
|
1379
1393
|
/** on* event handler properties (element.onclick = fn, script.onload = fn). */
|
|
1380
1394
|
export const EVENT_HANDLER_NAMES = Object.freeze([
|
|
1395
|
+
"dragstart", "drag", "dragenter", "dragleave", "dragover", "drop", "dragend", "pointercancel",
|
|
1381
1396
|
"click", "auxclick", "dblclick", "mousedown", "mouseup", "mousemove", "mouseover", "mouseout", "mouseenter", "mouseleave",
|
|
1382
1397
|
"keydown", "keyup", "keypress", "beforeinput", "input", "change", "submit", "reset", "focus", "blur", "load", "error",
|
|
1383
1398
|
"abort", "scroll", "resize", "wheel", "contextmenu", "touchstart", "touchend", "touchmove",
|
|
@@ -1394,11 +1409,13 @@ export function defineEventHandlerProperty(object, type, targetOf, realmFor) {
|
|
|
1394
1409
|
configurable: true,
|
|
1395
1410
|
enumerable: true,
|
|
1396
1411
|
get() {
|
|
1412
|
+
realmFor(this).hooks.prepareHandler?.(targetOf(this), type);
|
|
1397
1413
|
return handlers.get(this)?.handler ?? null;
|
|
1398
1414
|
},
|
|
1399
1415
|
set(value) {
|
|
1400
1416
|
const realm = realmFor(this);
|
|
1401
1417
|
const target = targetOf(this);
|
|
1418
|
+
realm.hooks.prepareHandler?.(target, type);
|
|
1402
1419
|
const previous = handlers.get(this);
|
|
1403
1420
|
if (previous) target.removeEventListener(type, previous.listener);
|
|
1404
1421
|
handlers.delete(this);
|
|
@@ -1427,6 +1444,10 @@ for (const property of REFLECTED_PROPERTIES) {
|
|
|
1427
1444
|
get() {
|
|
1428
1445
|
const node = nodeOf(this);
|
|
1429
1446
|
const value = node[property];
|
|
1447
|
+
if (property === "value" && node.tagName === "INPUT" && node.type === "file") {
|
|
1448
|
+
const file = inputFiles(node)[0];
|
|
1449
|
+
return file ? "C:\\fakepath\\" + file.name : "";
|
|
1450
|
+
}
|
|
1430
1451
|
if (typeof value === "string" && ["src", "href", "action", "poster"].includes(property)) {
|
|
1431
1452
|
const attribute = node.getAttribute(property);
|
|
1432
1453
|
if (property === "action" && !attribute) return node.ownerDocument.URL;
|
|
@@ -1531,6 +1552,15 @@ class DocumentBinding extends ParentBinding {
|
|
|
1531
1552
|
return realm.wrap(node === realm.window.document && realm.hooks.body ? realm.hooks.body() : node.body);
|
|
1532
1553
|
}
|
|
1533
1554
|
|
|
1555
|
+
execCommand(command, _showUI = false, _value = null) {
|
|
1556
|
+
return Boolean(realmOf(this).hooks.execCommand?.(String(command).toLowerCase()));
|
|
1557
|
+
}
|
|
1558
|
+
|
|
1559
|
+
queryCommandSupported(command) { return String(command).toLowerCase() === 'copy'; }
|
|
1560
|
+
queryCommandEnabled(command) {
|
|
1561
|
+
return this.queryCommandSupported(command) && Boolean(realmOf(this).hooks.copyEnabled?.());
|
|
1562
|
+
}
|
|
1563
|
+
|
|
1534
1564
|
get title() {
|
|
1535
1565
|
return nodeOf(this).title;
|
|
1536
1566
|
}
|
|
@@ -1853,11 +1883,15 @@ class EventBinding {
|
|
|
1853
1883
|
}
|
|
1854
1884
|
|
|
1855
1885
|
get offsetX() {
|
|
1856
|
-
|
|
1886
|
+
// CSSOM View: relative to the target padding edge while dispatching;
|
|
1887
|
+
// outside dispatch offsetX/Y fall back to pageX/Y (also for synthetic events).
|
|
1888
|
+
if (!this.#event.eventPhase) return this.pageX;
|
|
1889
|
+
return this.#realm.hooks.mouseOffset?.(this.#event.target, this.clientX, this.clientY)?.x ?? this.pageX;
|
|
1857
1890
|
}
|
|
1858
1891
|
|
|
1859
1892
|
get offsetY() {
|
|
1860
|
-
|
|
1893
|
+
if (!this.#event.eventPhase) return this.pageY;
|
|
1894
|
+
return this.#realm.hooks.mouseOffset?.(this.#event.target, this.clientX, this.clientY)?.y ?? this.pageY;
|
|
1861
1895
|
}
|
|
1862
1896
|
|
|
1863
1897
|
get movementX() {
|
|
@@ -1868,6 +1902,8 @@ class EventBinding {
|
|
|
1868
1902
|
return this.#event.movementY ?? 0;
|
|
1869
1903
|
}
|
|
1870
1904
|
|
|
1905
|
+
get dataTransfer() { return dragData(this.#event) ?? null; }
|
|
1906
|
+
|
|
1871
1907
|
get relatedTarget() {
|
|
1872
1908
|
return this.#realm.wrap(this.#event.relatedTarget ?? null) ?? null;
|
|
1873
1909
|
}
|
|
@@ -1918,6 +1954,10 @@ class EventBinding {
|
|
|
1918
1954
|
return typeof this.#event.newURL === "string" ? this.#event.newURL : undefined;
|
|
1919
1955
|
}
|
|
1920
1956
|
|
|
1957
|
+
get animationName() { return this.#event.animationName ?? ""; }
|
|
1958
|
+
get elapsedTime() { return this.#event.elapsedTime ?? 0; }
|
|
1959
|
+
get pseudoElement() { return this.#event.pseudoElement ?? ""; }
|
|
1960
|
+
|
|
1921
1961
|
get key() {
|
|
1922
1962
|
return this.#event.key;
|
|
1923
1963
|
}
|
|
@@ -2409,17 +2449,17 @@ export function cssStyleDeclarationInterface(realm) {
|
|
|
2409
2449
|
get cssFloat() { return styleState(this).style.getPropertyValue("float"); },
|
|
2410
2450
|
set cssFloat(value) { this.setProperty("float", value); },
|
|
2411
2451
|
item(index) { return styleState(this).style.item?.(Number(index) >>> 0) ?? ""; },
|
|
2412
|
-
getPropertyValue(property) { return styleState(this).style.getPropertyValue(
|
|
2413
|
-
getPropertyPriority(property) { return styleState(this).style.getPropertyPriority?.(
|
|
2452
|
+
getPropertyValue(property) { return styleState(this).style.getPropertyValue(animationProperty(property)); },
|
|
2453
|
+
getPropertyPriority(property) { return styleState(this).style.getPropertyPriority?.(animationProperty(property)) ?? ""; },
|
|
2414
2454
|
setProperty(property, value, priority = "") {
|
|
2415
2455
|
const { style, node, realm: owner } = styleState(this);
|
|
2416
2456
|
const before = style.cssText;
|
|
2417
|
-
style.setProperty(
|
|
2457
|
+
style.setProperty(animationProperty(property), value === null ? "" : String(value), String(priority));
|
|
2418
2458
|
if (style.cssText !== before) owner.hooks.touch(node);
|
|
2419
2459
|
},
|
|
2420
2460
|
removeProperty(property) {
|
|
2421
2461
|
const { style, node, realm: owner } = styleState(this);
|
|
2422
|
-
const previous = style.removeProperty(
|
|
2462
|
+
const previous = style.removeProperty(animationProperty(property));
|
|
2423
2463
|
owner.hooks.touch(node);
|
|
2424
2464
|
return previous;
|
|
2425
2465
|
},
|
|
@@ -2443,8 +2483,7 @@ export function cssStyleDeclarationInterface(realm) {
|
|
|
2443
2483
|
function styleBinding(node, realm) {
|
|
2444
2484
|
const style = node.style;
|
|
2445
2485
|
const prototype = cssStyleDeclarationInterface(realm).prototype;
|
|
2446
|
-
const toKebab = (property) => property.startsWith("--") ? property
|
|
2447
|
-
: property.replace(/[A-Z]/g, (letter) => `-${letter.toLowerCase()}`);
|
|
2486
|
+
const toKebab = (property) => property.startsWith("--") ? property : animationProperty(property);
|
|
2448
2487
|
const proxy = new Proxy(Object.create(prototype), {
|
|
2449
2488
|
get(target, property, receiver) {
|
|
2450
2489
|
if (typeof property !== "string" || property in prototype) return Reflect.get(target, property, receiver);
|
|
@@ -2460,6 +2499,8 @@ function styleBinding(node, realm) {
|
|
|
2460
2499
|
},
|
|
2461
2500
|
has: (_target, property) => {
|
|
2462
2501
|
if (typeof property !== "string") return property in prototype;
|
|
2502
|
+
const normalized = animationProperty(property);
|
|
2503
|
+
if(/^animation(?:-(?:name|duration|delay|iteration-count|direction|fill-mode|play-state|timing-function))?$/.test(normalized))return normalized in style || normalized.replace(/-([a-z])/g, (_,letter)=>letter.toUpperCase()) in style;
|
|
2463
2504
|
const camel = property.replace(/-([a-z])/g, (_, letter) => letter.toUpperCase());
|
|
2464
2505
|
return property in prototype || (property in style && !UNRENDERED_STYLE_PROPERTIES.has(camel));
|
|
2465
2506
|
},
|
|
@@ -2779,8 +2820,10 @@ const MOUSE_MEMBERS = ["screenX", "screenY", "clientX", "clientY", "pageX", "pag
|
|
|
2779
2820
|
/** Event interface → [parent interface, members it owns], parents first. */
|
|
2780
2821
|
const EVENT_INTERFACES = {
|
|
2781
2822
|
CustomEvent: ["Event", ["detail", "initCustomEvent"]],
|
|
2823
|
+
AnimationEvent: ["Event", ["animationName", "elapsedTime", "pseudoElement"]],
|
|
2782
2824
|
UIEvent: ["Event", ["detail", "view", "which", "initUIEvent"]],
|
|
2783
2825
|
MouseEvent: ["UIEvent", MOUSE_MEMBERS],
|
|
2826
|
+
DragEvent: ["MouseEvent", ["dataTransfer"]],
|
|
2784
2827
|
PointerEvent: ["MouseEvent", ["pointerType"]],
|
|
2785
2828
|
WheelEvent: ["MouseEvent", ["deltaX", "deltaY", "deltaMode"]],
|
|
2786
2829
|
KeyboardEvent: ["UIEvent", ["key", "code", "keyCode", "charCode", "repeat", ...MODIFIERS]],
|
|
@@ -2797,11 +2840,11 @@ const CREATE_EVENT_TYPES = new Map([["event", "Event"], ["events", "Event"], ["h
|
|
|
2797
2840
|
["mouseevents", "MouseEvent"], ["keyboardevent", "KeyboardEvent"], ["focusevent", "FocusEvent"],
|
|
2798
2841
|
["messageevent", "MessageEvent"], ["hashchangeevent", "HashChangeEvent"], ["popstateevent", "PopStateEvent"]]);
|
|
2799
2842
|
|
|
2800
|
-
const UI_EVENT_TYPES = ["UIEvent", "MouseEvent", "PointerEvent", "WheelEvent", "KeyboardEvent", "FocusEvent", "InputEvent"];
|
|
2843
|
+
const UI_EVENT_TYPES = ["AnimationEvent", "UIEvent", "MouseEvent", "PointerEvent", "WheelEvent", "KeyboardEvent", "FocusEvent", "InputEvent"];
|
|
2801
2844
|
const UI_EVENT_FIELDS = [
|
|
2802
2845
|
...["detail", "clientX", "clientY", "screenX", "screenY", "button", "buttons", "deltaX", "deltaY", "deltaZ", "deltaMode",
|
|
2803
|
-
"pointerId", "width", "height", "pressure", "location", "keyCode", "which", "charCode"].map((key) => [key, Number]),
|
|
2804
|
-
...["key", "code", "data", "inputType", "pointerType"].map((key) => [key, String]),
|
|
2846
|
+
"pointerId", "width", "height", "pressure", "location", "keyCode", "which", "charCode", "elapsedTime"].map((key) => [key, Number]),
|
|
2847
|
+
...["key", "code", "data", "inputType", "pointerType", "animationName", "pseudoElement"].map((key) => [key, String]),
|
|
2805
2848
|
...["ctrlKey", "altKey", "shiftKey", "metaKey", "repeat", "isComposing", "isPrimary"].map((key) => [key, Boolean]),
|
|
2806
2849
|
];
|
|
2807
2850
|
|
|
@@ -2854,7 +2897,17 @@ export function eventConstructors(realm) {
|
|
|
2854
2897
|
Object.defineProperty(CustomEvent.prototype, "constructor", { value: CustomEvent, writable: true, configurable: true });
|
|
2855
2898
|
// UI event subclasses: their init dictionaries are copied as primitives only
|
|
2856
2899
|
// (view and relatedTarget are dropped).
|
|
2857
|
-
|
|
2900
|
+
function DragEvent(type, options = {}) {
|
|
2901
|
+
if (!new.target) throw new TypeError("Constructor DragEvent requires 'new'");
|
|
2902
|
+
const data=options?.dataTransfer ?? null;
|
|
2903
|
+
if(data!==null && !(data instanceof DataTransfer))throw new TypeError("Invalid dataTransfer");
|
|
2904
|
+
const fields={...init(options)};
|
|
2905
|
+
for(const [key,coerce] of UI_EVENT_FIELDS)if(options?.[key]!==undefined)fields[key]=coerce(options[key]);
|
|
2906
|
+
return realm.wrapEvent(dragEvent(window,String(type),fields,data));
|
|
2907
|
+
}
|
|
2908
|
+
DragEvent.prototype=realm.eventPrototypes.get('DragEvent');
|
|
2909
|
+
Object.defineProperty(DragEvent.prototype,'constructor',{value:DragEvent,writable:true,configurable:true});
|
|
2910
|
+
const uiEvents = {DragEvent,DataTransfer};
|
|
2858
2911
|
for (const name of UI_EVENT_TYPES) {
|
|
2859
2912
|
const RealEvent = window[name];
|
|
2860
2913
|
if (typeof RealEvent !== "function") continue;
|
|
@@ -2936,7 +2989,7 @@ function cssEscape(value) {
|
|
|
2936
2989
|
* Must run after SES lockdown() (which provides `harden`).
|
|
2937
2990
|
*/
|
|
2938
2991
|
const BINDINGS = [NodeBinding, CharacterDataBinding, TextBinding, ParentBinding, FragmentBinding, ShadowRootBinding, ElementBinding,
|
|
2939
|
-
DocumentBinding, EventBinding, ClassListBinding, TreeWalkerBinding, NodeIteratorBinding, RangeBinding];
|
|
2992
|
+
DocumentBinding, EventBinding, DataTransfer, ClassListBinding, TreeWalkerBinding, NodeIteratorBinding, RangeBinding];
|
|
2940
2993
|
|
|
2941
2994
|
// Members defined with Object.defineProperty({ get() {...} }) are named
|
|
2942
2995
|
// "get"; Web IDL names an attribute's accessors "get x" / "set x" and an
|