@zappdev/cli 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (209) hide show
  1. package/README.md +55 -0
  2. package/dist/zapp-cli.js +9471 -0
  3. package/native/src/app/app.zc +490 -0
  4. package/native/src/event/event.zc +24 -0
  5. package/native/src/event/events.zc +70 -0
  6. package/native/src/platform/darwin/backend.zc +923 -0
  7. package/native/src/platform/darwin/backend_bootstrap.zc +9 -0
  8. package/native/src/platform/darwin/bootstrap.zc +9 -0
  9. package/native/src/platform/darwin/engine_jsc.zc +86 -0
  10. package/native/src/platform/darwin/engine_qjs.zc +92 -0
  11. package/native/src/platform/darwin/platform.zc +156 -0
  12. package/native/src/platform/darwin/webview.zc +550 -0
  13. package/native/src/platform/darwin/webview_bootstrap.zc +9 -0
  14. package/native/src/platform/darwin/window.zc +1223 -0
  15. package/native/src/platform/darwin/worker/common.zc +223 -0
  16. package/native/src/platform/darwin/worker/core/base64_core.zc +29 -0
  17. package/native/src/platform/darwin/worker/core/crypto_core.zc +19 -0
  18. package/native/src/platform/darwin/worker/core/encoding_core.zc +32 -0
  19. package/native/src/platform/darwin/worker/core/fetch_core.zc +145 -0
  20. package/native/src/platform/darwin/worker/core/url_core.zc +69 -0
  21. package/native/src/platform/darwin/worker/core/websocket_core.zc +179 -0
  22. package/native/src/platform/darwin/worker/dispatch.zc +55 -0
  23. package/native/src/platform/darwin/worker/jsc/base64_jsc.zc +39 -0
  24. package/native/src/platform/darwin/worker/jsc/crypto_jsc.zc +49 -0
  25. package/native/src/platform/darwin/worker/jsc/encoding_jsc.zc +86 -0
  26. package/native/src/platform/darwin/worker/jsc/fetch_jsc.zc +149 -0
  27. package/native/src/platform/darwin/worker/jsc/url_jsc.zc +54 -0
  28. package/native/src/platform/darwin/worker/jsc/websocket_jsc.zc +127 -0
  29. package/native/src/platform/darwin/worker/jsc.zc +670 -0
  30. package/native/src/platform/darwin/worker/mod.zc +30 -0
  31. package/native/src/platform/darwin/worker/qjs/fetch_qjs.zc +233 -0
  32. package/native/src/platform/darwin/worker/qjs/qjs_macros.zc +23 -0
  33. package/native/src/platform/darwin/worker/qjs/websocket_qjs.zc +223 -0
  34. package/native/src/platform/darwin/worker/qjs.zc +1053 -0
  35. package/native/src/platform/darwin/worker/timers.zc +149 -0
  36. package/native/src/platform/darwin/worker/timers_qjs.zc +209 -0
  37. package/native/src/platform/platform.zc +64 -0
  38. package/native/src/platform/shared/log.zc +156 -0
  39. package/native/src/platform/shared/worker/qjs/base64_qjs.zc +38 -0
  40. package/native/src/platform/shared/worker/qjs/crypto_qjs.zc +44 -0
  41. package/native/src/platform/shared/worker/qjs/encoding_qjs.zc +95 -0
  42. package/native/src/platform/shared/worker/qjs/url_qjs.zc +65 -0
  43. package/native/src/platform/shared/worker_registry.zc +206 -0
  44. package/native/src/platform/window.zc +446 -0
  45. package/native/src/platform/windows/backend.zc +452 -0
  46. package/native/src/platform/windows/backend_bootstrap.zc +9 -0
  47. package/native/src/platform/windows/bootstrap.zc +9 -0
  48. package/native/src/platform/windows/engine_qjs.zc +60 -0
  49. package/native/src/platform/windows/platform.zc +387 -0
  50. package/native/src/platform/windows/webview.zc +1175 -0
  51. package/native/src/platform/windows/webview_bootstrap.zc +9 -0
  52. package/native/src/platform/windows/window.zc +1271 -0
  53. package/native/src/platform/windows/worker/common.zc +409 -0
  54. package/native/src/platform/windows/worker/core/base64_core.zc +52 -0
  55. package/native/src/platform/windows/worker/core/crypto_core.zc +34 -0
  56. package/native/src/platform/windows/worker/core/encoding_core.zc +60 -0
  57. package/native/src/platform/windows/worker/core/fetch_core.zc +274 -0
  58. package/native/src/platform/windows/worker/core/url_core.zc +216 -0
  59. package/native/src/platform/windows/worker/core/websocket_core.zc +343 -0
  60. package/native/src/platform/windows/worker/dispatch.zc +34 -0
  61. package/native/src/platform/windows/worker/mod.zc +46 -0
  62. package/native/src/platform/windows/worker/qjs/fetch_qjs.zc +255 -0
  63. package/native/src/platform/windows/worker/qjs/websocket_qjs.zc +263 -0
  64. package/native/src/platform/windows/worker/qjs.zc +1049 -0
  65. package/native/src/platform/windows/worker/timers_qjs.zc +288 -0
  66. package/native/src/platform/worker.zc +8 -0
  67. package/native/src/service/service.zc +228 -0
  68. package/native/vendor/quickjs-ng/.gitattributes +4 -0
  69. package/native/vendor/quickjs-ng/.github/dependabot.yml +7 -0
  70. package/native/vendor/quickjs-ng/.github/workflows/ci.yml +812 -0
  71. package/native/vendor/quickjs-ng/.github/workflows/docs.yml +49 -0
  72. package/native/vendor/quickjs-ng/.github/workflows/release.yml +162 -0
  73. package/native/vendor/quickjs-ng/.github/workflows/test-docs.yml +23 -0
  74. package/native/vendor/quickjs-ng/.github/workflows/tsan.yml +32 -0
  75. package/native/vendor/quickjs-ng/.github/workflows/valgrind.yml +33 -0
  76. package/native/vendor/quickjs-ng/.gitmodules +5 -0
  77. package/native/vendor/quickjs-ng/CMakeLists.txt +553 -0
  78. package/native/vendor/quickjs-ng/LICENSE +24 -0
  79. package/native/vendor/quickjs-ng/Makefile +149 -0
  80. package/native/vendor/quickjs-ng/amalgam.js +53 -0
  81. package/native/vendor/quickjs-ng/api-test.c +927 -0
  82. package/native/vendor/quickjs-ng/builtin-array-fromasync.h +119 -0
  83. package/native/vendor/quickjs-ng/builtin-array-fromasync.js +36 -0
  84. package/native/vendor/quickjs-ng/builtin-iterator-zip-keyed.h +332 -0
  85. package/native/vendor/quickjs-ng/builtin-iterator-zip-keyed.js +194 -0
  86. package/native/vendor/quickjs-ng/builtin-iterator-zip.h +337 -0
  87. package/native/vendor/quickjs-ng/builtin-iterator-zip.js +210 -0
  88. package/native/vendor/quickjs-ng/ctest.c +17 -0
  89. package/native/vendor/quickjs-ng/cutils.h +2013 -0
  90. package/native/vendor/quickjs-ng/cxxtest.cc +2 -0
  91. package/native/vendor/quickjs-ng/dtoa.c +1619 -0
  92. package/native/vendor/quickjs-ng/dtoa.h +87 -0
  93. package/native/vendor/quickjs-ng/examples/fib.c +67 -0
  94. package/native/vendor/quickjs-ng/examples/fib_module.js +10 -0
  95. package/native/vendor/quickjs-ng/examples/hello.js +1 -0
  96. package/native/vendor/quickjs-ng/examples/hello_module.js +6 -0
  97. package/native/vendor/quickjs-ng/examples/meson.build +17 -0
  98. package/native/vendor/quickjs-ng/examples/pi_bigint.js +118 -0
  99. package/native/vendor/quickjs-ng/examples/point.c +154 -0
  100. package/native/vendor/quickjs-ng/examples/test_fib.js +8 -0
  101. package/native/vendor/quickjs-ng/examples/test_point.js +43 -0
  102. package/native/vendor/quickjs-ng/fuzz.c +51 -0
  103. package/native/vendor/quickjs-ng/gen/function_source.c +81 -0
  104. package/native/vendor/quickjs-ng/gen/hello.c +53 -0
  105. package/native/vendor/quickjs-ng/gen/hello_module.c +106 -0
  106. package/native/vendor/quickjs-ng/gen/repl.c +3053 -0
  107. package/native/vendor/quickjs-ng/gen/standalone.c +324 -0
  108. package/native/vendor/quickjs-ng/gen/test_fib.c +81 -0
  109. package/native/vendor/quickjs-ng/libregexp-opcode.h +58 -0
  110. package/native/vendor/quickjs-ng/libregexp.c +2687 -0
  111. package/native/vendor/quickjs-ng/libregexp.h +98 -0
  112. package/native/vendor/quickjs-ng/libunicode-table.h +4707 -0
  113. package/native/vendor/quickjs-ng/libunicode.c +1746 -0
  114. package/native/vendor/quickjs-ng/libunicode.h +126 -0
  115. package/native/vendor/quickjs-ng/list.h +107 -0
  116. package/native/vendor/quickjs-ng/lre-test.c +73 -0
  117. package/native/vendor/quickjs-ng/meson.build +684 -0
  118. package/native/vendor/quickjs-ng/meson_options.txt +6 -0
  119. package/native/vendor/quickjs-ng/qjs-wasi-reactor.c +208 -0
  120. package/native/vendor/quickjs-ng/qjs.c +748 -0
  121. package/native/vendor/quickjs-ng/qjsc.c +673 -0
  122. package/native/vendor/quickjs-ng/quickjs-atom.h +267 -0
  123. package/native/vendor/quickjs-ng/quickjs-c-atomics.h +54 -0
  124. package/native/vendor/quickjs-ng/quickjs-libc.c +4986 -0
  125. package/native/vendor/quickjs-ng/quickjs-libc.h +79 -0
  126. package/native/vendor/quickjs-ng/quickjs-opcode.h +369 -0
  127. package/native/vendor/quickjs-ng/quickjs.c +60259 -0
  128. package/native/vendor/quickjs-ng/quickjs.h +1419 -0
  129. package/native/vendor/quickjs-ng/repl.js +1927 -0
  130. package/native/vendor/quickjs-ng/run-test262.c +2417 -0
  131. package/native/vendor/quickjs-ng/standalone.js +129 -0
  132. package/native/vendor/quickjs-ng/tests/assert.js +49 -0
  133. package/native/vendor/quickjs-ng/tests/bug1221.js +16 -0
  134. package/native/vendor/quickjs-ng/tests/bug1296.js +12 -0
  135. package/native/vendor/quickjs-ng/tests/bug1297.js +22 -0
  136. package/native/vendor/quickjs-ng/tests/bug1301.js +21 -0
  137. package/native/vendor/quickjs-ng/tests/bug1302.js +24 -0
  138. package/native/vendor/quickjs-ng/tests/bug1305.js +26 -0
  139. package/native/vendor/quickjs-ng/tests/bug1318.js +54 -0
  140. package/native/vendor/quickjs-ng/tests/bug1352.js +8 -0
  141. package/native/vendor/quickjs-ng/tests/bug1354.js +6 -0
  142. package/native/vendor/quickjs-ng/tests/bug1355.js +58 -0
  143. package/native/vendor/quickjs-ng/tests/bug1368.js +9 -0
  144. package/native/vendor/quickjs-ng/tests/bug39/1.js +6 -0
  145. package/native/vendor/quickjs-ng/tests/bug39/2.js +6 -0
  146. package/native/vendor/quickjs-ng/tests/bug39/3.js +7 -0
  147. package/native/vendor/quickjs-ng/tests/bug488-upstream.js +7 -0
  148. package/native/vendor/quickjs-ng/tests/bug633/0.js +7 -0
  149. package/native/vendor/quickjs-ng/tests/bug633/1.js +4 -0
  150. package/native/vendor/quickjs-ng/tests/bug633/2.js +4 -0
  151. package/native/vendor/quickjs-ng/tests/bug633/3.js +4 -0
  152. package/native/vendor/quickjs-ng/tests/bug645/0.js +4 -0
  153. package/native/vendor/quickjs-ng/tests/bug645/1.js +9 -0
  154. package/native/vendor/quickjs-ng/tests/bug645/2.js +7 -0
  155. package/native/vendor/quickjs-ng/tests/bug648.js +13 -0
  156. package/native/vendor/quickjs-ng/tests/bug652.js +4 -0
  157. package/native/vendor/quickjs-ng/tests/bug741.js +19 -0
  158. package/native/vendor/quickjs-ng/tests/bug775.js +7 -0
  159. package/native/vendor/quickjs-ng/tests/bug776.js +7 -0
  160. package/native/vendor/quickjs-ng/tests/bug832.js +2 -0
  161. package/native/vendor/quickjs-ng/tests/bug858.js +26 -0
  162. package/native/vendor/quickjs-ng/tests/bug904.js +6 -0
  163. package/native/vendor/quickjs-ng/tests/bug988.js +7 -0
  164. package/native/vendor/quickjs-ng/tests/bug999.js +3 -0
  165. package/native/vendor/quickjs-ng/tests/destructured-export.js +8 -0
  166. package/native/vendor/quickjs-ng/tests/detect_module/0.js +1 -0
  167. package/native/vendor/quickjs-ng/tests/detect_module/1.js +2 -0
  168. package/native/vendor/quickjs-ng/tests/detect_module/2.js +1 -0
  169. package/native/vendor/quickjs-ng/tests/detect_module/3.js +8 -0
  170. package/native/vendor/quickjs-ng/tests/detect_module/4.js +3 -0
  171. package/native/vendor/quickjs-ng/tests/empty.js +0 -0
  172. package/native/vendor/quickjs-ng/tests/fixture_cyclic_import.js +2 -0
  173. package/native/vendor/quickjs-ng/tests/fixture_string_exports.js +12 -0
  174. package/native/vendor/quickjs-ng/tests/function_source.js +14 -0
  175. package/native/vendor/quickjs-ng/tests/microbench.js +1267 -0
  176. package/native/vendor/quickjs-ng/tests/null_or_undefined.js +38 -0
  177. package/native/vendor/quickjs-ng/tests/str-pad-leak.js +5 -0
  178. package/native/vendor/quickjs-ng/tests/test_bigint.js +107 -0
  179. package/native/vendor/quickjs-ng/tests/test_bjson.js +366 -0
  180. package/native/vendor/quickjs-ng/tests/test_builtin.js +1314 -0
  181. package/native/vendor/quickjs-ng/tests/test_closure.js +220 -0
  182. package/native/vendor/quickjs-ng/tests/test_cyclic_import.js +12 -0
  183. package/native/vendor/quickjs-ng/tests/test_domexception.js +35 -0
  184. package/native/vendor/quickjs-ng/tests/test_language.js +755 -0
  185. package/native/vendor/quickjs-ng/tests/test_loop.js +367 -0
  186. package/native/vendor/quickjs-ng/tests/test_queue_microtask.js +39 -0
  187. package/native/vendor/quickjs-ng/tests/test_std.js +340 -0
  188. package/native/vendor/quickjs-ng/tests/test_string_exports.js +25 -0
  189. package/native/vendor/quickjs-ng/tests/test_worker.js +43 -0
  190. package/native/vendor/quickjs-ng/tests/test_worker_module.js +30 -0
  191. package/native/vendor/quickjs-ng/tests.conf +14 -0
  192. package/native/vendor/quickjs-ng/unicode_download.sh +19 -0
  193. package/native/vendor/quickjs-ng/unicode_gen.c +3108 -0
  194. package/native/vendor/quickjs-ng/unicode_gen_def.h +310 -0
  195. package/native/vendor/quickjs-ng/update-version.sh +32 -0
  196. package/native/vendor/webview2/include/WebView2.h +60636 -0
  197. package/native/vendor/webview2/include/WebView2EnvironmentOptions.h +406 -0
  198. package/package.json +33 -0
  199. package/src/backend.ts +139 -0
  200. package/src/build-config.ts +87 -0
  201. package/src/build.ts +276 -0
  202. package/src/common.ts +195 -0
  203. package/src/config.ts +89 -0
  204. package/src/dev.ts +164 -0
  205. package/src/generate.ts +200 -0
  206. package/src/icons.ts +116 -0
  207. package/src/init.ts +190 -0
  208. package/src/package.ts +150 -0
  209. package/src/zapp-cli.ts +263 -0
