xbintsc 0.5.36 → 0.5.37

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 (66) hide show
  1. package/dist/src/cli/help.d.ts +1 -1
  2. package/dist/src/driver/version.d.ts +1 -1
  3. package/dist/src/driver/version.js +1 -1
  4. package/dist/src/extensions/gui/index.js +12 -0
  5. package/dist/src/extensions/gui/index.js.map +1 -1
  6. package/dist/tests/e2e/gui-browser.test.js +65 -0
  7. package/dist/tests/e2e/gui-browser.test.js.map +1 -1
  8. package/dist/tests/e2e/gui-canvas-args.test.d.ts +6 -0
  9. package/dist/tests/e2e/gui-canvas-args.test.js +217 -0
  10. package/dist/tests/e2e/gui-canvas-args.test.js.map +1 -0
  11. package/dist/tests/e2e/gui-canvas.test.js.map +1 -1
  12. package/dist/tests/e2e/gui-dom.test.js +215 -0
  13. package/dist/tests/e2e/gui-dom.test.js.map +1 -1
  14. package/dist/tests/e2e/gui-events.test.js +112 -0
  15. package/dist/tests/e2e/gui-events.test.js.map +1 -1
  16. package/doc/gui/api.md +38 -9
  17. package/doc/gui/canvas.md +49 -18
  18. package/doc/gui/dom.md +51 -4
  19. package/doc/zh-CN/gui/api.md +33 -4
  20. package/doc/zh-CN/gui/canvas.md +44 -19
  21. package/doc/zh-CN/gui/dom.md +45 -3
  22. package/package.json +7 -7
  23. package/runtime/ext_gui/browser.cpp +34 -49
  24. package/runtime/ext_gui/browser_abort.cpp +249 -0
  25. package/runtime/ext_gui/browser_dialogs.cpp +76 -0
  26. package/runtime/ext_gui/browser_media.cpp +176 -0
  27. package/runtime/ext_gui/browser_navigation.cpp +130 -0
  28. package/runtime/ext_gui/canvas.cpp +88 -30
  29. package/runtime/ext_gui/canvas.h +74 -9
  30. package/runtime/ext_gui/canvas_image_data.cpp +143 -0
  31. package/runtime/ext_gui/canvas_internal.h +8 -5
  32. package/runtime/ext_gui/canvas_pattern.cpp +90 -0
  33. package/runtime/ext_gui/canvas_raster.cpp +31 -9
  34. package/runtime/ext_gui/canvas_style.cpp +4 -2
  35. package/runtime/ext_gui/dom.h +4 -0
  36. package/runtime/ext_gui/dom_api.cpp +1 -0
  37. package/runtime/ext_gui/dom_api_canvas.cpp +120 -114
  38. package/runtime/ext_gui/dom_api_canvas_extra.cpp +57 -2
  39. package/runtime/ext_gui/dom_api_canvas_image.cpp +239 -0
  40. package/runtime/ext_gui/dom_api_canvas_paths.cpp +179 -0
  41. package/runtime/ext_gui/dom_api_computed_style.cpp +124 -0
  42. package/runtime/ext_gui/dom_api_dataset.cpp +154 -0
  43. package/runtime/ext_gui/dom_api_document.cpp +58 -5
  44. package/runtime/ext_gui/dom_api_events.cpp +93 -17
  45. package/runtime/ext_gui/dom_api_internal.h +55 -2
  46. package/runtime/ext_gui/dom_api_mutation.cpp +159 -0
  47. package/runtime/ext_gui/dom_api_node.cpp +40 -19
  48. package/runtime/ext_gui/dom_api_node_read.cpp +37 -1
  49. package/runtime/ext_gui/dom_api_path2d.cpp +358 -0
  50. package/runtime/ext_gui/dom_api_proto.cpp +35 -0
  51. package/runtime/ext_gui/dom_api_style.cpp +223 -13
  52. package/runtime/ext_gui/gui.cpp +59 -5
  53. package/runtime/ext_gui/gui_engine.h +47 -0
  54. package/runtime/ext_gui/gui_events.cpp +80 -9
  55. package/runtime/ext_gui/listener_options.h +43 -0
  56. package/runtime/ext_gui/paint.cpp +28 -0
  57. package/runtime/ext_gui/paint.h +4 -0
  58. package/runtime/ext_gui/window.cpp +14 -65
  59. package/runtime/ext_gui/window_events.cpp +125 -0
  60. package/runtime/rt.h +26 -0
  61. package/runtime/rt_internal.h +4 -0
  62. package/runtime/xt_builtins.c +0 -21
  63. package/runtime/xt_containers/access.inc +94 -2
  64. package/runtime/xt_containers/objects.inc +29 -31
  65. package/src/driver/version.ts +1 -1
  66. package/src/extensions/gui/index.ts +12 -0
