xbintsc 0.5.31 → 0.5.36

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 (286) hide show
  1. package/README.md +25 -16
  2. package/README.zh-CN.md +10 -8
  3. package/dist/scripts/check-version.d.ts +27 -0
  4. package/dist/scripts/check-version.js +71 -0
  5. package/dist/scripts/check-version.js.map +1 -0
  6. package/dist/scripts/package-platform.d.ts +58 -0
  7. package/dist/scripts/package-platform.js +142 -0
  8. package/dist/scripts/package-platform.js.map +1 -0
  9. package/dist/scripts/verify-platform-package.d.ts +34 -0
  10. package/dist/scripts/verify-platform-package.js +161 -0
  11. package/dist/scripts/verify-platform-package.js.map +1 -0
  12. package/dist/src/binder/references.js +14 -1
  13. package/dist/src/binder/references.js.map +1 -1
  14. package/dist/src/cli/help.d.ts +1 -1
  15. package/dist/src/cli/main.js +3 -1
  16. package/dist/src/cli/main.js.map +1 -1
  17. package/dist/src/codegen/generator/module.d.ts +1 -0
  18. package/dist/src/codegen/generator/module.js +54 -0
  19. package/dist/src/codegen/generator/module.js.map +1 -1
  20. package/dist/src/codegen/generator/statements/variables.js +6 -4
  21. package/dist/src/codegen/generator/statements/variables.js.map +1 -1
  22. package/dist/src/driver/runtime-lib.d.ts +29 -4
  23. package/dist/src/driver/runtime-lib.js +68 -10
  24. package/dist/src/driver/runtime-lib.js.map +1 -1
  25. package/dist/src/driver/version.d.ts +1 -1
  26. package/dist/src/driver/version.js +1 -1
  27. package/dist/src/extensions/gui/css.d.ts +26 -0
  28. package/dist/src/extensions/gui/css.js +100 -0
  29. package/dist/src/extensions/gui/css.js.map +1 -0
  30. package/dist/src/extensions/gui/html.js +9 -2
  31. package/dist/src/extensions/gui/html.js.map +1 -1
  32. package/dist/src/extensions/gui/index.js +22 -7
  33. package/dist/src/extensions/gui/index.js.map +1 -1
  34. package/dist/src/extensions/gui/three.d.ts +16 -0
  35. package/dist/src/extensions/gui/three.js +22 -0
  36. package/dist/src/extensions/gui/three.js.map +1 -0
  37. package/dist/src/extensions/media/index.d.ts +6 -5
  38. package/dist/src/extensions/media/index.js +15 -16
  39. package/dist/src/extensions/media/index.js.map +1 -1
  40. package/dist/src/parser/context.d.ts +41 -0
  41. package/dist/src/parser/context.js +89 -4
  42. package/dist/src/parser/context.js.map +1 -1
  43. package/dist/src/parser/literals.js +6 -12
  44. package/dist/src/parser/literals.js.map +1 -1
  45. package/dist/tests/driver/platform-package.test.d.ts +1 -0
  46. package/dist/tests/driver/platform-package.test.js +49 -0
  47. package/dist/tests/driver/platform-package.test.js.map +1 -0
  48. package/dist/tests/driver/runtime-lib.test.js +51 -4
  49. package/dist/tests/driver/runtime-lib.test.js.map +1 -1
  50. package/dist/tests/e2e/compile-builtins.test.js +24 -0
  51. package/dist/tests/e2e/compile-builtins.test.js.map +1 -1
  52. package/dist/tests/e2e/compile-objects-classes.test.js +23 -0
  53. package/dist/tests/e2e/compile-objects-classes.test.js.map +1 -1
  54. package/dist/tests/e2e/gui-audio.test.d.ts +14 -0
  55. package/dist/tests/e2e/gui-audio.test.js +287 -0
  56. package/dist/tests/e2e/gui-audio.test.js.map +1 -0
  57. package/dist/tests/e2e/gui-browser.test.js +1 -1
  58. package/dist/tests/e2e/gui-browser.test.js.map +1 -1
  59. package/dist/tests/e2e/gui-effects.test.d.ts +6 -0
  60. package/dist/tests/e2e/gui-effects.test.js +262 -0
  61. package/dist/tests/e2e/gui-effects.test.js.map +1 -0
  62. package/dist/tests/e2e/gui-example-webgl.test.d.ts +10 -0
  63. package/dist/tests/e2e/gui-example-webgl.test.js +75 -0
  64. package/dist/tests/e2e/gui-example-webgl.test.js.map +1 -0
  65. package/dist/tests/e2e/gui-example-webgl2.test.d.ts +8 -0
  66. package/dist/tests/e2e/gui-example-webgl2.test.js +72 -0
  67. package/dist/tests/e2e/gui-example-webgl2.test.js.map +1 -0
  68. package/dist/tests/e2e/gui-helpers.d.ts +7 -0
  69. package/dist/tests/e2e/gui-helpers.js +1 -0
  70. package/dist/tests/e2e/gui-helpers.js.map +1 -1
  71. package/dist/tests/e2e/gui-layout.test.js +48 -0
  72. package/dist/tests/e2e/gui-layout.test.js.map +1 -1
  73. package/dist/tests/e2e/gui-media.test.js +83 -4
  74. package/dist/tests/e2e/gui-media.test.js.map +1 -1
  75. package/dist/tests/e2e/gui-style.test.js +55 -0
  76. package/dist/tests/e2e/gui-style.test.js.map +1 -1
  77. package/dist/tests/e2e/gui-webgl.test.d.ts +11 -0
  78. package/dist/tests/e2e/gui-webgl.test.js +437 -0
  79. package/dist/tests/e2e/gui-webgl.test.js.map +1 -0
  80. package/dist/tests/e2e/gui-webgl2-3d.test.d.ts +4 -0
  81. package/dist/tests/e2e/gui-webgl2-3d.test.js +264 -0
  82. package/dist/tests/e2e/gui-webgl2-3d.test.js.map +1 -0
  83. package/dist/tests/e2e/gui-webgl2-blit.test.d.ts +10 -0
  84. package/dist/tests/e2e/gui-webgl2-blit.test.js +262 -0
  85. package/dist/tests/e2e/gui-webgl2-blit.test.js.map +1 -0
  86. package/dist/tests/e2e/gui-webgl2-formats.test.d.ts +6 -0
  87. package/dist/tests/e2e/gui-webgl2-formats.test.js +395 -0
  88. package/dist/tests/e2e/gui-webgl2-formats.test.js.map +1 -0
  89. package/dist/tests/e2e/gui-webgl2-mrt.test.d.ts +4 -0
  90. package/dist/tests/e2e/gui-webgl2-mrt.test.js +105 -0
  91. package/dist/tests/e2e/gui-webgl2-mrt.test.js.map +1 -0
  92. package/dist/tests/e2e/gui-webgl2-ubo.test.d.ts +8 -0
  93. package/dist/tests/e2e/gui-webgl2-ubo.test.js +112 -0
  94. package/dist/tests/e2e/gui-webgl2-ubo.test.js.map +1 -0
  95. package/dist/tests/e2e/gui-webgl2.test.d.ts +10 -0
  96. package/dist/tests/e2e/gui-webgl2.test.js +282 -0
  97. package/dist/tests/e2e/gui-webgl2.test.js.map +1 -0
  98. package/dist/tests/extensions/gui-index.test.js +1 -0
  99. package/dist/tests/extensions/gui-index.test.js.map +1 -1
  100. package/dist/tests/extensions/gui.test.js +41 -0
  101. package/dist/tests/extensions/gui.test.js.map +1 -1
  102. package/dist/tests/extensions/media.test.js +8 -3
  103. package/dist/tests/extensions/media.test.js.map +1 -1
  104. package/dist/tests/parser/expressions.test.js +30 -0
  105. package/dist/tests/parser/expressions.test.js.map +1 -1
  106. package/dist/tests/platform-tarball.test.d.ts +1 -0
  107. package/dist/tests/platform-tarball.test.js +111 -0
  108. package/dist/tests/platform-tarball.test.js.map +1 -0
  109. package/dist/tests/version.test.js +6 -0
  110. package/dist/tests/version.test.js.map +1 -1
  111. package/doc/ai/contributing.md +11 -0
  112. package/doc/ai/extensions.md +35 -12
  113. package/doc/ai/troubleshooting.md +17 -11
  114. package/doc/ai/zh-CN/contributing.md +9 -0
  115. package/doc/ai/zh-CN/extensions.md +30 -10
  116. package/doc/ai/zh-CN/troubleshooting.md +14 -8
  117. package/doc/gui/api.md +63 -14
  118. package/doc/gui/build.md +79 -16
  119. package/doc/gui/canvas.md +11 -3
  120. package/doc/gui/dom.md +35 -17
  121. package/doc/gui/known-issues.md +57 -24
  122. package/doc/gui/layout.md +18 -0
  123. package/doc/gui/media.md +21 -13
  124. package/doc/gui/rendering.md +50 -3
  125. package/doc/gui/webgl.md +315 -0
  126. package/doc/gui.md +113 -18
  127. package/doc/npm-package.md +59 -29
  128. package/doc/requirements.md +24 -11
  129. package/doc/zh-CN/gui/api.md +53 -11
  130. package/doc/zh-CN/gui/build.md +70 -16
  131. package/doc/zh-CN/gui/canvas.md +9 -2
  132. package/doc/zh-CN/gui/dom.md +22 -7
  133. package/doc/zh-CN/gui/known-issues.md +45 -19
  134. package/doc/zh-CN/gui/layout.md +14 -0
  135. package/doc/zh-CN/gui/media.md +18 -11
  136. package/doc/zh-CN/gui/rendering.md +41 -4
  137. package/doc/zh-CN/gui/webgl.md +121 -0
  138. package/doc/zh-CN/gui.md +91 -15
  139. package/doc/zh-CN/npm-package.md +48 -24
  140. package/doc/zh-CN/requirements.md +16 -7
  141. package/llms.txt +2 -0
  142. package/package.json +17 -1
  143. package/runtime/ext_gui/audio.cpp +365 -153
  144. package/runtime/ext_gui/audio_buffer.cpp +166 -0
  145. package/runtime/ext_gui/audio_buffer.h +37 -0
  146. package/runtime/ext_gui/audio_decode.cpp +219 -0
  147. package/runtime/ext_gui/audio_decode.h +39 -0
  148. package/runtime/ext_gui/audio_device.cpp +87 -0
  149. package/runtime/ext_gui/audio_effects.cpp +163 -0
  150. package/runtime/ext_gui/audio_effects.h +25 -0
  151. package/runtime/ext_gui/audio_internal.h +30 -0
  152. package/runtime/ext_gui/audio_mixer.cpp +494 -0
  153. package/runtime/ext_gui/audio_mixer.h +238 -0
  154. package/runtime/ext_gui/audio_param.cpp +104 -0
  155. package/runtime/ext_gui/audio_param_object.cpp +155 -0
  156. package/runtime/ext_gui/browser.cpp +50 -14
  157. package/runtime/ext_gui/canvas.cpp +3 -18
  158. package/runtime/ext_gui/canvas_internal.h +10 -0
  159. package/runtime/ext_gui/canvas_raster.cpp +60 -0
  160. package/runtime/ext_gui/clip_values.cpp +246 -0
  161. package/runtime/ext_gui/css.cpp +94 -0
  162. package/runtime/ext_gui/css.h +22 -1
  163. package/runtime/ext_gui/css_match.cpp +3 -0
  164. package/runtime/ext_gui/css_selector.cpp +30 -3
  165. package/runtime/ext_gui/css_sheet.cpp +89 -45
  166. package/runtime/ext_gui/document.cpp +4 -1
  167. package/runtime/ext_gui/document.h +2 -0
  168. package/runtime/ext_gui/dom.h +16 -0
  169. package/runtime/ext_gui/dom_api.cpp +1 -0
  170. package/runtime/ext_gui/dom_api_canvas.cpp +17 -80
  171. package/runtime/ext_gui/dom_api_canvas_extra.cpp +130 -1
  172. package/runtime/ext_gui/dom_api_canvas_pending.cpp +41 -0
  173. package/runtime/ext_gui/dom_api_internal.h +275 -0
  174. package/runtime/ext_gui/dom_api_webgl.cpp +383 -0
  175. package/runtime/ext_gui/dom_api_webgl2.cpp +549 -0
  176. package/runtime/ext_gui/dom_api_webgl2_draw.cpp +269 -0
  177. package/runtime/ext_gui/dom_api_webgl_buffer.cpp +216 -0
  178. package/runtime/ext_gui/dom_api_webgl_compressed.cpp +167 -0
  179. package/runtime/ext_gui/dom_api_webgl_framebuffer.cpp +160 -0
  180. package/runtime/ext_gui/dom_api_webgl_rt.h +30 -0
  181. package/runtime/ext_gui/dom_api_webgl_shader.cpp +443 -0
  182. package/runtime/ext_gui/dom_api_webgl_source.cpp +161 -0
  183. package/runtime/ext_gui/dom_api_webgl_state.cpp +590 -0
  184. package/runtime/ext_gui/dom_api_webgl_texture.cpp +485 -0
  185. package/runtime/ext_gui/dom_api_webgl_ubo.cpp +81 -0
  186. package/runtime/ext_gui/effect_values.cpp +312 -0
  187. package/runtime/ext_gui/gradient_values.cpp +476 -0
  188. package/runtime/ext_gui/grid_values.cpp +230 -0
  189. package/runtime/ext_gui/gui.cpp +44 -9
  190. package/runtime/ext_gui/gui_canvas.cpp +50 -2
  191. package/runtime/ext_gui/gui_engine.h +36 -4
  192. package/runtime/ext_gui/gui_events.cpp +4 -1
  193. package/runtime/ext_gui/gui_media.cpp +1 -1
  194. package/runtime/ext_gui/gui_webgl.cpp +83 -0
  195. package/runtime/ext_gui/image_bitmap.cpp +28 -0
  196. package/runtime/ext_gui/image_bitmap.h +26 -0
  197. package/runtime/ext_gui/image_bitmap_browser.cpp +136 -0
  198. package/runtime/ext_gui/layout.cpp +21 -0
  199. package/runtime/ext_gui/layout_flow.cpp +81 -4
  200. package/runtime/ext_gui/layout_flow_internal.h +31 -0
  201. package/runtime/ext_gui/layout_grid.cpp +374 -0
  202. package/runtime/ext_gui/media.cpp +32 -0
  203. package/runtime/ext_gui/media.h +14 -1
  204. package/runtime/ext_gui/media_api.h +25 -2
  205. package/runtime/ext_gui/media_query.cpp +211 -0
  206. package/runtime/ext_gui/paint.cpp +111 -77
  207. package/runtime/ext_gui/paint.h +33 -3
  208. package/runtime/ext_gui/paint_background.cpp +229 -0
  209. package/runtime/ext_gui/paint_clip.cpp +268 -0
  210. package/runtime/ext_gui/paint_effects.cpp +324 -0
  211. package/runtime/ext_gui/paint_internal.h +85 -0
  212. package/runtime/ext_gui/renderer.cpp +8 -31
  213. package/runtime/ext_gui/renderer_clip.cpp +196 -0
  214. package/runtime/ext_gui/renderer_image.cpp +13 -8
  215. package/runtime/ext_gui/renderer_internal.h +15 -8
  216. package/runtime/ext_gui/renderer_text.cpp +6 -31
  217. package/runtime/ext_gui/style.cpp +343 -20
  218. package/runtime/ext_gui/style.h +144 -3
  219. package/runtime/ext_gui/style_print.cpp +134 -1
  220. package/runtime/ext_gui/style_values.cpp +152 -126
  221. package/runtime/ext_gui/style_values_internal.h +35 -0
  222. package/runtime/ext_gui/webgl.h +600 -0
  223. package/runtime/ext_gui/webgl2_constants.h +274 -0
  224. package/runtime/ext_gui/webgl_blit.cpp +95 -0
  225. package/runtime/ext_gui/webgl_buffer.cpp +480 -0
  226. package/runtime/ext_gui/webgl_buffer_objects.cpp +270 -0
  227. package/runtime/ext_gui/webgl_clear.cpp +142 -0
  228. package/runtime/ext_gui/webgl_compressed.cpp +180 -0
  229. package/runtime/ext_gui/webgl_constants.h +317 -0
  230. package/runtime/ext_gui/webgl_context.cpp +551 -0
  231. package/runtime/ext_gui/webgl_draw.cpp +560 -0
  232. package/runtime/ext_gui/webgl_draw_buffers.cpp +75 -0
  233. package/runtime/ext_gui/webgl_glsl.h +165 -0
  234. package/runtime/ext_gui/webgl_glsl_compile.cpp +437 -0
  235. package/runtime/ext_gui/webgl_glsl_rewrite.cpp +367 -0
  236. package/runtime/ext_gui/webgl_glsl_scan.cpp +590 -0
  237. package/runtime/ext_gui/webgl_glsl_scan_block.cpp +126 -0
  238. package/runtime/ext_gui/webgl_glsl_tokens.h +28 -0
  239. package/runtime/ext_gui/webgl_msl.cpp +62 -0
  240. package/runtime/ext_gui/webgl_msl.h +33 -0
  241. package/runtime/ext_gui/webgl_pipeline.cpp +326 -0
  242. package/runtime/ext_gui/webgl_program_blocks.cpp +58 -0
  243. package/runtime/ext_gui/webgl_program_internal.h +24 -0
  244. package/runtime/ext_gui/webgl_shader.cpp +594 -0
  245. package/runtime/ext_gui/webgl_state.cpp +228 -0
  246. package/runtime/ext_gui/webgl_texture.cpp +420 -0
  247. package/runtime/ext_gui/webgl_texture_3d.cpp +215 -0
  248. package/runtime/ext_gui/webgl_uniform.cpp +167 -0
  249. package/runtime/ext_gui/webgl_uniform_block.h +31 -0
  250. package/runtime/ext_gui/webgl_webgl2.cpp +277 -0
  251. package/runtime/ext_media/decode_audio.cpp +308 -0
  252. package/runtime/ext_media/media.cpp +1 -1
  253. package/runtime/ext_media/media_bridge.c +11 -3
  254. package/runtime/rt.h +17 -0
  255. package/runtime/rt_internal.h +1 -0
  256. package/runtime/xt_array_buffer.c +30 -0
  257. package/runtime/xt_containers/access.inc +8 -0
  258. package/runtime/xt_regex/api.inc +2 -0
  259. package/runtime/xt_regex/matcher.inc +16 -3
  260. package/runtime/xt_regex/parser.inc +8 -2
  261. package/runtime/xt_regex/types.inc +3 -0
  262. package/runtime/xt_stdlib2/regexp-match.inc +60 -21
  263. package/runtime/xt_stdlib2/regexp.inc +1 -0
  264. package/runtime/xt_typed_array/elements.inc +13 -0
  265. package/runtime/xt_typed_array.c +1 -1
  266. package/scripts/build-gui.ts +90 -3
  267. package/scripts/build-media.ts +335 -62
  268. package/scripts/build-runtime.ts +21 -16
  269. package/scripts/check-version.ts +39 -9
  270. package/scripts/package-platform.ts +183 -0
  271. package/scripts/set-platform-version.ts +62 -0
  272. package/scripts/setup-vs-toolchain.ps1 +152 -0
  273. package/scripts/verify-platform-package.ts +182 -0
  274. package/src/binder/references.ts +13 -1
  275. package/src/cli/main.ts +5 -1
  276. package/src/codegen/generator/module.ts +50 -0
  277. package/src/codegen/generator/statements/variables.ts +6 -3
  278. package/src/driver/runtime-lib.ts +73 -9
  279. package/src/driver/version.ts +1 -1
  280. package/src/extensions/gui/css.ts +113 -0
  281. package/src/extensions/gui/html.ts +9 -2
  282. package/src/extensions/gui/index.ts +22 -7
  283. package/src/extensions/gui/three.ts +25 -0
  284. package/src/extensions/media/index.ts +16 -18
  285. package/src/parser/context.ts +99 -4
  286. package/src/parser/literals.ts +6 -12