@@ -0,0 +1,1049 @@
1
+ import "../../platform.zc";
2
+ import "../../shared/worker_registry.zc";
3
+ import "../bootstrap.zc";
4
+ import "./common.zc";
5
+ import "../engine_qjs.zc";
6
+ import "./timers_qjs.zc";
7
+ import "./dispatch.zc";
8
+ import "./core/encoding_core.zc";
9
+ import "./core/url_core.zc";
10
+ import "./core/base64_core.zc";
11
+ import "./core/crypto_core.zc";
12
+ import "./core/fetch_core.zc";
13
+ import "./core/websocket_core.zc";
14
+
15
+ raw {
16
+ #if defined(_WIN32) && defined(ZAPP_WORKER_ENGINE_QJS)
17
+ #include "quickjs.h"
18
+ #include "quickjs-libc.h"
19
+ #endif
20
+ }
21
+
22
+ import "../../shared/worker/qjs/encoding_qjs.zc";
23
+ import "../../shared/worker/qjs/url_qjs.zc";
24
+ import "../../shared/worker/qjs/base64_qjs.zc";
25
+ import "../../shared/worker/qjs/crypto_qjs.zc";
26
+ import "./qjs/fetch_qjs.zc";
27
+ import "./qjs/websocket_qjs.zc";
28
+
29
+ include <string.h>
30
+ include <stdlib.h>
31
+
32
+ raw {
33
+ #if defined(_WIN32) && defined(ZAPP_WORKER_ENGINE_QJS)
34
+ #include <windows.h>
35
+ #include <stdio.h>
36
+
37
+ extern void* engine_qjs_create_ctx(void* rt);
38
+ extern int engine_qjs_eval(void* ctx, const char* utf8, size_t len, const char* filename);
39
+ extern const char* engine_qjs_get_exception_message(void* ctx);
40
+ extern void engine_qjs_free_ctx(void* ctx);
41
+
42
+ extern void zapp_dispatch_worker_to_webviews(const char* kind, const char* worker_id, const char* payload_json);
43
+ extern const char* zapp_windows_worker_bootstrap_script(void);
44
+ extern void zapp_handle_message(void* app, char* msg);
45
+ extern void* app_get_active(void);
46
+ extern void zapp_windows_webview_eval_all(const char* js_utf8);
47
+ extern char* zapp_escape_js_string(const char* raw);
48
+
49
+ extern void zapp_windows_qjs_timers_cancel(const char* workerId);
50
+ extern void zapp_windows_qjs_timers_install(JSContext* ctx, JSValue bridge, const char* worker_id);
51
+ extern void zapp_qjs_encoding_install(JSContext* ctx, JSValue bridge);
52
+ extern void zapp_qjs_url_install(JSContext* ctx, JSValue bridge);
53
+ extern void zapp_qjs_base64_install(JSContext* ctx, JSValue bridge);
54
+ extern void zapp_qjs_crypto_install(JSContext* ctx, JSValue bridge);
55
+ extern void zapp_windows_qjs_fetch_install(JSContext* ctx, JSValue bridge, const char* worker_id);
56
+ extern void zapp_windows_qjs_websocket_install(JSContext* ctx, JSValue bridge, const char* worker_id);
57
+
58
+ // Forward declarations for worker state used by thread proc
59
+ JSRuntime* qjs_rt;
60
+
61
+ // -----------------------------------------------------------------------
62
+ // Worker thread: serial work queue (equivalent of GCD serial dispatch queue)
63
+ // -----------------------------------------------------------------------
64
+
65
+ typedef struct ZappWorkItem {
66
+ void (*func)(void* data);
67
+ void* data;
68
+ struct ZappWorkItem* next;
69
+ } ZappWorkItem;
70
+
71
+ static HANDLE zapp_qjs_thread = NULL;
72
+ static HANDLE zapp_qjs_wake_event = NULL;
73
+ static CRITICAL_SECTION zapp_qjs_queue_lock;
74
+ static ZappWorkItem* zapp_qjs_queue_head = NULL;
75
+ static ZappWorkItem* zapp_qjs_queue_tail = NULL;
76
+ static volatile LONG zapp_qjs_shutdown = 0;
77
+
78
+ static HANDLE zapp_qjs_rt_ready_event = NULL;
79
+
80
+ static DWORD WINAPI zapp_qjs_thread_proc(LPVOID param) {
81
+ (void)param;
82
+ extern int app_get_bootstrap_qjs_stack_size(void);
83
+ qjs_rt = JS_NewRuntime();
84
+ JS_SetMaxStackSize(qjs_rt, (size_t)app_get_bootstrap_qjs_stack_size());
85
+ if (zapp_qjs_rt_ready_event) SetEvent(zapp_qjs_rt_ready_event);
86
+ while (!zapp_qjs_shutdown) {
87
+ WaitForSingleObject(zapp_qjs_wake_event, INFINITE);
88
+ while (1) {
89
+ EnterCriticalSection(&zapp_qjs_queue_lock);
90
+ ZappWorkItem* item = zapp_qjs_queue_head;
91
+ if (item) {
92
+ zapp_qjs_queue_head = item->next;
93
+ if (!zapp_qjs_queue_head) zapp_qjs_queue_tail = NULL;
94
+ }
95
+ LeaveCriticalSection(&zapp_qjs_queue_lock);
96
+ if (!item) break;
97
+ item->func(item->data);
98
+ free(item);
99
+ }
100
+ }
101
+ return 0;
102
+ }
103
+
104
+ void zapp_windows_qjs_dispatch(void (*func)(void*), void* data) {
105
+ ZappWorkItem* item = (ZappWorkItem*)calloc(1, sizeof(ZappWorkItem));
106
+ item->func = func;
107
+ item->data = data;
108
+ EnterCriticalSection(&zapp_qjs_queue_lock);
109
+ if (zapp_qjs_queue_tail) {
110
+ zapp_qjs_queue_tail->next = item;
111
+ } else {
112
+ zapp_qjs_queue_head = item;
113
+ }
114
+ zapp_qjs_queue_tail = item;
115
+ LeaveCriticalSection(&zapp_qjs_queue_lock);
116
+ SetEvent(zapp_qjs_wake_event);
117
+ }
118
+
119
+ // -----------------------------------------------------------------------
120
+ // Worker state (maps using ZappStringMap from common.zc)
121
+ // -----------------------------------------------------------------------
122
+
123
+ static ZappStringMap qjs_contexts; // workerId -> JSContext*
124
+ static ZappStringMap qjs_owners; // workerId -> strdup(ownerId)
125
+ static ZappStringMap qjs_script_urls; // workerId -> strdup(scriptUrl)
126
+ static ZappStringMap qjs_shared_flags; // workerId -> (void*)(intptr_t)1 if shared
127
+ static ZappStringMap qjs_shared_ctxs; // scriptUrl -> JSContext*
128
+ static ZappStringMap qjs_shared_refs; // scriptUrl -> (void*)(intptr_t)refcount
129
+ static int qjs_initialized = 0;
130
+ static void* qjs_shared_loading_sentinel = (void*)(uintptr_t)0xDEADBEEF;
131
+
132
+ void zapp_windows_qjs_ensure_init(void) {
133
+ if (qjs_initialized) return;
134
+ qjs_initialized = 1;
135
+
136
+ InitializeCriticalSection(&zapp_qjs_queue_lock);
137
+ zapp_qjs_wake_event = CreateEventW(NULL, FALSE, FALSE, NULL);
138
+ zapp_qjs_rt_ready_event = CreateEventW(NULL, TRUE, FALSE, NULL);
139
+
140
+ zapp_map_init(&qjs_contexts);
141
+ zapp_map_init(&qjs_owners);
142
+ zapp_map_init(&qjs_script_urls);
143
+ zapp_map_init(&qjs_shared_flags);
144
+ zapp_map_init(&qjs_shared_ctxs);
145
+ zapp_map_init(&qjs_shared_refs);
146
+
147
+ zapp_qjs_thread = CreateThread(NULL, 4 * 1024 * 1024, zapp_qjs_thread_proc, NULL, 0, NULL);
148
+ WaitForSingleObject(zapp_qjs_rt_ready_event, INFINITE);
149
+ CloseHandle(zapp_qjs_rt_ready_event);
150
+ zapp_qjs_rt_ready_event = NULL;
151
+ }
152
+
153
+ void* zapp_windows_qjs_get_context(const char* workerId) {
154
+ return zapp_map_get(&qjs_contexts, workerId);
155
+ }
156
+
157
+ void zapp_windows_qjs_drain_jobs(void* ctx_opaque, const char* label) {
158
+ if (!ctx_opaque) return;
159
+ JSContext* ctx = (JSContext*)ctx_opaque;
160
+ JSContext* pctx = NULL;
161
+ int err_job = 0;
162
+ for (;;) {
163
+ err_job = JS_ExecutePendingJob(JS_GetRuntime(ctx), &pctx);
164
+ if (err_job <= 0) {
165
+ if (err_job < 0 && pctx) {
166
+ JSValue exception = JS_GetException(pctx);
167
+ const char* str = JS_ToCString(pctx, exception);
168
+ char log_buf[512];
169
+ snprintf(log_buf, sizeof(log_buf), "pending job error (%s): %s", label ? label : "unknown", str ? str : "unknown");
170
+ zapp_log_worker(ZAPP_LOG_ERROR, -1, -1, log_buf);
171
+ if (str) JS_FreeCString(pctx, str);
172
+ JS_FreeValue(pctx, exception);
173
+ }
174
+ break;
175
+ }
176
+ }
177
+ }
178
+
179
+ static void qjs_call_bridge_method(JSContext* ctx, const char* methodName, int argc, JSValue* argv) {
180
+ if (!ctx || !methodName) return;
181
+
182
+ JSValue global = JS_GetGlobalObject(ctx);
183
+ JSValue symbol = JS_GetPropertyStr(ctx, global, "Symbol");
184
+ JSValue symbolFor = JS_GetPropertyStr(ctx, symbol, "for");
185
+ JSValue arg = JS_NewString(ctx, "zapp.bridge");
186
+ JSValue bridgeSym = JS_Call(ctx, symbolFor, symbol, 1, &arg);
187
+ JSAtom bridgeAtom = JS_ValueToAtom(ctx, bridgeSym);
188
+ JSValue bridge = JS_GetProperty(ctx, global, bridgeAtom);
189
+
190
+ if (JS_IsObject(bridge)) {
191
+ JSValue fn = JS_GetPropertyStr(ctx, bridge, methodName);
192
+ if (JS_IsFunction(ctx, fn)) {
193
+ JSValue ret = JS_Call(ctx, fn, bridge, argc, argv);
194
+ if (JS_IsException(ret)) {
195
+ JSValue exception = JS_GetException(ctx);
196
+ const char* str = JS_ToCString(ctx, exception);
197
+ char log_buf[512];
198
+ snprintf(log_buf, sizeof(log_buf), "bridge.%s threw: %s", methodName, str ? str : "unknown");
199
+ zapp_log_worker(ZAPP_LOG_ERROR, -1, -1, log_buf);
200
+ if (str) JS_FreeCString(ctx, str);
201
+ JS_FreeValue(ctx, exception);
202
+ }
203
+ JS_FreeValue(ctx, ret);
204
+ zapp_windows_qjs_drain_jobs(ctx, methodName);
205
+ }
206
+ JS_FreeValue(ctx, fn);
207
+ }
208
+
209
+ JS_FreeAtom(ctx, bridgeAtom);
210
+ JS_FreeValue(ctx, bridge);
211
+ JS_FreeValue(ctx, bridgeSym);
212
+ JS_FreeValue(ctx, arg);
213
+ JS_FreeValue(ctx, symbolFor);
214
+ JS_FreeValue(ctx, symbol);
215
+ JS_FreeValue(ctx, global);
216
+ }
217
+
218
+ // -----------------------------------------------------------------------
219
+ // QJS bridge C functions (called from JS in worker context)
220
+ // -----------------------------------------------------------------------
221
+
222
+ static JSValue qjs_bridge_post(JSContext* ctx, JSValue this_val, int argc, JSValue* argv, int magic, JSValue* func_data) {
223
+ (void)this_val; (void)magic;
224
+ if (argc < 1) return JS_UNDEFINED;
225
+
226
+ const char* worker_id = JS_ToCString(ctx, func_data[0]);
227
+ if (!worker_id) return JS_UNDEFINED;
228
+
229
+ const char* target_id = worker_id;
230
+ if (argc >= 2 && !JS_IsUndefined(argv[1]) && !JS_IsNull(argv[1])) {
231
+ target_id = JS_ToCString(ctx, argv[1]);
232
+ }
233
+
234
+ JSValue global = JS_GetGlobalObject(ctx);
235
+ JSValue JSON = JS_GetPropertyStr(ctx, global, "JSON");
236
+ JSValue stringify = JS_GetPropertyStr(ctx, JSON, "stringify");
237
+ JSValue jsonStrVal = JS_Call(ctx, stringify, JSON, 1, &argv[0]);
238
+ const char* payload = JS_ToCString(ctx, jsonStrVal);
239
+
240
+ zapp_dispatch_worker_to_webviews("message", target_id, payload ? payload : "null");
241
+
242
+ if (payload) JS_FreeCString(ctx, payload);
243
+ JS_FreeValue(ctx, jsonStrVal);
244
+ JS_FreeValue(ctx, stringify);
245
+ JS_FreeValue(ctx, JSON);
246
+ JS_FreeValue(ctx, global);
247
+ if (target_id != worker_id) JS_FreeCString(ctx, target_id);
248
+ JS_FreeCString(ctx, worker_id);
249
+ return JS_UNDEFINED;
250
+ }
251
+
252
+ static JSValue qjs_bridge_emitToHost(JSContext* ctx, JSValue this_val, int argc, JSValue* argv, int magic, JSValue* func_data) {
253
+ (void)this_val; (void)magic; (void)func_data;
254
+ if (argc < 2) return JS_UNDEFINED;
255
+
256
+ const char* name = JS_ToCString(ctx, argv[0]);
257
+ if (!name) return JS_UNDEFINED;
258
+
259
+ JSValue global = JS_GetGlobalObject(ctx);
260
+ JSValue JSON = JS_GetPropertyStr(ctx, global, "JSON");
261
+ JSValue stringify = JS_GetPropertyStr(ctx, JSON, "stringify");
262
+ JSValue jsonStrVal = JS_Call(ctx, stringify, JSON, 1, &argv[1]);
263
+ const char* payload = JS_ToCString(ctx, jsonStrVal);
264
+ const char* p = payload ? payload : "{}";
265
+
266
+ // Deliver event to webviews
267
+ char* escaped_name = zapp_escape_js_string(name);
268
+ char* escaped_payload = zapp_escape_js_string(p);
269
+ size_t jsLen = strlen(escaped_name) + strlen(escaped_payload) + 256;
270
+ char* js = (char*)malloc(jsLen);
271
+ snprintf(js, jsLen,
272
+ "(function(){var b=globalThis[Symbol.for('zapp.bridge')];"
273
+ "if(b&&typeof b.deliverEvent==='function'){b.deliverEvent('%s','%s');}})();",
274
+ escaped_name, escaped_payload
275
+ );
276
+ zapp_windows_webview_eval_all(js);
277
+ free(js);
278
+ free(escaped_name);
279
+ free(escaped_payload);
280
+
281
+ if (payload) JS_FreeCString(ctx, payload);
282
+ JS_FreeValue(ctx, jsonStrVal);
283
+ JS_FreeValue(ctx, stringify);
284
+ JS_FreeValue(ctx, JSON);
285
+ JS_FreeValue(ctx, global);
286
+ JS_FreeCString(ctx, name);
287
+ return JS_UNDEFINED;
288
+ }
289
+
290
+ static JSValue qjs_bridge_invokeToHost(JSContext* ctx, JSValue this_val, int argc, JSValue* argv, int magic, JSValue* func_data) {
291
+ (void)this_val; (void)magic; (void)func_data;
292
+ if (argc < 2) return JS_UNDEFINED;
293
+
294
+ const char* name = JS_ToCString(ctx, argv[0]);
295
+ if (!name) return JS_UNDEFINED;
296
+
297
+ JSValue global = JS_GetGlobalObject(ctx);
298
+ JSValue JSON = JS_GetPropertyStr(ctx, global, "JSON");
299
+ JSValue stringify = JS_GetPropertyStr(ctx, JSON, "stringify");
300
+ JSValue jsonStrVal = JS_Call(ctx, stringify, JSON, 1, &argv[1]);
301
+ const char* payload = JS_ToCString(ctx, jsonStrVal);
302
+ const char* p = payload ? payload : "{}";
303
+
304
+ void* app_ptr = app_get_active();
305
+ if (app_ptr) {
306
+ size_t wireLen = strlen("invoke\n") + strlen(name) + 1 + strlen(p) + 1;
307
+ char* wire = (char*)malloc(wireLen);
308
+ snprintf(wire, wireLen, "invoke\n%s\n%s", name, p);
309
+ zapp_handle_message(app_ptr, wire);
310
+ free(wire);
311
+ }
312
+
313
+ if (payload) JS_FreeCString(ctx, payload);
314
+ JS_FreeValue(ctx, jsonStrVal);
315
+ JS_FreeValue(ctx, stringify);
316
+ JS_FreeValue(ctx, JSON);
317
+ JS_FreeValue(ctx, global);
318
+ JS_FreeCString(ctx, name);
319
+ return JS_UNDEFINED;
320
+ }
321
+
322
+ static JSValue qjs_bridge_reportError(JSContext* ctx, JSValue this_val, int argc, JSValue* argv, int magic, JSValue* func_data) {
323
+ (void)this_val; (void)magic;
324
+ if (argc < 1) return JS_UNDEFINED;
325
+
326
+ const char* worker_id = JS_ToCString(ctx, func_data[0]);
327
+ if (!worker_id) return JS_UNDEFINED;
328
+
329
+ const char* msg = JS_ToCString(ctx, argv[0]);
330
+ zapp_worker_dispatch_error_message(worker_id, msg ? msg : "Worker error");
331
+ if (msg) JS_FreeCString(ctx, msg);
332
+ JS_FreeCString(ctx, worker_id);
333
+ return JS_UNDEFINED;
334
+ }
335
+
336
+ static JSValue qjs_bridge_closeWorker(JSContext* ctx, JSValue this_val, int argc, JSValue* argv, int magic, JSValue* func_data) {
337
+ (void)this_val; (void)magic; (void)argc; (void)argv;
338
+ const char* worker_id = JS_ToCString(ctx, func_data[0]);
339
+ if (!worker_id) return JS_UNDEFINED;
340
+ zapp_dispatch_worker_to_webviews("close", worker_id, "{}");
341
+ JS_FreeCString(ctx, worker_id);
342
+ return JS_UNDEFINED;
343
+ }
344
+
345
+ static JSValue qjs_bridge_syncWait(JSContext* ctx, JSValue this_val, int argc, JSValue* argv, int magic, JSValue* func_data) {
346
+ (void)this_val; (void)magic;
347
+ if (argc < 1) return JS_UNDEFINED;
348
+ const char* worker_id = JS_ToCString(ctx, func_data[0]);
349
+ if (!worker_id) return JS_UNDEFINED;
350
+
351
+ JSValue global = JS_GetGlobalObject(ctx);
352
+ JSValue JSON = JS_GetPropertyStr(ctx, global, "JSON");
353
+ JSValue stringify = JS_GetPropertyStr(ctx, JSON, "stringify");
354
+ JSValue jsonStrVal = JS_Call(ctx, stringify, JSON, 1, &argv[0]);
355
+ const char* payload = JS_ToCString(ctx, jsonStrVal);
356
+
357
+ void* app_ptr = app_get_active();
358
+ if (app_ptr && payload) {
359
+ size_t wireLen = strlen("sync\nwait\n") + strlen(payload) + 1;
360
+ char* wire = (char*)malloc(wireLen);
361
+ snprintf(wire, wireLen, "sync\nwait\n%s", payload);
362
+ zapp_handle_message(app_ptr, wire);
363
+ free(wire);
364
+ }
365
+
366
+ if (payload) JS_FreeCString(ctx, payload);
367
+ JS_FreeValue(ctx, jsonStrVal);
368
+ JS_FreeValue(ctx, stringify);
369
+ JS_FreeValue(ctx, JSON);
370
+ JS_FreeValue(ctx, global);
371
+ JS_FreeCString(ctx, worker_id);
372
+ return JS_UNDEFINED;
373
+ }
374
+
375
+ static JSValue qjs_bridge_syncNotify(JSContext* ctx, JSValue this_val, int argc, JSValue* argv, int magic, JSValue* func_data) {
376
+ (void)this_val; (void)magic;
377
+ if (argc < 1) return JS_UNDEFINED;
378
+ const char* worker_id = JS_ToCString(ctx, func_data[0]);
379
+ if (!worker_id) return JS_UNDEFINED;
380
+
381
+ JSValue global = JS_GetGlobalObject(ctx);
382
+ JSValue JSON = JS_GetPropertyStr(ctx, global, "JSON");
383
+ JSValue stringify = JS_GetPropertyStr(ctx, JSON, "stringify");
384
+ JSValue jsonStrVal = JS_Call(ctx, stringify, JSON, 1, &argv[0]);
385
+ const char* payload = JS_ToCString(ctx, jsonStrVal);
386
+
387
+ void* app_ptr = app_get_active();
388
+ if (app_ptr && payload) {
389
+ size_t wireLen = strlen("sync\nnotify\n") + strlen(payload) + 1;
390
+ char* wire = (char*)malloc(wireLen);
391
+ snprintf(wire, wireLen, "sync\nnotify\n%s", payload);
392
+ zapp_handle_message(app_ptr, wire);
393
+ free(wire);
394
+ }
395
+
396
+ if (payload) JS_FreeCString(ctx, payload);
397
+ JS_FreeValue(ctx, jsonStrVal);
398
+ JS_FreeValue(ctx, stringify);
399
+ JS_FreeValue(ctx, JSON);
400
+ JS_FreeValue(ctx, global);
401
+ JS_FreeCString(ctx, worker_id);
402
+ return JS_UNDEFINED;
403
+ }
404
+
405
+ static JSValue qjs_bridge_syncCancel(JSContext* ctx, JSValue this_val, int argc, JSValue* argv, int magic, JSValue* func_data) {
406
+ (void)this_val; (void)magic;
407
+ if (argc < 1) return JS_UNDEFINED;
408
+ const char* worker_id = JS_ToCString(ctx, func_data[0]);
409
+ if (!worker_id) return JS_UNDEFINED;
410
+
411
+ JSValue global = JS_GetGlobalObject(ctx);
412
+ JSValue JSON = JS_GetPropertyStr(ctx, global, "JSON");
413
+ JSValue stringify = JS_GetPropertyStr(ctx, JSON, "stringify");
414
+ JSValue jsonStrVal = JS_Call(ctx, stringify, JSON, 1, &argv[0]);
415
+ const char* payload = JS_ToCString(ctx, jsonStrVal);
416
+
417
+ void* app_ptr = app_get_active();
418
+ if (app_ptr && payload) {
419
+ size_t wireLen = strlen("sync\ncancel\n") + strlen(payload) + 1;
420
+ char* wire = (char*)malloc(wireLen);
421
+ snprintf(wire, wireLen, "sync\ncancel\n%s", payload);
422
+ zapp_handle_message(app_ptr, wire);
423
+ free(wire);
424
+ }
425
+
426
+ if (payload) JS_FreeCString(ctx, payload);
427
+ JS_FreeValue(ctx, jsonStrVal);
428
+ JS_FreeValue(ctx, stringify);
429
+ JS_FreeValue(ctx, JSON);
430
+ JS_FreeValue(ctx, global);
431
+ JS_FreeCString(ctx, worker_id);
432
+ return JS_UNDEFINED;
433
+ }
434
+
435
+ extern void zapp_log_worker(int level, int window_id, int worker_id, const char* message);
436
+ #ifndef ZAPP_LOG_ERROR
437
+ #define ZAPP_LOG_ERROR 0
438
+ #define ZAPP_LOG_WARN 1
439
+ #define ZAPP_LOG_INFO 2
440
+ #define ZAPP_LOG_DEBUG 3
441
+ #define ZAPP_LOG_TRACE 4
442
+ #endif
443
+
444
+ static JSValue qjs_console_log(JSContext* ctx, JSValue this_val, int argc, JSValue* argv, int magic, JSValue* func_data) {
445
+ (void)this_val;
446
+ char msg_buf[4096];
447
+ size_t msg_len = 0;
448
+
449
+ JSValue global = JS_GetGlobalObject(ctx);
450
+ JSValue JSON_val = JS_GetPropertyStr(ctx, global, "JSON");
451
+ JSValue stringify = JS_IsObject(JSON_val) ? JS_GetPropertyStr(ctx, JSON_val, "stringify") : JS_UNDEFINED;
452
+
453
+ for (int i = 0; i < argc && msg_len < sizeof(msg_buf) - 1; i++) {
454
+ const char* str = NULL;
455
+ if (JS_IsFunction(ctx, stringify)) {
456
+ JSValue args[1] = { argv[i] };
457
+ JSValue jsonVal = JS_Call(ctx, stringify, JSON_val, 1, args);
458
+ if (!JS_IsException(jsonVal)) {
459
+ str = JS_ToCString(ctx, jsonVal);
460
+ }
461
+ JS_FreeValue(ctx, jsonVal);
462
+ }
463
+ if (!str) str = JS_ToCString(ctx, argv[i]);
464
+ if (str) {
465
+ int n = snprintf(msg_buf + msg_len, sizeof(msg_buf) - msg_len, "%s%s", i > 0 ? " " : "", str);
466
+ if (n > 0) msg_len += (size_t)n;
467
+ JS_FreeCString(ctx, str);
468
+ }
469
+ }
470
+ msg_buf[msg_len] = '\0';
471
+
472
+ JS_FreeValue(ctx, global);
473
+ if (JS_IsObject(JSON_val)) JS_FreeValue(ctx, stringify);
474
+ JS_FreeValue(ctx, JSON_val);
475
+
476
+ int level = (magic >= ZAPP_LOG_ERROR && magic <= ZAPP_LOG_TRACE) ? magic : ZAPP_LOG_INFO;
477
+ int window_id = -1, worker_seq_id = -1;
478
+ if (func_data) {
479
+ const char* wid = JS_ToCString(ctx, func_data[0]);
480
+ if (wid) {
481
+ zapp_worker_lookup(wid, &window_id, &worker_seq_id);
482
+ JS_FreeCString(ctx, wid);
483
+ }
484
+ }
485
+ zapp_log_worker(level, window_id, worker_seq_id, msg_buf);
486
+ return JS_UNDEFINED;
487
+ }
488
+
489
+ // -----------------------------------------------------------------------
490
+ // Worker reset
491
+ // -----------------------------------------------------------------------
492
+
493
+ void zapp_windows_reset_owner_workers(const char* owner_id) {
494
+ if (!owner_id || !qjs_initialized) return;
495
+
496
+ for (int i = 0; i < ZAPP_MAP_MAX; i++) {
497
+ if (qjs_owners.entries[i].key[0] == '\0') continue;
498
+ char* storedOwner = (char*)qjs_owners.entries[i].val;
499
+ if (!storedOwner || strcmp(storedOwner, owner_id) != 0) continue;
500
+
501
+ char workerId[128];
502
+ strncpy(workerId, qjs_owners.entries[i].key, sizeof(workerId) - 1);
503
+ workerId[sizeof(workerId) - 1] = '\0';
504
+
505
+ void* ctx_ptr = zapp_map_get(&qjs_contexts, workerId);
506
+ char* scriptUrl = (char*)zapp_map_get(&qjs_script_urls, workerId);
507
+ int isShared = zapp_map_get(&qjs_shared_flags, workerId) != NULL;
508
+
509
+ zapp_map_remove(&qjs_contexts, workerId);
510
+ char* owner_copy = (char*)zapp_map_get(&qjs_owners, workerId);
511
+ zapp_map_remove(&qjs_owners, workerId);
512
+ if (owner_copy) free(owner_copy);
513
+ char* url_copy = (char*)zapp_map_get(&qjs_script_urls, workerId);
514
+ zapp_map_remove(&qjs_script_urls, workerId);
515
+ zapp_map_remove(&qjs_shared_flags, workerId);
516
+
517
+ if (!ctx_ptr) { if (url_copy) free(url_copy); continue; }
518
+
519
+ zapp_windows_qjs_timers_cancel(workerId);
520
+
521
+ if (isShared && scriptUrl) {
522
+ JSContext* ctx = (JSContext*)ctx_ptr;
523
+ JSValue args[1];
524
+ args[0] = JS_NewString(ctx, workerId);
525
+ qjs_call_bridge_method(ctx, "dispatchDisconnect", 1, args);
526
+ JS_FreeValue(ctx, args[0]);
527
+
528
+ intptr_t rc = (intptr_t)zapp_map_get(&qjs_shared_refs, scriptUrl);
529
+ rc--;
530
+ if (rc > 0) {
531
+ zapp_map_set(&qjs_shared_refs, scriptUrl, (void*)rc);
532
+ } else {
533
+ zapp_map_remove(&qjs_shared_refs, scriptUrl);
534
+ zapp_map_remove(&qjs_shared_ctxs, scriptUrl);
535
+ engine_qjs_free_ctx(ctx_ptr);
536
+ }
537
+ } else {
538
+ engine_qjs_free_ctx(ctx_ptr);
539
+ }
540
+ if (url_copy) free(url_copy);
541
+ }
542
+ }
543
+
544
+ void zapp_windows_reset_all_workers(void) {
545
+ if (!qjs_initialized) return;
546
+
547
+ void* seen[ZAPP_MAP_MAX];
548
+ int seen_count = 0;
549
+
550
+ for (int i = 0; i < ZAPP_MAP_MAX; i++) {
551
+ if (qjs_contexts.entries[i].key[0] == '\0') continue;
552
+ void* ctx_ptr = qjs_contexts.entries[i].val;
553
+ if (!ctx_ptr) continue;
554
+
555
+ zapp_windows_qjs_timers_cancel(qjs_contexts.entries[i].key);
556
+
557
+ int found = 0;
558
+ for (int j = 0; j < seen_count; j++) {
559
+ if (seen[j] == ctx_ptr) { found = 1; break; }
560
+ }
561
+ if (!found && seen_count < ZAPP_MAP_MAX) {
562
+ seen[seen_count++] = ctx_ptr;
563
+ engine_qjs_free_ctx(ctx_ptr);
564
+ }
565
+ }
566
+
567
+ // Free strdup'd values in owners and script_urls
568
+ for (int i = 0; i < ZAPP_MAP_MAX; i++) {
569
+ if (qjs_owners.entries[i].val) free(qjs_owners.entries[i].val);
570
+ if (qjs_script_urls.entries[i].val) free(qjs_script_urls.entries[i].val);
571
+ }
572
+
573
+ zapp_map_clear(&qjs_contexts);
574
+ zapp_map_clear(&qjs_owners);
575
+ zapp_map_clear(&qjs_script_urls);
576
+ zapp_map_clear(&qjs_shared_flags);
577
+ zapp_map_clear(&qjs_shared_ctxs);
578
+ zapp_map_clear(&qjs_shared_refs);
579
+ }
580
+
581
+ // -----------------------------------------------------------------------
582
+ // Create worker data (passed to QJS thread)
583
+ // -----------------------------------------------------------------------
584
+
585
+ typedef struct {
586
+ char workerId[128];
587
+ char ownerId[128];
588
+ char scriptUrl[512];
589
+ int shared;
590
+ int maxWorkers;
591
+ int inspectable;
592
+ } ZappCreateWorkerData;
593
+
594
+ static void zapp_qjs_create_worker_on_thread(void* data) {
595
+ ZappCreateWorkerData* d = (ZappCreateWorkerData*)data;
596
+
597
+ // Shared worker: check if already loaded
598
+ if (d->shared) {
599
+ void* existingCtx = zapp_map_get(&qjs_shared_ctxs, d->scriptUrl);
600
+ if (existingCtx && existingCtx != qjs_shared_loading_sentinel) {
601
+ intptr_t rc = (intptr_t)zapp_map_get(&qjs_shared_refs, d->scriptUrl);
602
+ zapp_map_set(&qjs_shared_refs, d->scriptUrl, (void*)(rc + 1));
603
+ zapp_map_set(&qjs_contexts, d->workerId, existingCtx);
604
+ zapp_map_set(&qjs_script_urls, d->workerId, _strdup(d->scriptUrl));
605
+ zapp_map_set(&qjs_shared_flags, d->workerId, (void*)(intptr_t)1);
606
+ if (d->ownerId[0]) zapp_map_set(&qjs_owners, d->workerId, _strdup(d->ownerId));
607
+ zapp_worker_register(d->workerId);
608
+
609
+ JSValue args[1];
610
+ args[0] = JS_NewString((JSContext*)existingCtx, d->workerId);
611
+ qjs_call_bridge_method((JSContext*)existingCtx, "dispatchConnect", 1, args);
612
+ JS_FreeValue((JSContext*)existingCtx, args[0]);
613
+ free(d);
614
+ return;
615
+ }
616
+ zapp_map_set(&qjs_shared_ctxs, d->scriptUrl, qjs_shared_loading_sentinel);
617
+ }
618
+
619
+ if (d->maxWorkers > 0 && zapp_map_count_unique_ptrs(&qjs_contexts) >= d->maxWorkers) {
620
+ if (d->shared) zapp_map_remove(&qjs_shared_ctxs, d->scriptUrl);
621
+ zapp_worker_dispatch_max_workers_error(d->workerId, d->maxWorkers);
622
+ free(d);
623
+ return;
624
+ }
625
+
626
+ char* source = zapp_worker_read_source(d->scriptUrl);
627
+ if (!source) {
628
+ if (d->shared) zapp_map_remove(&qjs_shared_ctxs, d->scriptUrl);
629
+ zapp_worker_dispatch_error_message(d->workerId, "Failed to load script");
630
+ free(d);
631
+ return;
632
+ }
633
+
634
+ void* ctx_opaque = engine_qjs_create_ctx(qjs_rt);
635
+ if (!ctx_opaque) {
636
+ if (d->shared) zapp_map_remove(&qjs_shared_ctxs, d->scriptUrl);
637
+ zapp_worker_dispatch_error_message(d->workerId, "Failed to create context");
638
+ free(source);
639
+ free(d);
640
+ return;
641
+ }
642
+ JSContext* ctx = (JSContext*)ctx_opaque;
643
+
644
+ zapp_map_set(&qjs_contexts, d->workerId, ctx);
645
+ zapp_map_set(&qjs_script_urls, d->workerId, _strdup(d->scriptUrl));
646
+ zapp_map_set(&qjs_shared_flags, d->workerId, d->shared ? (void*)(intptr_t)1 : NULL);
647
+ if (d->ownerId[0]) zapp_map_set(&qjs_owners, d->workerId, _strdup(d->ownerId));
648
+ zapp_worker_register(d->workerId);
649
+
650
+ JSValue global = JS_GetGlobalObject(ctx);
651
+
652
+ JSValue wIdVal = JS_NewString(ctx, d->workerId);
653
+ JSValue funcData[1] = { wIdVal };
654
+
655
+ JSValue consoleObj = JS_NewObject(ctx);
656
+ JS_SetPropertyStr(ctx, consoleObj, "log", JS_NewCFunctionData(ctx, qjs_console_log, 1, ZAPP_LOG_INFO, 1, funcData));
657
+ JS_SetPropertyStr(ctx, consoleObj, "info", JS_NewCFunctionData(ctx, qjs_console_log, 1, ZAPP_LOG_INFO, 1, funcData));
658
+ JS_SetPropertyStr(ctx, consoleObj, "warn", JS_NewCFunctionData(ctx, qjs_console_log, 1, ZAPP_LOG_WARN, 1, funcData));
659
+ JS_SetPropertyStr(ctx, consoleObj, "error", JS_NewCFunctionData(ctx, qjs_console_log, 1, ZAPP_LOG_ERROR, 1, funcData));
660
+ JS_SetPropertyStr(ctx, consoleObj, "debug", JS_NewCFunctionData(ctx, qjs_console_log, 1, ZAPP_LOG_DEBUG, 1, funcData));
661
+ JS_SetPropertyStr(ctx, consoleObj, "trace", JS_NewCFunctionData(ctx, qjs_console_log, 1, ZAPP_LOG_TRACE, 1, funcData));
662
+ JS_SetPropertyStr(ctx, global, "console", consoleObj);
663
+
664
+ // Bridge object
665
+ JSValue bridge = JS_NewObject(ctx);
666
+
667
+ JS_SetPropertyStr(ctx, bridge, "postMessage", JS_NewCFunctionData(ctx, qjs_bridge_post, 1, 0, 1, funcData));
668
+ JS_SetPropertyStr(ctx, bridge, "emitToHost", JS_NewCFunctionData(ctx, qjs_bridge_emitToHost, 2, 0, 1, funcData));
669
+ JS_SetPropertyStr(ctx, bridge, "invokeToHost", JS_NewCFunctionData(ctx, qjs_bridge_invokeToHost, 2, 0, 1, funcData));
670
+ JS_SetPropertyStr(ctx, bridge, "reportError", JS_NewCFunctionData(ctx, qjs_bridge_reportError, 1, 0, 1, funcData));
671
+ JS_SetPropertyStr(ctx, bridge, "closeWorker", JS_NewCFunctionData(ctx, qjs_bridge_closeWorker, 0, 0, 1, funcData));
672
+ JS_SetPropertyStr(ctx, bridge, "syncWait", JS_NewCFunctionData(ctx, qjs_bridge_syncWait, 1, 0, 1, funcData));
673
+ JS_SetPropertyStr(ctx, bridge, "syncNotify", JS_NewCFunctionData(ctx, qjs_bridge_syncNotify, 1, 0, 1, funcData));
674
+ JS_SetPropertyStr(ctx, bridge, "syncCancel", JS_NewCFunctionData(ctx, qjs_bridge_syncCancel, 1, 0, 1, funcData));
675
+
676
+ zapp_windows_qjs_timers_install(ctx, bridge, d->workerId);
677
+ zapp_qjs_crypto_install(ctx, bridge);
678
+ zapp_qjs_encoding_install(ctx, bridge);
679
+ zapp_qjs_url_install(ctx, bridge);
680
+ zapp_qjs_base64_install(ctx, bridge);
681
+ zapp_windows_qjs_fetch_install(ctx, bridge, d->workerId);
682
+ zapp_windows_qjs_websocket_install(ctx, bridge, d->workerId);
683
+
684
+ JS_SetPropertyStr(ctx, global, "globalThis", JS_DupValue(ctx, global));
685
+ JS_SetPropertyStr(ctx, global, "self", JS_DupValue(ctx, global));
686
+ JS_SetPropertyStr(ctx, global, "__zappWorkerDispatchId", JS_NewString(ctx, d->workerId));
687
+ JS_SetPropertyStr(ctx, global, "__zappBridge", bridge);
688
+
689
+ JS_FreeValue(ctx, wIdVal);
690
+
691
+ // Run bootstrap
692
+ const char* bsrc = zapp_windows_worker_bootstrap_script();
693
+ int qjs_ret = engine_qjs_eval(ctx, bsrc, strlen(bsrc), "bootstrap.js");
694
+ if (qjs_ret != 0) {
695
+ if (d->shared) zapp_map_remove(&qjs_shared_ctxs, d->scriptUrl);
696
+ const char* msg = engine_qjs_get_exception_message(ctx);
697
+ char errBuf[1024];
698
+ snprintf(errBuf, sizeof(errBuf), "{\"message\":\"Bootstrap failed: %s\"}", msg ? msg : "unknown");
699
+ zapp_dispatch_worker_to_webviews("error", d->workerId, errBuf);
700
+ engine_qjs_free_ctx(ctx);
701
+ zapp_map_remove(&qjs_contexts, d->workerId);
702
+ free(source);
703
+ free(d);
704
+ return;
705
+ }
706
+
707
+ // Wrap and run user script
708
+ size_t srcLen = strlen(source);
709
+ size_t wrappedLen = srcLen + 64;
710
+ char* wrapped = (char*)malloc(wrappedLen);
711
+ snprintf(wrapped, wrappedLen, "(async function(){\n%s\n})();", source);
712
+ free(source);
713
+
714
+ qjs_ret = engine_qjs_eval(ctx, wrapped, strlen(wrapped), d->scriptUrl[0] ? d->scriptUrl : "<script>");
715
+ free(wrapped);
716
+
717
+ if (qjs_ret != 0) {
718
+ if (d->shared) zapp_map_remove(&qjs_shared_ctxs, d->scriptUrl);
719
+ const char* msg = engine_qjs_get_exception_message(ctx);
720
+ char errBuf[1024];
721
+ snprintf(errBuf, sizeof(errBuf), "{\"message\":\"%s\"}", msg ? msg : "Script error");
722
+ zapp_dispatch_worker_to_webviews("error", d->workerId, errBuf);
723
+ zapp_map_remove(&qjs_contexts, d->workerId);
724
+ engine_qjs_free_ctx(ctx);
725
+ free(d);
726
+ return;
727
+ }
728
+
729
+ zapp_windows_qjs_drain_jobs(ctx, "create");
730
+
731
+ if (d->shared) {
732
+ zapp_map_set(&qjs_shared_ctxs, d->scriptUrl, ctx);
733
+ zapp_map_set(&qjs_shared_refs, d->scriptUrl, (void*)(intptr_t)1);
734
+
735
+ JSValue args[1];
736
+ args[0] = JS_NewString(ctx, d->workerId);
737
+ qjs_call_bridge_method(ctx, "dispatchConnect", 1, args);
738
+ JS_FreeValue(ctx, args[0]);
739
+ }
740
+
741
+ JS_FreeValue(ctx, global);
742
+ free(d);
743
+ }
744
+
745
+ // -----------------------------------------------------------------------
746
+ // Post message data (passed to QJS thread)
747
+ // -----------------------------------------------------------------------
748
+
749
+ typedef struct {
750
+ char workerId[128];
751
+ char* dataJson;
752
+ } ZappPostWorkerData;
753
+
754
+ static void zapp_qjs_post_message_on_thread(void* data) {
755
+ ZappPostWorkerData* d = (ZappPostWorkerData*)data;
756
+ void* ctx_ptr = zapp_map_get(&qjs_contexts, d->workerId);
757
+ if (!ctx_ptr) { free(d->dataJson); free(d); return; }
758
+
759
+ JSContext* ctx = (JSContext*)ctx_ptr;
760
+ JSValue global = JS_GetGlobalObject(ctx);
761
+ JSValue JSON_obj = JS_GetPropertyStr(ctx, global, "JSON");
762
+ JSValue parseFunc = JS_GetPropertyStr(ctx, JSON_obj, "parse");
763
+ JSValue argJson = JS_NewString(ctx, d->dataJson);
764
+ JSValue parsedData = JS_Call(ctx, parseFunc, JSON_obj, 1, &argJson);
765
+
766
+ if (JS_IsException(parsedData)) {
767
+ JSValue exception = JS_GetException(ctx);
768
+ const char* str = JS_ToCString(ctx, exception);
769
+ char log_buf[512];
770
+ snprintf(log_buf, sizeof(log_buf), "JSON.parse in post failed: %s", str ? str : "unknown");
771
+ zapp_log_worker(ZAPP_LOG_ERROR, -1, -1, log_buf);
772
+ if (str) JS_FreeCString(ctx, str);
773
+ JS_FreeValue(ctx, exception);
774
+ } else {
775
+ JSValue args[2];
776
+ args[0] = parsedData;
777
+ args[1] = JS_NewString(ctx, d->workerId);
778
+ qjs_call_bridge_method(ctx, "dispatchMessage", 2, args);
779
+ JS_FreeValue(ctx, args[1]);
780
+ }
781
+
782
+ JS_FreeValue(ctx, parsedData);
783
+ JS_FreeValue(ctx, argJson);
784
+ JS_FreeValue(ctx, parseFunc);
785
+ JS_FreeValue(ctx, JSON_obj);
786
+ JS_FreeValue(ctx, global);
787
+
788
+ free(d->dataJson);
789
+ free(d);
790
+ }
791
+
792
+ // -----------------------------------------------------------------------
793
+ // Terminate worker data (passed to QJS thread)
794
+ // -----------------------------------------------------------------------
795
+
796
+ typedef struct {
797
+ char workerId[128];
798
+ char scriptUrl[512];
799
+ int isShared;
800
+ void* ctx_ptr;
801
+ } ZappTerminateWorkerData;
802
+
803
+ static void zapp_qjs_terminate_on_thread(void* data) {
804
+ ZappTerminateWorkerData* d = (ZappTerminateWorkerData*)data;
805
+
806
+ zapp_windows_qjs_timers_cancel(d->workerId);
807
+
808
+ if (d->isShared && d->scriptUrl[0]) {
809
+ JSContext* ctx = (JSContext*)d->ctx_ptr;
810
+ if (ctx) {
811
+ JSValue args[1];
812
+ args[0] = JS_NewString(ctx, d->workerId);
813
+ qjs_call_bridge_method(ctx, "dispatchDisconnect", 1, args);
814
+ JS_FreeValue(ctx, args[0]);
815
+ }
816
+
817
+ intptr_t rc = (intptr_t)zapp_map_get(&qjs_shared_refs, d->scriptUrl);
818
+ rc--;
819
+ if (rc > 0) {
820
+ zapp_map_set(&qjs_shared_refs, d->scriptUrl, (void*)rc);
821
+ } else {
822
+ zapp_map_remove(&qjs_shared_refs, d->scriptUrl);
823
+ zapp_map_remove(&qjs_shared_ctxs, d->scriptUrl);
824
+ engine_qjs_free_ctx(d->ctx_ptr);
825
+ }
826
+ } else {
827
+ engine_qjs_free_ctx(d->ctx_ptr);
828
+ }
829
+
830
+ free(d);
831
+ }
832
+
833
+ // -----------------------------------------------------------------------
834
+ // Dispatch sync result into worker context
835
+ // -----------------------------------------------------------------------
836
+
837
+ typedef struct {
838
+ char workerId[128];
839
+ char* payloadJson;
840
+ } ZappSyncResultData;
841
+
842
+ static void zapp_qjs_sync_result_on_thread(void* data) {
843
+ ZappSyncResultData* d = (ZappSyncResultData*)data;
844
+ void* ctx_ptr = zapp_map_get(&qjs_contexts, d->workerId);
845
+ if (!ctx_ptr) { free(d->payloadJson); free(d); return; }
846
+
847
+ JSContext* ctx = (JSContext*)ctx_ptr;
848
+ JSValue arg = JS_NewString(ctx, d->payloadJson);
849
+ qjs_call_bridge_method(ctx, "dispatchSyncResult", 1, &arg);
850
+ JS_FreeValue(ctx, arg);
851
+
852
+ free(d->payloadJson);
853
+ free(d);
854
+ }
855
+
856
+ void zapp_windows_worker_dispatch_sync_result(const char* worker_id, const char* payload_json) {
857
+ if (!worker_id || !payload_json || !qjs_initialized) return;
858
+ ZappSyncResultData* d = (ZappSyncResultData*)calloc(1, sizeof(ZappSyncResultData));
859
+ strncpy(d->workerId, worker_id, sizeof(d->workerId) - 1);
860
+ d->payloadJson = _strdup(payload_json);
861
+ zapp_windows_qjs_dispatch(zapp_qjs_sync_result_on_thread, d);
862
+ }
863
+ #endif
864
+ }
865
+
866
+ raw {
867
+ #if defined(_WIN32) && defined(ZAPP_WORKER_ENGINE_QJS)
868
+ typedef struct {
869
+ char* name;
870
+ char* payload;
871
+ } ZappEventFanoutData;
872
+
873
+ static void zapp_event_fanout_on_qjs_thread(void* data) {
874
+ ZappEventFanoutData* d = (ZappEventFanoutData*)data;
875
+
876
+ void* seen[ZAPP_MAP_MAX];
877
+ int seen_count = 0;
878
+ for (int i = 0; i < ZAPP_MAP_MAX; i++) {
879
+ if (qjs_contexts.entries[i].key[0] == '\0') continue;
880
+ JSContext* ctx = (JSContext*)qjs_contexts.entries[i].val;
881
+ if (!ctx) continue;
882
+
883
+ int already_seen = 0;
884
+ for (int j = 0; j < seen_count; j++) {
885
+ if (seen[j] == ctx) { already_seen = 1; break; }
886
+ }
887
+ if (already_seen) continue;
888
+ if (seen_count < ZAPP_MAP_MAX) seen[seen_count++] = ctx;
889
+
890
+ JSValue args[2];
891
+ args[0] = JS_NewString(ctx, d->name);
892
+ args[1] = JS_NewString(ctx, d->payload);
893
+ qjs_call_bridge_method(ctx, "deliverEvent", 2, args);
894
+ JS_FreeValue(ctx, args[0]);
895
+ JS_FreeValue(ctx, args[1]);
896
+ }
897
+ free(d->name);
898
+ free(d->payload);
899
+ free(d);
900
+ }
901
+
902
+ static void zapp_windows_send_event_to_js(void* app_ptr, char* name, char* payload) {
903
+ (void)app_ptr;
904
+ if (name == NULL) return;
905
+ if (payload == NULL) payload = "{}";
906
+
907
+ // 1. Deliver to all webviews (thread-safe via PostMessage)
908
+ char* escaped_name = zapp_escape_js_string(name);
909
+ char* escaped_payload = zapp_escape_js_string(payload);
910
+ size_t jsLen = strlen(escaped_name) + strlen(escaped_payload) + 256;
911
+ char* js = (char*)malloc(jsLen);
912
+ snprintf(js, jsLen,
913
+ "(function(){var b=globalThis[Symbol.for('zapp.bridge')];"
914
+ "if(b&&typeof b.deliverEvent==='function'){b.deliverEvent('%s','%s');}})();",
915
+ escaped_name, escaped_payload
916
+ );
917
+ zapp_windows_webview_eval_all(js);
918
+ free(js);
919
+ free(escaped_name);
920
+ free(escaped_payload);
921
+
922
+ // 2. Deliver to all QJS worker contexts on the QJS thread
923
+ if (!qjs_initialized) return;
924
+ ZappEventFanoutData* d = (ZappEventFanoutData*)calloc(1, sizeof(ZappEventFanoutData));
925
+ d->name = _strdup(name);
926
+ d->payload = _strdup(payload);
927
+ zapp_windows_qjs_dispatch(zapp_event_fanout_on_qjs_thread, d);
928
+ }
929
+ #endif
930
+ }
931
+
932
+ struct WindowsWorkerQjs {}
933
+
934
+ impl WorkerBackend for WindowsWorkerQjs {
935
+ fn bind_runtime(self, app: App*) -> void {
936
+ raw {
937
+ #if defined(_WIN32) && defined(ZAPP_WORKER_ENGINE_QJS)
938
+ app->event.send_event_to_js = (void*)zapp_windows_send_event_to_js;
939
+ #endif
940
+ }
941
+ }
942
+
943
+ fn handle_bridge(self, app: App*, action: string, payload: string) -> void {
944
+ raw {
945
+ #if defined(_WIN32) && defined(ZAPP_WORKER_ENGINE_QJS)
946
+ if (action == NULL || payload == NULL) return;
947
+
948
+ zapp_windows_qjs_ensure_init();
949
+
950
+ const char* actionStr = (const char*)action;
951
+ const char* payloadStr = (const char*)payload;
952
+
953
+ char* workerId = zapp_json_get_string(payloadStr, "id");
954
+ if (!workerId) {
955
+ workerId = zapp_json_get_string(payloadStr, "workerId");
956
+ }
957
+ if (!workerId) return;
958
+
959
+ char* ownerId = zapp_json_get_string(payloadStr, "ownerId");
960
+ int shared = zapp_json_get_bool(payloadStr, "shared", 0);
961
+
962
+ if (strcmp(actionStr, "reset_owner") == 0) {
963
+ if (ownerId && ownerId[0]) {
964
+ zapp_windows_reset_owner_workers(ownerId);
965
+ }
966
+ free(workerId);
967
+ if (ownerId) free(ownerId);
968
+ return;
969
+ }
970
+
971
+ if (strcmp(actionStr, "create") == 0) {
972
+ char* scriptUrl = zapp_json_get_string(payloadStr, "scriptUrl");
973
+ if (!scriptUrl) { free(workerId); if (ownerId) free(ownerId); return; }
974
+
975
+ int maxW = app->config.maxWorkers;
976
+
977
+ ZappCreateWorkerData* d = (ZappCreateWorkerData*)calloc(1, sizeof(ZappCreateWorkerData));
978
+ strncpy(d->workerId, workerId, sizeof(d->workerId) - 1);
979
+ if (ownerId) strncpy(d->ownerId, ownerId, sizeof(d->ownerId) - 1);
980
+ strncpy(d->scriptUrl, scriptUrl, sizeof(d->scriptUrl) - 1);
981
+ d->shared = shared;
982
+ d->maxWorkers = maxW;
983
+ d->inspectable = app->config.webContentInspectable;
984
+
985
+ zapp_windows_qjs_dispatch(zapp_qjs_create_worker_on_thread, d);
986
+ free(scriptUrl);
987
+ free(workerId);
988
+ if (ownerId) free(ownerId);
989
+ return;
990
+ }
991
+
992
+ if (strcmp(actionStr, "post") == 0) {
993
+ char* dataJson = zapp_json_get_raw(payloadStr, "data");
994
+ if (!dataJson) dataJson = _strdup("{}");
995
+
996
+ ZappPostWorkerData* d = (ZappPostWorkerData*)calloc(1, sizeof(ZappPostWorkerData));
997
+ strncpy(d->workerId, workerId, sizeof(d->workerId) - 1);
998
+ d->dataJson = dataJson;
999
+
1000
+ zapp_windows_qjs_dispatch(zapp_qjs_post_message_on_thread, d);
1001
+ free(workerId);
1002
+ if (ownerId) free(ownerId);
1003
+ return;
1004
+ }
1005
+
1006
+ if (strcmp(actionStr, "terminate") == 0) {
1007
+ if (ownerId) {
1008
+ char* existingOwner = (char*)zapp_map_get(&qjs_owners, workerId);
1009
+ if (existingOwner && strcmp(existingOwner, ownerId) != 0) {
1010
+ free(workerId);
1011
+ free(ownerId);
1012
+ return;
1013
+ }
1014
+ }
1015
+
1016
+ void* ctx_ptr = zapp_map_get(&qjs_contexts, workerId);
1017
+ if (!ctx_ptr) { free(workerId); if (ownerId) free(ownerId); return; }
1018
+
1019
+ char* scriptUrl = (char*)zapp_map_get(&qjs_script_urls, workerId);
1020
+ int isShared = zapp_map_get(&qjs_shared_flags, workerId) != NULL;
1021
+
1022
+ zapp_map_remove(&qjs_contexts, workerId);
1023
+ char* owner_copy = (char*)zapp_map_get(&qjs_owners, workerId);
1024
+ zapp_map_remove(&qjs_owners, workerId);
1025
+ if (owner_copy) free(owner_copy);
1026
+ char* url_copy = (char*)zapp_map_get(&qjs_script_urls, workerId);
1027
+ zapp_map_remove(&qjs_script_urls, workerId);
1028
+ zapp_map_remove(&qjs_shared_flags, workerId);
1029
+
1030
+ ZappTerminateWorkerData* d = (ZappTerminateWorkerData*)calloc(1, sizeof(ZappTerminateWorkerData));
1031
+ strncpy(d->workerId, workerId, sizeof(d->workerId) - 1);
1032
+ if (url_copy) strncpy(d->scriptUrl, url_copy, sizeof(d->scriptUrl) - 1);
1033
+ d->isShared = isShared;
1034
+ d->ctx_ptr = ctx_ptr;
1035
+
1036
+ zapp_windows_qjs_dispatch(zapp_qjs_terminate_on_thread, d);
1037
+
1038
+ if (url_copy) free(url_copy);
1039
+ free(workerId);
1040
+ if (ownerId) free(ownerId);
1041
+ return;
1042
+ }
1043
+
1044
+ free(workerId);
1045
+ if (ownerId) free(ownerId);
1046
+ #endif
1047
+ }
1048
+ }
1049
+ }