@@ -22,10 +22,38 @@ run(); // 驱动主循环,直到所有窗口
22
22
  `getHTML` / `setBackground` / `close` / `isOpen` / `on` / `off`,以及
23
23
  `driver()`——它报告当前使用的 SDL_GPU 后端(`"vulkan"`、`"direct3d12"`、
24
24
  `"metal"` 等,由 SDL 挑选设备支持的最佳后端)。同时也提供 Web 写法:
25
- `addEventListener` / `removeEventListener` 是 `on` / `off` 的别名(其
26
- `options` 参数会被接受并忽略),`closed` getter 对应 `isOpen()`,因此按浏览器
25
+ `addEventListener` / `removeEventListener` 接受标准的选项对象(`capture`、
26
+ `once`、`passive`、`signal`),`closed` getter 对应 `isOpen()`,因此按浏览器
27
27
  API 编写的文档可以原样运行。
28
28
 
29
+ 元素监听器支持同一组选项:`{ once }` 在首次调用后移除监听器,`{ passive: true }`
30
+ 使 `preventDefault()` 变成无操作,`{ signal }`(来自 `new AbortController()` 的
31
+ `AbortSignal`)在 abort 时移除监听器,包括添加时就已经 abort 的信号。
32
+ `element.dispatchEvent(event)` 派发的就是**同一个**事件对象(身份可观察),并填充
33
+ `target` / `currentTarget` / `eventPhase`、记录路径供 `event.composedPath()` 使用。
34
+ `new Event(...)`、`new CustomEvent(...)`、`new MouseEvent(...)`、
35
+ `new KeyboardEvent(...)`、`new PointerEvent(...)`、`new WheelEvent(...)`、
36
+ `new FocusEvent(...)`、`new InputEvent(...)` 与 `new SubmitEvent(...)` 会保留 `init`
37
+ 对象的全部字段(`detail`、`composed`、鼠标/键盘成员等)。窗口监听器接受同一组
38
+ 选项(`capture` 参与监听器身份匹配;窗口投递没有捕获阶段)。
39
+
40
+ 窗口导航是真实实现的:`scrollTo(x, y)` / `scrollTo({ left, top })` /
41
+ `scrollBy(...)` 移动页面视口(会按内容边界夹取,绘制与命中测试随之偏移),
42
+ `scrollX` / `scrollY` / `pageXOffset` / `pageYOffset` 报告偏移量。`focus()`
43
+ 提升操作系统窗口并置位 `document.hasFocus()`,`blur()` 清除它。事件:
44
+ `ready`(首帧呈现后)、`load`、`close`、`resize`、`scroll`、`focus`、`blur`,
45
+ 以及输入事件 `mousemove`、`mousedown`、`mouseup`、`click`、`wheel`、
46
+ `keydown`、`keyup`。
47
+
48
+ `createWindow(options)` 除标题与尺寸外还支持:`{ title, width, height,
49
+ highDensity, resizable, borderless, alwaysOnTop, hidden, fullscreen, x, y,
50
+ opacity, maximized, minimized }`,它们会映射到对应的 SDL 窗口标志/调用,而不是
51
+ 被忽略。
52
+
53
+ `window.confirm(message)` / `window.prompt(message, defaultValue)` 不会再丢弃
54
+ 参数:设置 `XT_GUI_DIALOGS=1` 且存在窗口时显示原生消息框;否则把消息写到 stderr
55
+ 并返回文档化的默认值(`confirm` → `false`,`prompt` → `defaultValue` 或 `""`)。
56
+
29
57
  在 Windows 上,GUI 程序会被链接为 **Windows 子系统**可执行文件,因此启动时不会闪出控制台
30
58
  窗口;这也意味着从资源管理器启动时 stdout/stderr 无处可去,调试时可在
31
59
  `xbintsc.config.json` 里设置 `app.console: true` 保留控制台子系统。
@@ -60,12 +88,13 @@ requestAnimationFrame(frame);
60
88
  | `navigator` | `userAgent`(以 `xbintsc` 结尾的桌面 Chrome 字符串)、`platform`、`vendor`、`language(s)`、`hardwareConcurrency`、`maxTouchPoints`、`onLine`、`vibrate()` |
61
89
  | `localStorage` | 内存中的 `getItem` / `setItem` / `removeItem` / `clear` / `key` / `length` |
62
90
  | `AudioContext`、`webkitAudioContext` | 带真实混音器与解码器的 Web Audio 图(见下) |