@@ -19,6 +19,7 @@
19
19
  #include "paint.h"
20
20
 
21
21
  #include <stdio.h>
22
+ #include <stdlib.h>
22
23
  #include <string.h>
23
24
 
24
25
  namespace guidev {
@@ -98,11 +99,32 @@ static void xt_gui_gc_scan_roots(void) {
98
99
 
99
100
  /* -- initialisation ------------------------------------------------------- */
100
101
 
101
- static int ensure_init(void) { if (g_sdl_initialized) return 1;
102
- xt_gc_register_root_provider(xt_gui_gc_scan_roots);
102
+ /**
103
+ * Exit status for "this host cannot run a GUI program": no video driver, or no
104
+ * GPU device the SDL_GPU backends accept. It is deliberately distinct from a
105
+ * failed program so a CI script (or a user) can skip the run instead of
106
+ * reporting a crash. 77 is the autotools "skip" convention; `ci.yml` treats it
107
+ * that way for the provisional macOS job.
108
+ */
109
+ const int kGuiUnavailableExit = 77;
110
+
111
+ /** Report a window that could not be created as a JavaScript `Error`. Returning
112
+ * `undefined` used to surface one line later as `Cannot read properties of
113
+ * undefined (reading 'on')`, which hid the real diagnostic. */
114
+ static void throw_gui_error(const char *message) {
115
+ xt_value text = xt_string_from_cstr(message);
116
+ xt_throw(xt_error_ctor_by_name(xt_string_from_cstr("Error"), 1, &text));
117
+ }
118
+
119
+ static void ensure_init(void) {
120
+ if (g_sdl_initialized) return;
103
121
  if (!SDL_Init(SDL_INIT_VIDEO)) {
104
122
  fprintf(stderr, "xt_gui: SDL_Init failed: %s\n", SDL_GetError());
105
- return 0;
123
+ fprintf(stderr,
124
+ "xt_gui: the GUI extension needs a display (or a virtual one such as Xvfb); "
125
+ "exiting with status %d.\n",
126
+ kGuiUnavailableExit);
127
+ exit(kGuiUnavailableExit);
106
128
  }
107
129
  /* Accept every shader format so the same code runs on Metal, Vulkan and
108
130
  * D3D12; the backend only needs the one it supports. The engine ships MSL,
@@ -113,11 +135,17 @@ static int ensure_init(void) { if (g_sdl_initialized) return 1;
113
135
  g_device = SDL_CreateGPUDevice(formats, false, NULL);
114
136
  if (g_device == NULL) {
115
137
  fprintf(stderr, "xt_gui: SDL_CreateGPUDevice failed: %s\n", SDL_GetError());
138
+ fprintf(stderr,
139
+ "xt_gui: the GUI extension needs a Metal (macOS), Vulkan or D3D12 device; "
140
+ "this host does not provide a usable one. Exiting with status %d.\n",
141
+ kGuiUnavailableExit);
116
142
  SDL_Quit();
117
- return 0;
143
+ exit(kGuiUnavailableExit);
118
144
  }
145
+ /* Registered only once a window can exist: a failed init has nothing to
146
+ * mark, and a caller that cannot retry never needs a second provider. */
147
+ xt_gc_register_root_provider(xt_gui_gc_scan_roots);
119
148
  g_sdl_initialized = 1;
120
- return 1;
121
149
  }
122
150
 
123
151
  /* -- window lookup -------------------------------------------------------- */
@@ -254,15 +282,18 @@ xt_value xt_gui_ensure_window(void) {
254
282
  for (int i = g_window_count - 1; i >= 0; i--) {
255
283
  if (g_windows[i].open) return g_windows[i].object;
256
284
  }
257
- if (!ensure_init()) return XT_UNDEFINED;
285
+ ensure_init();
258
286
  XtGuiWindow *record = create_window_record("xbintsc gui", 800, 600, true);
259
- if (record == NULL) return XT_UNDEFINED;
287
+ if (record == NULL) {
288
+ throw_gui_error("xbintsc gui: could not open the implicit window (see the diagnostic above)");
289
+ return XT_UNDEFINED;
290
+ }
260
291
  g_implicit_window = 1;
261
292
  return record->object;
262
293
  }
263
294
 
264
295
  extern "C" xt_value xt_gui_create_window(int32_t argc, xt_value *argv) {
265
- if (!ensure_init()) return XT_UNDEFINED;
296
+ ensure_init();
266
297
 
267
298
  const char *title = "xbintsc gui";
268
299
  int width = 800;
@@ -304,7 +335,11 @@ extern "C" xt_value xt_gui_create_window(int32_t argc, xt_value *argv) {
304
335
  }
305
336
 
306
337
  XtGuiWindow *record = create_window_record(title, width, height, high_density);
307
- return record != NULL ? record->object : XT_UNDEFINED;
338
+ if (record == NULL) {
339
+ throw_gui_error("xbintsc gui: could not open a window (see the diagnostic above)");
340
+ return XT_UNDEFINED;
341
+ }
342
+ return record->object;
308
343
  }
309
344
 
310
345
  /* -- run() / quit() ------------------------------------------------------- */
@@ -65,11 +65,17 @@ xtgui::Canvas2D *xt_gui_canvas_get(XtGuiWindow *win, const xtgui::Node *node) {
65
65
  auto canvas = std::make_unique<xtgui::Canvas2D>(width, height);
66
66
  xtgui::Canvas2D *raw = canvas.get();
67
67
  win->canvases[node] = std::move(canvas);
68
+ /* A context obtained after the first frame must reach the display list
69
+ * immediately, so `paintList`/the next paint sees its size and texture id. */
70
+ if (win->document != nullptr) {
71
+ win->document->setCanvasInfo(node, (float)width, (float)height, raw->id());
72
+ }
68
73
  return raw;
69
74
  }
70
75
 
71
76
  void xt_gui_canvas_forget(XtGuiWindow *win, const xtgui::Node *node) {
72
77
  if (win == nullptr || node == nullptr) return;
78
+ xt_gui_webgl_forget(win, node);
73
79
  auto found = win->canvases.find(node);
74
80
  if (found == win->canvases.end()) return;
75
81
  releaseCanvasTexture(found->second.get());
@@ -81,8 +87,46 @@ void xt_gui_canvas_sync(XtGuiWindow *win) {
81
87
  std::vector<const xtgui::Node *> nodes;
82
88
  collectCanvases(win->document->root(), &nodes);
83
89
  for (const xtgui::Node *node : nodes) {
84
- xtgui::Canvas2D *canvas = xt_gui_canvas_get(win, node);
85
- if (canvas == nullptr) continue;
90
+ if (xtgui::WebGLContext *webgl = xt_gui_webgl_find(win, node)) {
91
+ win->document->setCanvasInfo(node, (float)webgl->width(), (float)webgl->height(),
92
+ webgl->id());
93
+ continue;
94
+ }
95
+ xtgui::Canvas2D *canvas = xt_gui_canvas_find(win, node);
96
+ if (canvas != nullptr) {
97
+ win->document->setCanvasInfo(node, (float)canvas->width(), (float)canvas->height(),
98
+ canvas->id());
99
+ continue;
100
+ }
101
+ /* No context yet: the attributes still define the intrinsic box, but there
102
+ * is nothing to paint (a canvas with no context is transparent). */
103
+ int width = canvasAttributeSize(node, "width", 300);
104
+ int height = canvasAttributeSize(node, "height", 150);
105
+ win->document->setCanvasInfo(node, (float)width, (float)height, 0);
106
+ }
107
+ }
108
+
109
+ void xt_gui_canvas_bind_pending(XtGuiWindow *win, const xtgui::Node *node, xt_value handle) {
110
+ if (win == nullptr || node == nullptr) return;
111
+ xt_value pending = xt_object_get_cstr(handle, "__xt_gui_pending_canvas");
112
+ if (!XT_IS_NUMBER(pending)) return;
113
+ uint64_t id = (uint64_t)xt_to_number(pending);
114
+ xt_object_set(handle, xt_string_from_cstr("__xt_gui_pending_canvas"), XT_UNDEFINED);
115
+ auto found = win->pending_canvases.find(id);
116
+ if (found == win->pending_canvases.end()) return;
117
+ if (!isCanvasElement(node)) {
118
+ win->pending_canvases.erase(found);
119
+ return;
120
+ }
121
+ xtgui::Canvas2D *canvas = found->second.get();
122
+ /* The parsed element's width/height attributes win over the default 300x150
123
+ * a pre-load context started with. */
124
+ int width = canvasAttributeSize(node, "width", canvas->width());
125
+ int height = canvasAttributeSize(node, "height", canvas->height());
126
+ if (width != canvas->width() || height != canvas->height()) canvas->resize(width, height);
127
+ win->canvases[node] = std::move(found->second);
128
+ win->pending_canvases.erase(found);
129
+ if (win->document != nullptr) {
86
130
  win->document->setCanvasInfo(node, (float)canvas->width(), (float)canvas->height(),
87
131
  canvas->id());
88
132
  }
@@ -90,6 +134,10 @@ void xt_gui_canvas_sync(XtGuiWindow *win) {
90
134
 
91
135
  void xt_gui_canvas_shutdown(XtGuiWindow *win) {
92
136
  if (win == nullptr) return;
137
+ xt_gui_webgl_shutdown(win);
93
138
  for (auto &entry : win->canvases) releaseCanvasTexture(entry.second.get());
94
139
  win->canvases.clear();
140
+ /* `pending_canvases` survives `loadHTML`: a module that asked for a context
141
+ * before the page existed still needs its bitmap when the parsed element
142
+ * binds. They are released with the window record. */
95
143
  }
@@ -45,6 +45,9 @@ xt_value xt_browser_local_storage(int32_t argc, xt_value *argv);
45
45
  xt_value xt_browser_missing(int32_t argc, xt_value *argv);
46
46
  /** A DOM type name (`Image`, `HTMLCanvasElement`, …) as a constructor value. */
47
47
  xt_value xt_browser_type(int32_t argc, xt_value *argv);
48
+ /** `createImageBitmap(source)`, exposed as a global *value*: returns the
49
+ * closure the compiler stores for the bare identifier. */
50
+ xt_value xt_browser_create_image_bitmap(int32_t argc, xt_value *argv);
48
51
  xt_value xt_browser_request_animation_frame(int32_t argc, xt_value *argv);
49
52
  xt_value xt_browser_cancel_animation_frame(int32_t argc, xt_value *argv);
50
53
  xt_value xt_browser_match_media(int32_t argc, xt_value *argv);
@@ -63,11 +66,13 @@ void xt_browser_schedule_frame(void);
63
66
  /** Whether `run()` currently owns the frame loop (the pump then stays idle). */
64
67
  int xt_gui_run_active(void);
65
68
 
66
- /* Web Audio objects (`audio.cpp`). */
67
- /** `AudioContext.prototype` — also the `instanceof AudioContext` target. */
69
+ /* Web Audio objects (`audio.cpp`).
70
+ * `AudioContext.prototype` — also the `instanceof AudioContext` target. */
68
71
  xt_value xt_gui_audio_proto(void);
69
- /** `new AudioContext(...)`: it has no mixer backend yet, so the graph records
70
- * its shape and parameters but nothing is rendered to the sound device. */
72
+ /** `new AudioContext(...)`: builds a real graph (mixer in `audio_mixer.cpp`)
73
+ * and decodes through `audio_decode.cpp` + the optional media backend. The
74
+ * device opens on the first `start()`/`resume()`; a headless host stays silent
75
+ * while the graph still renders. */
71
76
  xt_value xt_gui_audio_create(int32_t argc, xt_value *argv);
72
77
 
73
78
  #ifdef __cplusplus
@@ -84,6 +89,7 @@ xt_value xt_gui_audio_create(int32_t argc, xt_value *argv);
84
89
  #include "document.h"
85
90
  #include "media.h"
86
91
  #include "renderer.h"
92
+ #include "webgl.h"
87
93
 
88
94
  /** One element-level event listener registered through `addEventListener`. */
89
95
  struct XtGuiNodeListener {
@@ -162,6 +168,14 @@ struct XtGuiWindow {
162
168
  * lazily by `getContext("2d")` (or by `xt_gui_canvas_sync` for a live canvas),
163
169
  * released when the node is forgotten or the document reloads. */
164
170
  std::unordered_map<const xtgui::Node *, std::unique_ptr<xtgui::Canvas2D>> canvases;
171
+ /** One WebGL context per `<canvas>` that asked for `getContext("webgl")`.
172
+ * A canvas uses either this map or `canvases`, never both. */
173
+ std::unordered_map<const xtgui::Node *, std::unique_ptr<xtgui::WebGLContext>> webgl_canvases;
174
+ /** Canvases whose element handle was created before `loadHTML` (a module
175
+ * that calls `getElementById("scene").getContext("2d")` while it
176
+ * initialises). Keyed by canvas id; moved into `canvases` when the pending
177
+ * handle binds to the parsed element. */
178
+ std::unordered_map<uint64_t, std::unique_ptr<xtgui::Canvas2D>> pending_canvases;
165
179
  /** The batch currently running: its callbacks have been swapped out of
166
180
  * `animation_frames`, and a collection triggered by one of them must still
167
181
  * see the rest. Kept on the record (not a local) so the GC root provider can
@@ -243,12 +257,30 @@ xtgui::Canvas2D *xt_gui_canvas_get(XtGuiWindow *win, const xtgui::Node *node);
243
257
  xtgui::Canvas2D *xt_gui_canvas_find(XtGuiWindow *win, const xtgui::Node *node);
244
258
  /** Drop a node's canvas (called when its handle is forgotten). */
245
259
  void xt_gui_canvas_forget(XtGuiWindow *win, const xtgui::Node *node);
260
+ /** Move a pre-load canvas into the live table when its pending element handle
261
+ * binds. `handle` is the DOM handle that was used for `getContext("2d")`. */
262
+ void xt_gui_canvas_bind_pending(XtGuiWindow *win, const xtgui::Node *node, xt_value handle);
263
+
246
264
  /** Publish live canvases to the document (sizes + render ids) so layout and
247
265
  * paint see them; creates a context for a canvas that has none yet. */
248
266
  void xt_gui_canvas_sync(XtGuiWindow *win);
249
267
  /** Release every canvas of `win` (window close / `loadHTML`). */
250
268
  void xt_gui_canvas_shutdown(XtGuiWindow *win);
251
269
 
270
+ /** The WebGL context of a `<canvas>`, creating it on first use. `webgl2`
271
+ * selects the WebGL 2 API/prototype; a canvas keeps the first context kind it
272
+ * asked for, so a later request for the other one answers null. */
273
+ xtgui::WebGLContext *xt_gui_webgl_get(XtGuiWindow *win, const xtgui::Node *node, bool webgl2);
274
+ /** The existing WebGL context of `node`, or null (no side effects). */
275
+ xtgui::WebGLContext *xt_gui_webgl_find(XtGuiWindow *win, const xtgui::Node *node);
276
+ /** Drop a node's WebGL context (called when its handle is forgotten). */
277
+ void xt_gui_webgl_forget(XtGuiWindow *win, const xtgui::Node *node);
278
+ /** Release every WebGL context of `win`. */
279
+ void xt_gui_webgl_shutdown(XtGuiWindow *win);
280
+ /** Submit every live WebGL context's queued commands. Called once per frame
281
+ * before the page's own render pass samples a canvas texture. */
282
+ void xt_gui_webgl_submit_all();
283
+
252
284
  #endif /* __cplusplus */
253
285
 
254
286
  #endif /* XT_GUI_ENGINE_H */
@@ -272,7 +272,7 @@ void xt_gui_render_window(XtGuiWindow *win) {
272
272
  bool can_paint = xt_gui_renderer_ensure(g_device, win->window);
273
273
  if (can_paint && win->geometry.dirty && win->document != nullptr) {
274
274
  xtgui::DisplayList list;
275
- xtgui::xt_paint_build(win->document->layout().root(), list);
275
+ xtgui::xt_paint_build(win->document->layout().root(), list, viewport_width, viewport_height);
276
276
  xt_gui_geometry_upload(g_device, &win->geometry, list);
277
277
  float density = SDL_GetWindowPixelDensity(win->window);
278
278
  float text_scale = density > 0.0f ? density : 1.0f;
@@ -287,6 +287,9 @@ void xt_gui_render_window(XtGuiWindow *win) {
287
287
  xt_gui_media_upload_frames(g_device);
288
288
  /* Canvases are CPU-drawn: push any new bitmap without a relayout. */
289
289
  xt_gui_canvas_upload_frames(g_device);
290
+ /* WebGL contexts record into their own command buffers; submit them first so
291
+ * the page's render pass samples this frame's drawing-buffer contents. */
292
+ xt_gui_webgl_submit_all();
290
293
  SDL_GPUCommandBuffer *cmd = SDL_AcquireGPUCommandBuffer(g_device);
291
294
  if (cmd == NULL) return;
292
295
  SDL_GPUTexture *swapchain = NULL;
@@ -8,7 +8,7 @@
8
8
  * - the renderer (`renderer_image.cpp`), which owns the GPU textures.
9
9
  *
10
10
  * It also provides the SDL audio sink the decoder writes PCM to, which keeps
11
- * SDL out of `media.a`.
11
+ * SDL out of `media.a`/`media.lib`.
12
12
  *
13
13
  * The frame loop calls `xt_gui_media_sync` (open/close decoders so the next
14
14
  * layout sees the current sources and intrinsic sizes) and `xt_gui_media_tick`
@@ -0,0 +1,83 @@
1
+ /*
2
+ * xbintsc GUI engine — `<canvas>` WebGL lifetime.
3
+ *
4
+ * A WebGL context is owned by the window, keyed by its DOM element (the same
5
+ * shape as the 2D canvas and video tables). Its colour texture is what the
6
+ * page's image pipeline samples, so no pixels pass through the CPU.
7
+ */
8
+
9
+ #include <memory>
10
+
11
+ #include "gui_engine.h"
12
+ #include "gui_internal.h"
13
+
14
+ namespace {
15
+
16
+ bool isCanvasElement(const xtgui::Node *node) {
17
+ return node != nullptr && node->isElement() && node->isTag("canvas");
18
+ }
19
+
20
+ uint64_t next_webgl_id() {
21
+ static uint64_t counter = 0;
22
+ counter++;
23
+ /* Keep WebGL ids out of the Canvas2D id space so the renderer can tell the
24
+ * two kinds of canvas apart from the id alone. */
25
+ return (1ULL << 32) | counter;
26
+ }
27
+
28
+ } // namespace
29
+
30
+ xtgui::WebGLContext *xt_gui_webgl_find(XtGuiWindow *win, const xtgui::Node *node) {
31
+ if (win == nullptr || node == nullptr) return nullptr;
32
+ auto found = win->webgl_canvases.find(node);
33
+ return found == win->webgl_canvases.end() ? nullptr : found->second.get();
34
+ }
35
+
36
+ xtgui::WebGLContext *xt_gui_webgl_get(XtGuiWindow *win, const xtgui::Node *node, bool webgl2) {
37
+ if (win == nullptr || !isCanvasElement(node)) return nullptr;
38
+ xtgui::WebGLContext *existing = xt_gui_webgl_find(win, node);
39
+ if (existing != nullptr) return existing->is_webgl2() == webgl2 ? existing : nullptr;
40
+ if (win->canvases.find(node) != win->canvases.end()) return nullptr; // already a 2D canvas
41
+ /* WebGL shaders reach SDL_GPU as SPIR-V (Vulkan, translated by glslang) or as
42
+ * MSL (Metal, the same module cross-compiled by SPIRV-Cross). A device whose
43
+ * backend consumes neither format — Direct3D 12 needs DXIL — has no WebGL
44
+ * context in this build, so `getContext("webgl")` answers null instead of
45
+ * handing out a context whose draws could never create a pipeline. */
46
+ if (guidev::g_device == nullptr ||
47
+ (SDL_GetGPUShaderFormats(guidev::g_device) &
48
+ (SDL_GPU_SHADERFORMAT_SPIRV | SDL_GPU_SHADERFORMAT_MSL)) == 0) {
49
+ return nullptr;
50
+ }
51
+ const std::string *width_attr = node->attr("width");
52
+ const std::string *height_attr = node->attr("height");
53
+ float width = 300.0f;
54
+ float height = 150.0f;
55
+ if (width_attr != nullptr) xtgui::xt_css_parse_number(*width_attr, &width);
56
+ if (height_attr != nullptr) xtgui::xt_css_parse_number(*height_attr, &height);
57
+ auto context = std::make_unique<xtgui::WebGLContext>(
58
+ next_webgl_id(), (int)(width + 0.5f), (int)(height + 0.5f), webgl2);
59
+ xtgui::WebGLContext *raw = context.get();
60
+ win->webgl_canvases[node] = std::move(context);
61
+ if (win->document != nullptr) {
62
+ win->document->setCanvasInfo(node, (float)raw->width(), (float)raw->height(), raw->id());
63
+ }
64
+ return raw;
65
+ }
66
+
67
+ void xt_gui_webgl_forget(XtGuiWindow *win, const xtgui::Node *node) {
68
+ if (win == nullptr || node == nullptr) return;
69
+ win->webgl_canvases.erase(node);
70
+ }
71
+
72
+ void xt_gui_webgl_shutdown(XtGuiWindow *win) {
73
+ if (win == nullptr) return;
74
+ win->webgl_canvases.clear();
75
+ }
76
+
77
+ void xt_gui_webgl_submit_all() {
78
+ for (int i = 0; i < XT_GUI_MAX_WINDOWS; i++) {
79
+ XtGuiWindow &win = guidev::g_windows[i];
80
+ if (!win.open) continue;
81
+ for (auto &entry : win.webgl_canvases) entry.second->submit();
82
+ }
83
+ }
@@ -0,0 +1,28 @@
1
+ /*
2
+ * xbintsc GUI engine — `ImageBitmap` pixel storage.
3
+ */
4
+
5
+ #include "image_bitmap.h"
6
+
7
+ #include <unordered_map>
8
+
9
+ namespace xtgui {
10
+ namespace {
11
+
12
+ std::unordered_map<uint64_t, Image> g_bitmaps;
13
+ uint64_t g_next_bitmap = 1;
14
+
15
+ } // namespace
16
+
17
+ uint64_t xt_image_bitmap_add(const Image &image) {
18
+ const uint64_t id = g_next_bitmap++;
19
+ g_bitmaps[id] = image;
20
+ return id;
21
+ }
22
+
23
+ const Image *xt_image_bitmap_get(uint64_t id) {
24
+ auto found = g_bitmaps.find(id);
25
+ return found == g_bitmaps.end() ? nullptr : &found->second;
26
+ }
27
+
28
+ } // namespace xtgui
@@ -0,0 +1,26 @@
1
+ /*
2
+ * xbintsc GUI engine — `ImageBitmap` storage.
3
+ *
4
+ * `createImageBitmap(source)` returns a settled promise of an immutable
5
+ * bitmap. The pixels stay in C++ so `texImage2D` can consume them without
6
+ * materialising a JavaScript array; the handle object carries the numeric id
7
+ * under `__xt_bitmap`.
8
+ */
9
+ #ifndef XT_GUI_IMAGE_BITMAP_H
10
+ #define XT_GUI_IMAGE_BITMAP_H
11
+
12
+ #include <cstdint>
13
+
14
+ #include "image.h"
15
+
16
+ namespace xtgui {
17
+
18
+ /** Store a decoded bitmap and return its process-wide id. */
19
+ uint64_t xt_image_bitmap_add(const Image &image);
20
+
21
+ /** Look up a bitmap; null when the id is unknown. */
22
+ const Image *xt_image_bitmap_get(uint64_t id);
23
+
24
+ } // namespace xtgui
25
+
26
+ #endif /* XT_GUI_IMAGE_BITMAP_H */
@@ -0,0 +1,136 @@
1
+ /*
2
+ * xbintsc GUI engine — `createImageBitmap`.
3
+ *
4
+ * The browser entry point returns a promise (as it does on the web), so code
5
+ * that writes `const bitmap = await createImageBitmap(canvas)` works unchanged.
6
+ * The pixels are kept in `image_bitmap.cpp`; `texImage2D` reads the same
7
+ * registry through the bitmap's `__xt_bitmap` id.
8
+ */
9
+
10
+ #include "gui_engine.h"
11
+ #include "dom_api_internal.h"
12
+
13
+ #include <cstring>
14
+ #include <string>
15
+ #include <vector>
16
+
17
+ #include "image_bitmap.h"
18
+ #include "webgl.h"
19
+
20
+ namespace {
21
+
22
+ void flip_rows(std::vector<unsigned char> *pixels, int width, int height) {
23
+ const size_t stride = (size_t)width * 4;
24
+ std::vector<unsigned char> row(stride);
25
+ for (int y = 0; y < height / 2; y++) {
26
+ unsigned char *top = pixels->data() + (size_t)y * stride;
27
+ unsigned char *bottom = pixels->data() + (size_t)(height - 1 - y) * stride;
28
+ memcpy(row.data(), top, stride);
29
+ memcpy(top, bottom, stride);
30
+ memcpy(bottom, row.data(), stride);
31
+ }
32
+ }
33
+
34
+ bool source_pixels(xt_value source, xtgui::Image *out) {
35
+ /* ImageData / another bitmap-like object: width, height, data. */
36
+ xt_value width_value = xt_object_get_cstr(source, "width");
37
+ xt_value height_value = xt_object_get_cstr(source, "height");
38
+ xt_value data = xt_object_get_cstr(source, "data");
39
+ if (XT_IS_OBJECT(data) && XT_IS_NUMBER(width_value) && XT_IS_NUMBER(height_value)) {
40
+ const int width = (int)xt_to_number(width_value);
41
+ const int height = (int)xt_to_number(height_value);
42
+ const std::vector<uint8_t> bytes = domapi::webgl_bytes(data);
43
+ if (width <= 0 || height <= 0 || bytes.size() < (size_t)width * height * 4) return false;
44
+ out->width = width;
45
+ out->height = height;
46
+ out->pixels.assign(bytes.begin(), bytes.begin() + (size_t)width * height * 4);
47
+ return true;
48
+ }
49
+
50
+ XtGuiWindow *win = nullptr;
51
+ xtgui::Node *node = domapi::resolve_node(source, &win);
52
+ if (node == nullptr || !node->isElement()) return false;
53
+ if (node->isTag("img")) {
54
+ const std::string *src = node->attr("src");
55
+ const xtgui::Image *image = src != nullptr ? xtgui::xt_image_load(*src) : nullptr;
56
+ if (image == nullptr) return false;
57
+ *out = *image;
58
+ return true;
59
+ }
60
+ if (node->isTag("canvas")) {
61
+ if (xtgui::Canvas2D *canvas = xt_gui_canvas_find(win, node)) {
62
+ *out = canvas->image();
63
+ return true;
64
+ }
65
+ if (xtgui::WebGLContext *context = xt_gui_webgl_find(win, node)) {
66
+ out->width = context->width();
67
+ out->height = context->height();
68
+ out->pixels.resize((size_t)out->width * out->height * 4);
69
+ if (!context->read_pixels(0, 0, out->width, out->height, out->pixels.data())) {
70
+ return false;
71
+ }
72
+ /* readPixels is GL-ordered (bottom-up); bitmaps are stored top-down. */
73
+ flip_rows(&out->pixels, out->width, out->height);
74
+ return true;
75
+ }
76
+ return false;
77
+ }
78
+ if (node->isTag("video")) {
79
+ xtgui::MediaPlayer *player = xt_gui_media_player(win, node);
80
+ const xtgui::Image *frame = player != nullptr ? player->frame() : nullptr;
81
+ if (frame == nullptr || frame->width <= 0 || frame->height <= 0) return false;
82
+ *out = *frame;
83
+ return true;
84
+ }
85
+ return false;
86
+ }
87
+
88
+ xt_value bitmap_object(const xtgui::Image &image) {
89
+ const uint64_t id = xtgui::xt_image_bitmap_add(image);
90
+ xt_value name = xt_string_from_cstr("ImageBitmap");
91
+ xt_value constructor = xt_browser_type(1, &name);
92
+ xt_value prototype = xt_object_get_cstr(constructor, "prototype");
93
+ xt_value bitmap = XT_IS_OBJECT(prototype) ? xt_object_new_with_proto(prototype)
94
+ : xt_object_new();
95
+ xt_object_set(bitmap, xt_string_from_cstr("width"), xt_number(image.width));
96
+ xt_object_set(bitmap, xt_string_from_cstr("height"), xt_number(image.height));
97
+ xt_object_set(bitmap, xt_string_from_cstr("__xt_bitmap"), xt_number((double)id));
98
+ return bitmap;
99
+ }
100
+
101
+ } // namespace
102
+
103
+ namespace {
104
+
105
+ xt_value create_bitmap(int32_t argc, xt_value *argv) {
106
+ if (argc < 1) return xt_promise_reject(xt_string_from_cstr("TypeError"));
107
+ xtgui::Image image;
108
+ if (!source_pixels(xt_arg(argc, argv, 0), &image)) {
109
+ return xt_promise_reject(xt_string_from_cstr("InvalidStateError"));
110
+ }
111
+ return xt_promise_resolve(bitmap_object(image));
112
+ }
113
+
114
+ /** The closure ABI the runtime calls for a first-class function value. */
115
+ xt_value create_bitmap_call(xt_value self, xt_value env, int32_t argc, xt_value *argv) {
116
+ (void)self;
117
+ (void)env;
118
+ return create_bitmap(argc, argv);
119
+ }
120
+
121
+ xt_value g_create_bitmap = XT_UNDEFINED;
122
+
123
+ } // namespace
124
+
125
+ /* `createImageBitmap` is a *global value*: the compiler calls this getter once
126
+ * and stores the returned closure, which is then called like any function. */
127
+ xt_value xt_browser_create_image_bitmap(int32_t argc, xt_value *argv) {
128
+ (void)argc;
129
+ (void)argv;
130
+ if (!XT_IS_OBJECT(g_create_bitmap)) {
131
+ g_create_bitmap = xt_closure_new((void *)create_bitmap_call, 0, nullptr);
132
+ xt_gc_add_root(&g_create_bitmap);
133
+ }
134
+ return g_create_bitmap;
135
+ }
136
+
@@ -123,6 +123,21 @@ std::unique_ptr<LayoutBox> buildBoxTree(const Node *node,
123
123
  if (box->intrinsic_height <= 0.0f) box->intrinsic_height = 150.0f;
124
124
  }
125
125
  if (box->is_inline_svg) return box;
126
+
127
+ /* `::before` / `::after` boxes are emitted around the DOM children. Only a
128
+ * pseudo-element with `content` generates a box (CSS: a `::before` rule with
129
+ * no content has no effect). Replaced elements never generate them. */
130
+ auto appendPseudo = [&](Node *pseudo) {
131
+ if (pseudo == nullptr || box->is_image || box->is_canvas || box->is_media) return;
132
+ auto pseudoStyle = styles.find(pseudo);
133
+ if (pseudoStyle == styles.end() || !pseudoStyle->second.has_content) return;
134
+ std::unique_ptr<LayoutBox> pseudoBox = buildBoxTree(pseudo, styles, style, media, canvases);
135
+ if (pseudoBox != nullptr) {
136
+ pseudoBox->parent = box.get();
137
+ box->children.push_back(std::move(pseudoBox));
138
+ }
139
+ };
140
+ if (node->isElement() && !node->is_pseudo) appendPseudo(node->pseudo_before);
126
141
  for (const std::unique_ptr<Node> &child : node->children) {
127
142
  std::unique_ptr<LayoutBox> childBox =
128
143
  buildBoxTree(child.get(), styles, style, media, canvases);
@@ -131,6 +146,7 @@ std::unique_ptr<LayoutBox> buildBoxTree(const Node *node,
131
146
  box->children.push_back(std::move(childBox));
132
147
  }
133
148
  }
149
+ if (node->isElement() && !node->is_pseudo) appendPseudo(node->pseudo_after);
134
150
  return box;
135
151
  }
136
152
 
@@ -211,15 +227,20 @@ namespace {
211
227
 
212
228
  const LayoutBox *hitTestBox(const LayoutBox *box, float x, float y) {
213
229
  if (box == nullptr || box->display == Display::None) return nullptr;
230
+ if (box->style != nullptr && !box->style->visible) return nullptr;
214
231
  /* Later siblings paint on top, so probe them first. */
215
232
  for (auto it = box->children.rbegin(); it != box->children.rend(); ++it) {
216
233
  if (const LayoutBox *hit = hitTestBox(it->get(), x, y)) return hit;
217
234
  }
235
+ if (box->style != nullptr && !box->style->pointer_events) return nullptr;
218
236
  float local_x = x;
219
237
  float local_y = y;
220
238
  if (box->has_transform) box->inverse.apply(x, y, &local_x, &local_y);
221
239
  if (local_x >= box->x && local_x < box->x + box->width && local_y >= box->y &&
222
240
  local_y < box->y + box->height) {
241
+ /* A generated box is part of its originating element: events must target
242
+ * the real element handle, not the synthetic node. */
243
+ if (box->node != nullptr && box->node->is_pseudo && box->parent != nullptr) return box->parent;
223
244
  return box;
224
245
  }
225
246
  return nullptr;