63
- | `Event`、`CustomEvent`、`ProgressEvent` 等 | 事件构造器 |
91
+ | `Event`、`CustomEvent`、`ProgressEvent`、`MouseEvent`、`KeyboardEvent`、`PointerEvent`、`WheelEvent`、`FocusEvent`、`InputEvent`、`SubmitEvent` | 事件构造器;`init` 参数按原样保留(`detail`、`composed`、鼠标/键盘字段等) |
92
+ | `AbortController`、`AbortSignal` | 真实的 controller/signal 对,用于 `{ signal }` 移除监听器,支持 `abort()`、`aborted`、`reason`、`onabort` 与 `throwIfAborted()` |
64
93
  | `HTMLCanvasElement`、`HTMLImageElement`、`WebGLRenderingContext`、`WebGL2RenderingContext`、`WebGLBuffer`/`WebGLTexture`/`WebGLShader`/`WebGLProgram`/`WebGLFramebuffer`/`WebGLRenderbuffer`/`WebGLUniformLocation`/`WebGLActiveInfo`/`WebGLVertexArrayObject`/`WebGLSampler`/`WebGLQuery`/`WebGLSync`/`WebGLTransformFeedback` | 真实的构造函数值。`canvas.getContext("webgl")` 与 `getContext("webgl2")` 返回 GPU 上下文(见 [WebGL](./webgl.md))。 |
65
94
  | `ImageBitmap`、`createImageBitmap(source)` | 由已解码像素支撑的不可变位图(`<img>`、`<canvas>`、`<video>` 帧与 `ImageData` 形状);promise 立即 settle,`texImage2D` 可直接接受该对象 |
66
95
  | `ImageData`、`OffscreenCanvas`、`XRWebGL*`、`Request`、`Headers`、`Response`、`ReadableStream`、`DOMParser` | `typeof` 探测返回 `"undefined"`(没有 XR/fetch/流式后端),让页面走自己的“不支持”分支,而不是调用空壳。Three.js 页面在本后端上使用真实渲染器(见[已知问题](./known-issues.md))。 |
67
96
  | `requestAnimationFrame` / `cancelAnimationFrame` | 排入窗口的动画帧列表,见[运行时集成](./runtime.md) |
68
- | `alert` / `confirm` / `prompt`、`matchMedia`、`getComputedStyle`、`escape` | 宿主实现(Annex B `escape` 按 UTF-16 码元编码,与 `decodeURIComponent(escape(bytes))` 一致) |
97
+ | `alert` / `confirm` / `prompt`、`matchMedia`、`getComputedStyle`、`escape` | 宿主实现。`matchMedia` 返回**实时**列表(按查询缓存):`matches` 在窗口尺寸变化时重新求值,翻转时触发 `change` 监听器。`confirm`/`prompt` 仅在 `XT_GUI_DIALOGS=1` 时使用原生对话框,否则把消息写到 stderr 并返回文档化的默认值(Annex B `escape` 按 UTF-16 码元编码,与 `decodeURIComponent(escape(bytes))` 一致) |
69
98
 
70
99
  `getElementById` / `querySelector` 在 `loadHTML` 之前也能回答:此时得到的是**挂起**句柄,
71
100
  首次使用时才绑定到真实节点,因此在模块初始化阶段缓存 `document.querySelector("#id")`
@@ -31,12 +31,19 @@ const ctx = canvas.getContext("2d"); // 或 "webgl" 取得 GPU 上下文
31
31
  元素上,因此每次都返回**同一个**对象,绘制状态(变换、样式、路径)按 canvas 而非按调用
32
32
  保存。`ctx.canvas` 是所属元素的句柄。
33
33
 
34
+ `getContext("2d", attributes)` 在首次调用时会尊重 `alpha`:传入 `{ alpha: false }`
35
+ 时位图以不透明黑色初始化(清除也回到不透明黑)。其余属性由
36
+ `getContextAttributes()` 按规范默认值报告(`antialias: true`、`depth: true`、
37
+ `stencil: false`、`premultipliedAlpha: true`、`preserveDrawingBuffer: false`、
38
+ `colorSpace: "srgb"` 等),特性探测因此能得到诚实的答案。
39
+
34
40
  ## 绘制模型
35
41
 
36
42
  光栅器刻意保持精简且为自研(不依赖 Skia/Cairo)。它按每条像素行 **4 条垂直子扫描线**
37
- 进行抗锯齿,并做一次**水平解析覆盖率**计算;路径采用**非零环绕规则**填充(因此当弧线
38
- 以相反方向环绕时能形成空洞)。轴对齐矩形(变换中 `b == 0 && c == 0`)走直接的覆盖率
39
- 快速路径。混合为非预乘 source-over,并按 `globalAlpha` 缩放。
43
+ 进行抗锯齿,并做一次**水平解析覆盖率**计算;路径默认采用**非零环绕规则**填充(因此当弧线
44
+ 以相反方向环绕时能形成空洞),`fill("evenodd")` 则改用奇偶规则。轴对齐矩形(变换中
45
+ `b == 0 && c == 0`)走直接的覆盖率快速路径。混合为非预乘 source-over,并按
46
+ `globalAlpha` 缩放。
40
47
 
41
48
  填充或描边可以使用纯色,也可以使用 **`CanvasGradient`**。渐变的坐标属于用户空间,
42
49
  因此光栅器会先用当前变换的逆矩阵把每个设备像素映射回用户空间再采样——形状被平移、
@@ -50,9 +57,11 @@ Canvas 像素**不参与垃圾回收**:canvas 由窗口拥有、以 DOM 节点
50
57
  ## 支持的 API
51
58
 
52
59
  状态与变换:`save` / `restore`(会恢复变换、线宽、透明度、字体/文本设置**以及**
53
- 填充/描边样式)、`translate`、`rotate`、`scale`、`transform(a,b,c,d,e,f)`、
54
- `setTransform`、`resetTransform`、`getTransform`(返回带 `a`…`f` 与 `is2D` 的对象)。
55
- 变换按 canvas 的顺序组合:`translate(x, y); rotate(a)` 围绕平移后的原点旋转。
60
+ 填充/描边样式)、`translate`、`rotate`、`scale`、`transform(a,b,c,d,e,f)` /
61
+ `transform(matrix)`、`setTransform(a,b,c,d,e,f)` / `setTransform(matrix)`、
62
+ `resetTransform`、`getTransform`(返回带 `a`…`f` 与 `is2D` 的对象)。矩阵可以是
63
+ `{ a, b, c, d, e, f }` 对象或六元素数组。变换按 canvas 的顺序组合:
64
+ `translate(x, y); rotate(a)` 围绕平移后的原点旋转。
56
65
 
57
66
  样式与设置:`fillStyle` / `strokeStyle`(层叠所用的颜色解析器能识别的 CSS 颜色,或
58
67
  `CanvasGradient`)、`lineWidth`、`globalAlpha`、`lineCap`(`butt`/`round`/`square`)、
@@ -70,20 +79,27 @@ Canvas 像素**不参与垃圾回收**:canvas 由窗口拥有、以 DOM 节点
70
79
  会忽略 `x0`/`y0`——请使用同心圆(`x0 == x1`、`y0 == y1`)。
71
80
 
72
81
  路径:`beginPath`、`closePath`、`moveTo`、`lineTo`、`rect`、`roundRect`、`arc`、
73
- `ellipse`、`quadraticCurveTo`、`bezierCurveTo`,然后 `fill`(多环、非零环绕)或
74
- `stroke`。贝塞尔与二次曲线段会被展平为线段;弧线与椭圆同样被展平。`roundRect` 只接受
75
- 单个圆角半径数字(数组形式尚未实现)。
82
+ `ellipse`、`quadraticCurveTo`、`bezierCurveTo`,然后
83
+ `fill(fillRule?)` / `fill(path, fillRule?)`(多环;`"nonzero"` 或 `"evenodd"`)或
84
+ `stroke(path?)`。贝塞尔与二次曲线段会被展平为线段;弧线与椭圆同样被展平。
85
+ `roundRect` 接受单个半径数字,或 CSS 半径列表(`[all]`、`[tl, br]`、
86
+ `[tl, tr, br]`、`[tl, tr, br, bl]`,元素为数字或 `{ x, y }` 对;圆角按圆弧绘制)。
87
+
88
+ `Path2D` 支持同一组路径命令(`moveTo`、`lineTo`、`closePath`、`rect`、
89
+ `roundRect`、`arc`、`ellipse`、`quadraticCurveTo`、`bezierCurveTo`、`addPath`),
90
+ 并可传给 `fill`/`stroke`,因此一次构建的路径可以在不同变换下重复使用。
76
91
 
77
92
  矩形:`clearRect`、`fillRect`、`strokeRect`。
78
93
 
79
94
  虚线:`setLineDash(segments)` / `getLineDash()` / `lineDashOffset` 实现标准规则
80
95
  (奇数长度列表会复制一遍,负数/非有限值会被忽略,全零列表表示实线)。
81
96
 
82
- 文本:`measureText(text)`(返回 `{ width }`)、`fillText(text, x, y)` 与
83
- `strokeText(text, x, y)`。文本经 HarfBuzz 排版、FreeType 光栅化(与 HTML 文本相同的
84
- 技术栈),按变换的近似缩放绘制。**仅支持平移与均匀缩放**——旋转或斜切变换仍会把每个
85
- 字形放到其变换后的基线点,但不会旋转字形位图。`strokeText` 目前用描边样式执行填充,
86
- 而非生成轮廓。
97
+ 文本:`measureText(text)`(返回 `{ width }`)、`fillText(text, x, y, maxWidth?)` 与
98
+ `strokeText(text, x, y, maxWidth?)`。文本经 HarfBuzz 排版、FreeType 光栅化(与 HTML
99
+ 文本相同的技术栈),按变换的近似缩放绘制。正的 `maxWidth` 会按比例压缩步进与字形位图,
100
+ 使文本宽度不超过该值(与 canvas API 一致),而不是忽略该参数。**仅支持平移与均匀
101
+ 缩放**——旋转或斜切变换仍会把每个字形放到其变换后的基线点,但不会旋转字形位图。
102
+ `strokeText` 目前用描边样式执行填充,而非生成轮廓。
87
103
 
88
104
  图像:`drawImage(source, …)` 支持 3 参数(`dx, dy`)、5 参数(`dx, dy, dw, dh`)与
89
105
  9 参数(本引擎为**目标优先**:`dx, dy, dw, dh, sx, sy, sw, sh`)。完整的仿射变换
@@ -95,12 +111,21 @@ Canvas 像素**不参与垃圾回收**:canvas 由窗口拥有、以 DOM 节点
95
111
  测试钩子:`getImageData(x, y, w, h)` 把区域复制为 `{ width, height, data }`,其中
96
112
  `data` 是按行主序的 RGBA8 字节数组。越界像素为透明。
97
113
 
114
+ 图案:`createPattern(source, repetition?)` 接受 `<img>` 或另一个 `<canvas>`,在用户
115
+ 空间中平铺(`repeat`、`repeat-x`、`repeat-y`、`no-repeat`),因此平铺会跟随当前
116
+ 变换。把结果赋给 `fillStyle`/`strokeStyle`;重新赋颜色会替换掉图案。
117
+
118
+ 图像数据与导出:`createImageData(width, height)` 分配一个空的
119
+ `{ width, height, data }`,`putImageData(imageData, dx, dy[, dirtyX, dirtyY,
120
+ dirtyWidth, dirtyHeight])` 直接把字节写入位图(不受变换、混合或 `globalAlpha`
121
+ 影响)。`canvas.toDataURL(type?)` 导出 PNG data URL,`canvas.toBlob(callback,
122
+ type?)` 从微任务中交付 Blob 形状的 `{ type, size }`(字节在
123
+ `__xt_gui_blob_data` 上;本运行时没有 `URL.createObjectURL`)。
124
+
98
125
  ## 尚未实现
99
126
 
100
- 图案(`createPattern`)、阴影、`clip()`、`globalCompositeOperation`、
101
- `putImageData`、`createImageData`、`isPointInPath`、以 `DOMMatrix` 调用的
102
- `setTransform`、可切换图像平滑的 `drawImage`、`toDataURL`/`toBlob`、把 `<canvas>` 用作
103
- CSS `background-image`,以及离屏画布(`OffscreenCanvas`、
104
- `transferControlToOffscreen`)。`roundRect` 只接受单个半径,径向渐变会忽略内圆偏移。
127
+ 阴影、`clip()`、`globalCompositeOperation`、`isPointInPath`、可切换图像平滑的
128
+ `drawImage`、把 `<canvas>` 用作 CSS `background-image`,以及离屏画布
129
+ (`OffscreenCanvas`、`transferControlToOffscreen`)。径向渐变会忽略内圆偏移。
105
130
 
106
131
  另见[媒体](./media.md):`drawImage` 与纹理上传所共享的图像/视频/音频解码栈。
@@ -68,17 +68,59 @@ created.textContent = "added";
68
68
  box.appendChild(created);
69
69
  box.removeChild(created);
70
70
 
71
+ // 父子变更辅助方法可在同一次调用中混用节点与字符串。
72
+ box.append("text", doc.createElement("span"));
73
+ box.prepend(doc.createElement("b"));
74
+ box.insertAdjacentHTML("beforeend", "<i>tail</i>"); // beforebegin/afterbegin/beforeend/afterend
75
+ box.replaceChildren("fresh", doc.createElement("em"));
76
+
77
+ // 命名空间会被记录;外部(SVG)元素名保留大小写。
78
+ const gradient = doc.createElementNS("http://www.w3.org/2000/svg", "svg:linearGradient");
79
+ console.log(gradient.namespaceURI, gradient.localName, gradient.prefix);
80
+
71
81
  inner.addEventListener("click", (e) => console.log(e.target.id, e.currentTarget.id));
72
82
  inner.click(); // 在该元素处合成一次点击
73
83
  console.log(box.offsetWidth, box.offsetHeight); // 取整后的边框盒
74
84
  console.log(box.contains(inner)); // 后代判断
75
85
  ```
76
86
 
87
+ ### style 属性、dataset、计算样式与几何
88
+
89
+ 内联 `style` 对象是一个 CSSOM 声明块。以属性名赋值/读取的是与 `setProperty`
90
+ 相同的声明块(`el.style.display = "none"`、`el.style.backgroundColor = "#f00"`,
91
+ 驼峰名会映射到 CSS 拼写,`el.style["--accent"]` 用于自定义属性);
92
+ `setProperty(name, value, priority)` 在 `priority` 为 `"important"` 时以
93
+ `!important` 保存该声明(其他优先级值会让本次调用成为无操作,空值则移除该属性)。
94
+ `getPropertyValue` / `getPropertyPriority` 读回声明,`removeProperty` 返回旧值,
95
+ `cssText` 读取或整体替换声明块,`style.length` / `style.item(i)` 枚举声明块,
96
+ 未设置的属性返回 `""`。
97
+
98
+ `el.dataset` 是元素 `data-*` 属性之上的实时 `DOMStringMap`:
99
+ `dataset.fooBar` 读取 `data-foo-bar`,赋值写入属性,`delete` 移除属性,
100
+ `Object.keys(dataset)` / `for...in` 只列出 data 键。
101
+
102
+ `getComputedStyle(el, pseudoElt)` 从**层叠结果**读取——UA 样式表、作者规则、
103
+ 继承和内联声明——而不是回显内联块,因此写在样式表里的规则同样可见。它支持
104
+ `getPropertyValue`、`getPropertyPriority` 与命名属性(`cs.width`、
105
+ `cs.backgroundColor`、`cs["--accent"]`)。传入 `"::before"` 或 `"::after"`
106
+ 可读取生成内容盒;其他伪元素返回空值。每次读取都会先结算待处理的重新样式,
107
+ 因此早先创建的句柄在后续变更后仍然有效。
108
+
109
+ `getBoundingClientRect()` 在 `x` / `y` / `width` / `height` 之外还带有旧式的
110
+ `top` / `right` / `bottom` / `left` 成员;`document.title = …` 写入文档的
111
+ `<title>`(页面没有该元素时会创建它以及 `<head>`)。
112
+
77
113
  变更操作会把文档标记为脏;引擎会延迟地(在下一次读取或下一帧之前)重新计算
78
114
  样式并重新布局。`inner = …`/`innerHTML = …` **不会**执行脚本。元素事件支持
79
- 捕获与冒泡阶段、`stopPropagation`、`once`,并向上冒泡到 `document`/`window`。
80
- 旧式 `win.on(type, fn)` 负载保留其**字符串** `e.target`(`div#id.class`);
81
- 元素 `Event.target` 是一个句柄,其描述符与同一字符串匹配。
115
+ 捕获与冒泡阶段、`stopPropagation`、`once`、`passive` 与 `signal`(`AbortController`
116
+ 的信号会在 abort 时移除监听器),并向上冒泡到 `document`/`window`。
117
+ `dispatchEvent` 派发同一个事件对象,并填充 `target`/`currentTarget`/`eventPhase`/
118
+ `composedPath()`。旧式 `win.on(type, fn)` 负载保留其**字符串** `e.target`
119
+ (`div#id.class`);元素 `Event.target` 是一个句柄,其描述符与同一字符串匹配。
120
+
121
+ `element.focus()` 会把元素滚动到可见区域(传入 `{ preventScroll: true }` 则保持
122
+ 当前页面偏移);`document.hasFocus()` 报告由 `window.focus()` / `window.blur()`
123
+ 切换的窗口焦点状态。
82
124
 
83
125
  ### 节点生命周期
84
126
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "xbintsc",
3
- "version": "0.5.36",
3
+ "version": "0.5.37",
4
4
  "description": "xbintsc - a TypeScript binary compiler (TypeScript -> LLVM IR -> native binary)",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -72,12 +72,12 @@
72
72
  "vitest": "^2.1.8"
73
73
  },
74
74
  "optionalDependencies": {
75
- "@xbintsc/linux-x64": "0.5.36",
76
- "@xbintsc/linux-arm64": "0.5.36",
77
- "@xbintsc/darwin-x64": "0.5.36",
78
- "@xbintsc/darwin-arm64": "0.5.36",
79
- "@xbintsc/win32-x64": "0.5.36",
80
- "@xbintsc/win32-arm64": "0.5.36"
75
+ "@xbintsc/linux-x64": "0.5.37",
76
+ "@xbintsc/linux-arm64": "0.5.37",
77
+ "@xbintsc/darwin-x64": "0.5.37",
78
+ "@xbintsc/darwin-arm64": "0.5.37",
79
+ "@xbintsc/win32-x64": "0.5.37",
80
+ "@xbintsc/win32-arm64": "0.5.37"
81
81
  },
82
82
  "engines": {
83
83
  "node": ">=22"
@@ -29,7 +29,6 @@ namespace {
29
29
  xt_value g_navigator = XT_UNDEFINED;
30
30
  xt_value g_local_storage = XT_UNDEFINED;
31
31
  xt_value g_location = XT_UNDEFINED;
32
- xt_value g_media_list = XT_UNDEFINED;
33
32
  xt_value g_document_global = XT_UNDEFINED;
34
33
  /** Type name -> the constructor value handed out for it (built on first use). */
35
34
  std::unordered_map<std::string, xt_value> g_types;
@@ -156,35 +155,28 @@ xt_value win_location_get(xt_value self, xt_value env, int32_t argc, xt_value *a
156
155
  return g_location;
157
156
  }
158
157
 
159
- xt_value media_list_build(const std::string &query, bool matches);
160
-
161
- /** `matchMedia(query)` -> a MediaQueryList-shaped object evaluated against the
162
- * current window's viewport, using the same evaluator as `@media` rules. The
163
- * list is a snapshot (there is no live change event), which matches the
164
- * engine's restyle-on-resize model. */
158
+ /** `matchMedia(query)` -> a live `MediaQueryList` evaluated against the current
159
+ * viewport with the same evaluator as `@media` rules. The list is cached per
160
+ * window and query, and `change` listeners fire when a resize flips it. */
165
161
  xt_value win_match_media(xt_value self, xt_value env, int32_t argc, xt_value *argv) {
166
162
  (void)env;
167
163
  std::string query;
168
164
  if (argc >= 1) query = cstr(xt_arg(argc, argv, 0));
169
165
  XtGuiWindow *win = xt_gui_window_from_this(self);
170
- if (win == NULL) {
171
- xt_value handle = xt_gui_ensure_window();
172
- win = xt_gui_window_from_this(handle);
173
- }
174
- float width = 0.0f;
175
- float height = 0.0f;
176
- if (win != NULL) xt_gui_window_viewport(win, &width, &height);
177
- return media_list_build(query, xtgui::xt_css_media_matches(query, width, height));
166
+ if (win == NULL) win = xt_gui_window_from_this(xt_gui_ensure_window());
167
+ if (win == NULL) return xt_object_new();
168
+ return xt_browser_media_list(win->object, query);
178
169
  }
179
170
 
180
- /** `getComputedStyle(el)`: the engine exposes the element's style handle, which
181
- * answers `getPropertyValue` for everything the page set. */
171
+ /** `getComputedStyle(el, pseudoElt)`: a live view of the *cascaded* style, so
172
+ * stylesheet rules (not only the inline `style` attribute) are visible. The
173
+ * optional pseudo-element selector ("::before"/"::after") resolves against the
174
+ * generated-content box; any other pseudo-element yields empty values, like a
175
+ * browser does for one the engine did not generate. */
182
176
  xt_value win_get_computed_style(xt_value self, xt_value env, int32_t argc, xt_value *argv) {
183
177
  (void)self;
184
178
  (void)env;
185
- xt_value element = xt_arg(argc, argv, 0);
186
- xt_value style = xt_get(element, xt_string_from_cstr("style"));
187
- return XT_IS_OBJECT(style) ? style : xt_object_new();
179
+ return domapi::computed_style_handle(xt_arg(argc, argv, 0), xt_arg(argc, argv, 1));
188
180
  }
189
181
 
190
182
  xt_value win_alert(xt_value self, xt_value env, int32_t argc, xt_value *argv) {
@@ -196,19 +188,13 @@ xt_value win_alert(xt_value self, xt_value env, int32_t argc, xt_value *argv) {
196
188
  }
197
189
 
198
190
  xt_value win_confirm(xt_value self, xt_value env, int32_t argc, xt_value *argv) {
199
- (void)self;
200
191
  (void)env;
201
- (void)argc;
202
- (void)argv;
203
- return XT_FALSE;
192
+ return xt_browser_confirm(xt_gui_window_from_this(self), argc, argv);
204
193
  }
205
194
 
206
195
  xt_value win_prompt(xt_value self, xt_value env, int32_t argc, xt_value *argv) {
207
- (void)self;
208
196
  (void)env;
209
- (void)argc;
210
- (void)argv;
211
- return XT_UNDEFINED;
197
+ return xt_browser_prompt(xt_gui_window_from_this(self), argc, argv);
212
198
  }
213
199
 
214
200
  /* -- host objects --------------------------------------------------------- */
@@ -253,18 +239,6 @@ xt_value location_build(void) {
253
239
  return location;
254
240
  }
255
241
 
256
- xt_value media_list_build(const std::string &query, bool matches) {
257
- xt_value list = xt_object_new();
258
- set_prop(list, "media", xt_string_from_cstr(query.c_str()));
259
- set_prop(list, "matches", matches ? XT_TRUE : XT_FALSE);
260
- set_method(list, "addListener", (void *)noop);
261
- set_method(list, "removeListener", (void *)noop);
262
- set_method(list, "addEventListener", (void *)noop);
263
- set_method(list, "removeEventListener", (void *)noop);
264
- set_method(list, "dispatchEvent", (void *)noop);
265
- return list;
266
- }
267
-
268
242
  /* -- localStorage --------------------------------------------------------- */
269
243
 
270
244
  xt_value storage_get_item(xt_value self, xt_value env, int32_t argc, xt_value *argv) {
@@ -338,7 +312,12 @@ xt_value host_type_construct(xt_value self, xt_value env, int32_t argc, xt_value
338
312
  /** The prototype a DOM type name should inherit, so `el instanceof Element`
339
313
  * holds for every element handle the engine hands out. */
340
314
  xt_value type_proto(const std::string &name) {
341
- if (name == "Event" || name == "CustomEvent" || name == "ProgressEvent") {
315
+ if (name == "AbortController") return domapi::abort_controller_proto();
316
+ if (name == "AbortSignal") return domapi::abort_signal_proto();
317
+ if (name == "Event" || name == "CustomEvent" || name == "ProgressEvent" ||
318
+ name == "UIEvent" || name == "MouseEvent" || name == "PointerEvent" ||
319
+ name == "WheelEvent" || name == "KeyboardEvent" || name == "FocusEvent" ||
320
+ name == "InputEvent" || name == "SubmitEvent") {
342
321
  return domapi::event_proto();
343
322
  }
344
323
  if (name == "AudioContext" || name == "webkitAudioContext") return xt_gui_audio_proto();
@@ -357,6 +336,7 @@ xt_value type_proto(const std::string &name) {
357
336
  if (name == "WebGLQuery") return domapi::webgl_object_proto("query");
358
337
  if (name == "WebGLSync") return domapi::webgl_object_proto("sync");
359
338
  if (name == "WebGLTransformFeedback") return domapi::webgl_object_proto("transformfeedback");
339
+ if (name == "Path2D") return domapi::path2d_proto();
360
340
  if (name == "ImageBitmap") {
361
341
  /* A bitmap is a plain immutable object, not a DOM node: a node proto would
362
342
  * expose the element width/height accessors and answer 0. */
@@ -394,9 +374,15 @@ xt_value host_type_construct(xt_value self, xt_value env, int32_t argc, xt_value
394
374
  if (name == "AudioContext" || name == "webkitAudioContext") {
395
375
  return xt_gui_audio_create(argc, argv);
396
376
  }
397
- if (name == "Event" || name == "CustomEvent" || name == "ProgressEvent") {
377
+ if (name == "AbortController") return domapi::abort_controller_new();
378
+ if (name == "AbortSignal") return domapi::abort_signal_new(false);
379
+ if (name == "Event" || name == "CustomEvent" || name == "ProgressEvent" ||
380
+ name == "UIEvent" || name == "MouseEvent" || name == "PointerEvent" ||
381
+ name == "WheelEvent" || name == "KeyboardEvent" || name == "FocusEvent" ||
382
+ name == "InputEvent" || name == "SubmitEvent") {
398
383
  return domapi::event_construct(xt_arg(argc, argv, 0), xt_arg(argc, argv, 1));
399
384
  }
385
+ if (name == "Path2D") return domapi::path2d_create(xt_arg(argc, argv, 0));
400
386
  xt_value target = xt_object_new_with_proto(type_proto(name));
401
387
  set_prop(target, "constructor", type_value(name));
402
388
  return target;
@@ -438,10 +424,6 @@ void ensure_singletons(void) {
438
424
  g_location = location_build();
439
425
  xt_gc_add_root(&g_location);
440
426
  }
441
- if (!XT_IS_OBJECT(g_media_list)) {
442
- g_media_list = media_list_build("", false);
443
- xt_gc_add_root(&g_media_list);
444
- }
445
427
  }
446
428
 
447
429
  } // namespace
@@ -474,9 +456,8 @@ void xt_browser_install_window(xt_value proto) {
474
456
  set_method(proto, "confirm", (void *)win_confirm);
475
457
  set_method(proto, "prompt", (void *)win_prompt);
476
458
  set_method(proto, "escape", (void *)xt_browser_escape);
477
- set_method(proto, "focus", (void *)noop);
478
- set_method(proto, "blur", (void *)noop);
479
- set_method(proto, "scrollTo", (void *)noop);
459
+ /* Page scroll and window focus/blur live in `browser_navigation.cpp`. */
460
+ xt_browser_install_window_navigation(proto);
480
461
  }
481
462
 
482
463
  /** `window` / `self`. */
@@ -581,3 +562,7 @@ xt_value xt_browser_alert(int32_t argc, xt_value *argv) {
581
562
  xt_value xt_browser_prompt(int32_t argc, xt_value *argv) {
582
563
  return win_prompt(XT_UNDEFINED, XT_UNDEFINED, argc, argv);
583
564
  }
565
+
566
+ xt_value xt_browser_confirm_global(int32_t argc, xt_value *argv) {
567
+ return win_confirm(XT_UNDEFINED, XT_UNDEFINED, argc, argv);
568
+ }