sparkbun 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 (131) hide show
  1. package/bin/sparkbun.cjs +18 -0
  2. package/dist-linux-arm64/bsdiff +0 -0
  3. package/dist-linux-arm64/bspatch +0 -0
  4. package/dist-linux-arm64/libElectrobunCore.so +0 -0
  5. package/dist-linux-arm64/libNativeWrapper.so +0 -0
  6. package/dist-linux-arm64/libasar.so +0 -0
  7. package/dist-linux-x64/bsdiff +0 -0
  8. package/dist-linux-x64/bspatch +0 -0
  9. package/dist-linux-x64/libElectrobunCore.so +0 -0
  10. package/dist-linux-x64/libNativeWrapper.so +0 -0
  11. package/dist-linux-x64/libasar.so +0 -0
  12. package/dist-macos-arm64/bsdiff +0 -0
  13. package/dist-macos-arm64/bspatch +0 -0
  14. package/dist-macos-arm64/libElectrobunCore.dylib +0 -0
  15. package/dist-macos-arm64/libNativeWrapper.dylib +0 -0
  16. package/dist-macos-arm64/libasar.dylib +0 -0
  17. package/dist-macos-arm64/libwebgpu_dawn.dylib +0 -0
  18. package/dist-macos-arm64/preload-full.js +885 -0
  19. package/dist-macos-arm64/preload-sandboxed.js +111 -0
  20. package/dist-macos-arm64/process_helper +0 -0
  21. package/dist-win-x64/ElectrobunCore.dll +0 -0
  22. package/dist-win-x64/WebView2Loader.dll +0 -0
  23. package/dist-win-x64/bsdiff.exe +0 -0
  24. package/dist-win-x64/bspatch.exe +0 -0
  25. package/dist-win-x64/libNativeWrapper.dll +0 -0
  26. package/dist-win-x64/zig-asar/arm64/libasar.dll +0 -0
  27. package/dist-win-x64/zig-asar/x64/libasar.dll +0 -0
  28. package/package.json +47 -0
  29. package/scripts/build-and-upload-artifacts.js +207 -0
  30. package/scripts/gen-webgpu-ffi.mjs +162 -0
  31. package/scripts/install-windows-deps.ps1 +80 -0
  32. package/scripts/package-release.js +237 -0
  33. package/scripts/push-version.js +84 -0
  34. package/scripts/update-bun-version.ts +122 -0
  35. package/scripts/update-cef-version.ts +145 -0
  36. package/src/browser/builtinrpcSchema.ts +19 -0
  37. package/src/browser/global.d.ts +36 -0
  38. package/src/browser/index.ts +234 -0
  39. package/src/browser/webviewtag.ts +88 -0
  40. package/src/browser/wgputag.ts +48 -0
  41. package/src/bun/SparkBunConfig.ts +497 -0
  42. package/src/bun/__tests__/ffi-contract.test.ts +105 -0
  43. package/src/bun/core/ApplicationMenu.ts +70 -0
  44. package/src/bun/core/BrowserView.ts +416 -0
  45. package/src/bun/core/BrowserWindow.ts +396 -0
  46. package/src/bun/core/BuildConfig.ts +71 -0
  47. package/src/bun/core/ContextMenu.ts +75 -0
  48. package/src/bun/core/GpuWindow.ts +289 -0
  49. package/src/bun/core/Paths.ts +5 -0
  50. package/src/bun/core/Socket.ts +22 -0
  51. package/src/bun/core/Tray.ts +197 -0
  52. package/src/bun/core/Updater.ts +1131 -0
  53. package/src/bun/core/Utils.ts +487 -0
  54. package/src/bun/core/WGPUView.ts +167 -0
  55. package/src/bun/core/menuRoles.ts +181 -0
  56. package/src/bun/events/ApplicationEvents.ts +22 -0
  57. package/src/bun/events/event.ts +27 -0
  58. package/src/bun/events/eventEmitter.ts +45 -0
  59. package/src/bun/events/trayEvents.ts +11 -0
  60. package/src/bun/events/webviewEvents.ts +39 -0
  61. package/src/bun/events/windowEvents.ts +23 -0
  62. package/src/bun/index.ts +120 -0
  63. package/src/bun/preload/.generated/compiled.ts +2 -0
  64. package/src/bun/preload/build.ts +65 -0
  65. package/src/bun/preload/dragRegions.ts +41 -0
  66. package/src/bun/preload/encryption.ts +86 -0
  67. package/src/bun/preload/events.ts +171 -0
  68. package/src/bun/preload/globals.d.ts +45 -0
  69. package/src/bun/preload/index-sandboxed.ts +28 -0
  70. package/src/bun/preload/index.ts +77 -0
  71. package/src/bun/preload/internalRpc.ts +80 -0
  72. package/src/bun/preload/overlaySync.ts +107 -0
  73. package/src/bun/preload/webviewTag.ts +451 -0
  74. package/src/bun/preload/wgpuTag.ts +246 -0
  75. package/src/bun/proc/linux.md +43 -0
  76. package/src/bun/proc/native.ts +3253 -0
  77. package/src/bun/webGPU.ts +346 -0
  78. package/src/bun/webgpuAdapter.ts +3011 -0
  79. package/src/cli/bun.lockb +0 -0
  80. package/src/cli/index.ts +4653 -0
  81. package/src/cli/package-lock.json +81 -0
  82. package/src/cli/package.json +11 -0
  83. package/src/cli/templates/embedded.ts +2 -0
  84. package/src/core/build.zig +16 -0
  85. package/src/core/main.zig +3378 -0
  86. package/src/extractor/build.zig +22 -0
  87. package/src/installer/installer-template.ts +216 -0
  88. package/src/launcher/main.ts +221 -0
  89. package/src/native/build/libNativeWrapper.so +0 -0
  90. package/src/native/linux/build/nativeWrapper.o +0 -0
  91. package/src/native/linux/cef_loader.cpp +110 -0
  92. package/src/native/linux/cef_loader.h +28 -0
  93. package/src/native/linux/cef_process_helper_linux.cpp +160 -0
  94. package/src/native/linux/nativeWrapper.cpp +11768 -0
  95. package/src/native/macos/cef_process_helper_mac.cc +160 -0
  96. package/src/native/macos/nativeWrapper.mm +9172 -0
  97. package/src/native/shared/accelerator_parser.h +72 -0
  98. package/src/native/shared/app_paths.h +110 -0
  99. package/src/native/shared/asar.h +35 -0
  100. package/src/native/shared/cache_migration.h +244 -0
  101. package/src/native/shared/callbacks.h +57 -0
  102. package/src/native/shared/cef_response_filter.h +189 -0
  103. package/src/native/shared/chromium_flags.h +181 -0
  104. package/src/native/shared/config.h +66 -0
  105. package/src/native/shared/download_event.h +197 -0
  106. package/src/native/shared/ffi_helpers.h +139 -0
  107. package/src/native/shared/glob_match.h +59 -0
  108. package/src/native/shared/json_menu_parser.h +223 -0
  109. package/src/native/shared/mime_types.h +101 -0
  110. package/src/native/shared/navigation_rules.h +98 -0
  111. package/src/native/shared/partition_context.h +137 -0
  112. package/src/native/shared/pending_resize_queue.h +45 -0
  113. package/src/native/shared/permissions.h +118 -0
  114. package/src/native/shared/permissions_cef.h +74 -0
  115. package/src/native/shared/preload_script.h +71 -0
  116. package/src/native/shared/shutdown_guard.h +134 -0
  117. package/src/native/shared/thread_safe_map.h +138 -0
  118. package/src/native/shared/webview_storage.h +91 -0
  119. package/src/native/win/cef_process_helper_win.cpp +143 -0
  120. package/src/native/win/dcomp_compositor.h +352 -0
  121. package/src/native/win/nativeWrapper.cpp +12434 -0
  122. package/src/npmbin/index.js +34 -0
  123. package/src/shared/bun-version.ts +3 -0
  124. package/src/shared/cef-version.ts +5 -0
  125. package/src/shared/naming.test.ts +327 -0
  126. package/src/shared/naming.ts +188 -0
  127. package/src/shared/platform.ts +48 -0
  128. package/src/shared/rpc.ts +541 -0
  129. package/src/shared/sparkbun-version.ts +2 -0
  130. package/src/types/three.d.ts +1 -0
  131. package/tsconfig.json +31 -0
@@ -0,0 +1,3253 @@
1
+ import { join, dirname } from "path";
2
+ import { createReadStream } from "node:fs";
3
+ import sparkBunEventEmitter from "../events/eventEmitter";
4
+ import SparkBunEvent from "../events/event";
5
+ import { BrowserView } from "../core/BrowserView";
6
+ import { WGPUView } from "../core/WGPUView";
7
+ import {
8
+ preloadScript,
9
+ preloadScriptSandboxed,
10
+ } from "../preload/.generated/compiled";
11
+
12
+ // Menu data reference system to avoid serialization overhead
13
+ const menuDataRegistry = new Map<string, any>();
14
+ let menuDataCounter = 0;
15
+ function storeMenuData(data: any): string {
16
+ const id = `menuData_${++menuDataCounter}`;
17
+ menuDataRegistry.set(id, data);
18
+ return id;
19
+ }
20
+
21
+ function getMenuData(id: string): any {
22
+ return menuDataRegistry.get(id);
23
+ }
24
+
25
+ function clearMenuData(id: string): void {
26
+ menuDataRegistry.delete(id);
27
+ }
28
+
29
+ // Shared methods for EB delimiter serialization/deserialization
30
+ const SPARKBUN_DELIMITER = "|EB|";
31
+
32
+ function serializeMenuAction(action: string, data: any): string {
33
+ const dataId = storeMenuData(data);
34
+ return `${SPARKBUN_DELIMITER}${dataId}|${action}`;
35
+ }
36
+
37
+ function deserializeMenuAction(encodedAction: string): {
38
+ action: string;
39
+ data: any;
40
+ } {
41
+ let actualAction = encodedAction;
42
+ let data = undefined;
43
+
44
+ if (encodedAction.startsWith(SPARKBUN_DELIMITER)) {
45
+ const parts = encodedAction.split("|");
46
+ if (parts.length >= 4) {
47
+ // ['', 'EB', 'dataId', 'actualAction', ...]
48
+ const dataId = parts[2]!;
49
+ actualAction = parts.slice(3).join("|"); // Rejoin in case action contains |
50
+ data = getMenuData(dataId);
51
+
52
+ // Clean up data from registry after use
53
+ clearMenuData(dataId);
54
+ }
55
+ }
56
+
57
+ return { action: actualAction, data };
58
+ }
59
+
60
+ // todo: set up FFI, this is already in the webworker.
61
+
62
+ import {
63
+ dlopen,
64
+ suffix,
65
+ JSCallback,
66
+ CString,
67
+ ptr,
68
+ FFIType,
69
+ toArrayBuffer,
70
+ type Pointer,
71
+ } from "bun:ffi";
72
+
73
+ function getWindowPtr(winId: number) {
74
+ return core?.symbols.getWindowPointer(winId) || null;
75
+ }
76
+
77
+ function getCoreLastError(): string | null {
78
+ const error = core?.symbols.electrobun_core_last_error();
79
+ if (!error) {
80
+ return null;
81
+ }
82
+
83
+ const message = error.toString();
84
+ return message.length > 0 ? message : null;
85
+ }
86
+
87
+ let webviewRuntimeConfigured = false;
88
+
89
+ function ensureWebviewRuntimeConfigured() {
90
+ if (webviewRuntimeConfigured) {
91
+ return;
92
+ }
93
+
94
+ const configured = core?.symbols.configureWebviewRuntime(
95
+ 0,
96
+ toCString(preloadScript),
97
+ toCString(preloadScriptSandboxed),
98
+ );
99
+
100
+ if (!configured) {
101
+ throw getCoreLastError() || "Failed to configure webview runtime";
102
+ }
103
+
104
+ webviewRuntimeConfigured = true;
105
+ }
106
+
107
+ const core = (() => {
108
+ try {
109
+ const binDir = dirname(process.argv0);
110
+ const corePath = join(
111
+ binDir,
112
+ process.platform === "win32"
113
+ ? "ElectrobunCore.dll"
114
+ : `libElectrobunCore.${suffix}`,
115
+ );
116
+ return dlopen(corePath, {
117
+ electrobun_core_last_error: {
118
+ args: [],
119
+ returns: FFIType.cstring,
120
+ },
121
+ getWindowStyle: {
122
+ args: [
123
+ FFIType.bool,
124
+ FFIType.bool,
125
+ FFIType.bool,
126
+ FFIType.bool,
127
+ FFIType.bool,
128
+ FFIType.bool,
129
+ FFIType.bool,
130
+ FFIType.bool,
131
+ FFIType.bool,
132
+ FFIType.bool,
133
+ FFIType.bool,
134
+ FFIType.bool,
135
+ ],
136
+ returns: FFIType.u32,
137
+ },
138
+ createWindow: {
139
+ args: [
140
+ FFIType.f64,
141
+ FFIType.f64,
142
+ FFIType.f64,
143
+ FFIType.f64,
144
+ FFIType.u32,
145
+ FFIType.cstring,
146
+ FFIType.bool,
147
+ FFIType.cstring,
148
+ FFIType.bool,
149
+ FFIType.bool,
150
+ FFIType.f64,
151
+ FFIType.f64,
152
+ FFIType.function,
153
+ FFIType.function,
154
+ FFIType.function,
155
+ FFIType.function,
156
+ FFIType.function,
157
+ FFIType.function,
158
+ ],
159
+ returns: FFIType.u32,
160
+ },
161
+ getWindowPointer: {
162
+ args: [FFIType.u32],
163
+ returns: FFIType.ptr,
164
+ },
165
+ setWindowTitle: {
166
+ args: [FFIType.u32, FFIType.cstring],
167
+ returns: FFIType.void,
168
+ },
169
+ minimizeWindow: {
170
+ args: [FFIType.u32],
171
+ returns: FFIType.void,
172
+ },
173
+ restoreWindow: {
174
+ args: [FFIType.u32],
175
+ returns: FFIType.void,
176
+ },
177
+ isWindowMinimized: {
178
+ args: [FFIType.u32],
179
+ returns: FFIType.bool,
180
+ },
181
+ maximizeWindow: {
182
+ args: [FFIType.u32],
183
+ returns: FFIType.void,
184
+ },
185
+ unmaximizeWindow: {
186
+ args: [FFIType.u32],
187
+ returns: FFIType.void,
188
+ },
189
+ isWindowMaximized: {
190
+ args: [FFIType.u32],
191
+ returns: FFIType.bool,
192
+ },
193
+ showWindow: {
194
+ args: [FFIType.u32, FFIType.bool],
195
+ returns: FFIType.void,
196
+ },
197
+ activateWindow: {
198
+ args: [FFIType.u32],
199
+ returns: FFIType.void,
200
+ },
201
+ hideWindow: {
202
+ args: [FFIType.u32],
203
+ returns: FFIType.void,
204
+ },
205
+ closeWindow: {
206
+ args: [FFIType.u32],
207
+ returns: FFIType.void,
208
+ },
209
+ setWindowFullScreen: {
210
+ args: [FFIType.u32, FFIType.bool],
211
+ returns: FFIType.void,
212
+ },
213
+ isWindowFullScreen: {
214
+ args: [FFIType.u32],
215
+ returns: FFIType.bool,
216
+ },
217
+ setWindowAlwaysOnTop: {
218
+ args: [FFIType.u32, FFIType.bool],
219
+ returns: FFIType.void,
220
+ },
221
+ isWindowAlwaysOnTop: {
222
+ args: [FFIType.u32],
223
+ returns: FFIType.bool,
224
+ },
225
+ setWindowVisibleOnAllWorkspaces: {
226
+ args: [FFIType.u32, FFIType.bool],
227
+ returns: FFIType.void,
228
+ },
229
+ isWindowVisibleOnAllWorkspaces: {
230
+ args: [FFIType.u32],
231
+ returns: FFIType.bool,
232
+ },
233
+ setWindowPosition: {
234
+ args: [FFIType.u32, FFIType.f64, FFIType.f64],
235
+ returns: FFIType.void,
236
+ },
237
+ setWindowButtonPosition: {
238
+ args: [FFIType.u32, FFIType.f64, FFIType.f64],
239
+ returns: FFIType.void,
240
+ },
241
+ setWindowSize: {
242
+ args: [FFIType.u32, FFIType.f64, FFIType.f64],
243
+ returns: FFIType.void,
244
+ },
245
+ setWindowFrame: {
246
+ args: [FFIType.u32, FFIType.f64, FFIType.f64, FFIType.f64, FFIType.f64],
247
+ returns: FFIType.void,
248
+ },
249
+ getWindowFrame: {
250
+ args: [FFIType.u32, FFIType.ptr, FFIType.ptr, FFIType.ptr, FFIType.ptr],
251
+ returns: FFIType.void,
252
+ },
253
+ configureWebviewRuntime: {
254
+ args: [
255
+ FFIType.u32,
256
+ FFIType.cstring,
257
+ FFIType.cstring,
258
+ ],
259
+ returns: FFIType.bool,
260
+ },
261
+ createWebview: {
262
+ args: [
263
+ FFIType.u32,
264
+ FFIType.u32,
265
+ FFIType.cstring,
266
+ FFIType.cstring,
267
+ FFIType.f64,
268
+ FFIType.f64,
269
+ FFIType.f64,
270
+ FFIType.f64,
271
+ FFIType.bool,
272
+ FFIType.cstring,
273
+ FFIType.function,
274
+ FFIType.function,
275
+ FFIType.function,
276
+ FFIType.function,
277
+ FFIType.function,
278
+ FFIType.cstring,
279
+ FFIType.cstring,
280
+ FFIType.cstring,
281
+ FFIType.bool,
282
+ FFIType.bool,
283
+ FFIType.bool,
284
+ ],
285
+ returns: FFIType.u32,
286
+ },
287
+ getWebviewPointer: {
288
+ args: [FFIType.u32],
289
+ returns: FFIType.ptr,
290
+ },
291
+ resizeWebview: {
292
+ args: [
293
+ FFIType.u32,
294
+ FFIType.f64,
295
+ FFIType.f64,
296
+ FFIType.f64,
297
+ FFIType.f64,
298
+ FFIType.cstring,
299
+ ],
300
+ returns: FFIType.void,
301
+ },
302
+ loadURLInWebView: {
303
+ args: [FFIType.u32, FFIType.cstring],
304
+ returns: FFIType.void,
305
+ },
306
+ loadHTMLInWebView: {
307
+ args: [FFIType.u32, FFIType.cstring],
308
+ returns: FFIType.void,
309
+ },
310
+ updatePreloadScriptToWebView: {
311
+ args: [FFIType.u32, FFIType.cstring, FFIType.cstring, FFIType.bool],
312
+ returns: FFIType.void,
313
+ },
314
+ webviewCanGoBack: {
315
+ args: [FFIType.u32],
316
+ returns: FFIType.bool,
317
+ },
318
+ webviewCanGoForward: {
319
+ args: [FFIType.u32],
320
+ returns: FFIType.bool,
321
+ },
322
+ webviewGoBack: {
323
+ args: [FFIType.u32],
324
+ returns: FFIType.void,
325
+ },
326
+ webviewGoForward: {
327
+ args: [FFIType.u32],
328
+ returns: FFIType.void,
329
+ },
330
+ webviewReload: {
331
+ args: [FFIType.u32],
332
+ returns: FFIType.void,
333
+ },
334
+ webviewRemove: {
335
+ args: [FFIType.u32],
336
+ returns: FFIType.void,
337
+ },
338
+ setWebviewHTMLContent: {
339
+ args: [FFIType.u32, FFIType.cstring],
340
+ returns: FFIType.void,
341
+ },
342
+ webviewSetTransparent: {
343
+ args: [FFIType.u32, FFIType.bool],
344
+ returns: FFIType.void,
345
+ },
346
+ webviewSetPassthrough: {
347
+ args: [FFIType.u32, FFIType.bool],
348
+ returns: FFIType.void,
349
+ },
350
+ webviewSetHidden: {
351
+ args: [FFIType.u32, FFIType.bool],
352
+ returns: FFIType.void,
353
+ },
354
+ setWebviewNavigationRules: {
355
+ args: [FFIType.u32, FFIType.cstring],
356
+ returns: FFIType.void,
357
+ },
358
+ webviewFindInPage: {
359
+ args: [FFIType.u32, FFIType.cstring, FFIType.bool, FFIType.bool],
360
+ returns: FFIType.void,
361
+ },
362
+ webviewStopFind: {
363
+ args: [FFIType.u32],
364
+ returns: FFIType.void,
365
+ },
366
+ evaluateJavaScriptWithNoCompletion: {
367
+ args: [FFIType.u32, FFIType.cstring],
368
+ returns: FFIType.void,
369
+ },
370
+ sendHostMessageToWebviewViaTransport: {
371
+ args: [FFIType.u32, FFIType.cstring],
372
+ returns: FFIType.bool,
373
+ },
374
+ popNextQueuedHostMessage: {
375
+ args: [FFIType.ptr],
376
+ returns: FFIType.ptr,
377
+ },
378
+ getHostMessageWakeupReadFD: {
379
+ args: [],
380
+ returns: FFIType.int,
381
+ },
382
+ freeCoreString: {
383
+ args: [FFIType.ptr],
384
+ returns: FFIType.void,
385
+ },
386
+ clearWebviewHostTransport: {
387
+ args: [FFIType.u32],
388
+ returns: FFIType.void,
389
+ },
390
+ dispatchHostWebviewEvent: {
391
+ args: [FFIType.u32, FFIType.cstring, FFIType.cstring],
392
+ returns: FFIType.bool,
393
+ },
394
+ sendInternalMessageToWebview: {
395
+ args: [FFIType.u32, FFIType.cstring],
396
+ returns: FFIType.bool,
397
+ },
398
+ webviewOpenDevTools: {
399
+ args: [FFIType.u32],
400
+ returns: FFIType.void,
401
+ },
402
+ webviewCloseDevTools: {
403
+ args: [FFIType.u32],
404
+ returns: FFIType.void,
405
+ },
406
+ webviewToggleDevTools: {
407
+ args: [FFIType.u32],
408
+ returns: FFIType.void,
409
+ },
410
+ webviewSetPageZoom: {
411
+ args: [FFIType.u32, FFIType.f64],
412
+ returns: FFIType.void,
413
+ },
414
+ webviewGetPageZoom: {
415
+ args: [FFIType.u32],
416
+ returns: FFIType.f64,
417
+ },
418
+ createWGPUView: {
419
+ args: [
420
+ FFIType.u32,
421
+ FFIType.f64,
422
+ FFIType.f64,
423
+ FFIType.f64,
424
+ FFIType.f64,
425
+ FFIType.bool,
426
+ FFIType.bool,
427
+ FFIType.bool,
428
+ ],
429
+ returns: FFIType.u32,
430
+ },
431
+ getWGPUViewPointer: {
432
+ args: [FFIType.u32],
433
+ returns: FFIType.ptr,
434
+ },
435
+ setWGPUViewFrame: {
436
+ args: [FFIType.u32, FFIType.f64, FFIType.f64, FFIType.f64, FFIType.f64],
437
+ returns: FFIType.void,
438
+ },
439
+ resizeWGPUView: {
440
+ args: [FFIType.u32, FFIType.f64, FFIType.f64, FFIType.f64, FFIType.f64, FFIType.cstring],
441
+ returns: FFIType.void,
442
+ },
443
+ setWGPUViewTransparent: {
444
+ args: [FFIType.u32, FFIType.bool],
445
+ returns: FFIType.void,
446
+ },
447
+ setWGPUViewPassthrough: {
448
+ args: [FFIType.u32, FFIType.bool],
449
+ returns: FFIType.void,
450
+ },
451
+ setWGPUViewHidden: {
452
+ args: [FFIType.u32, FFIType.bool],
453
+ returns: FFIType.void,
454
+ },
455
+ removeWGPUView: {
456
+ args: [FFIType.u32],
457
+ returns: FFIType.void,
458
+ },
459
+ getWGPUViewNativeHandle: {
460
+ args: [FFIType.u32],
461
+ returns: FFIType.ptr,
462
+ },
463
+ runWGPUViewTest: {
464
+ args: [FFIType.u32],
465
+ returns: FFIType.void,
466
+ },
467
+ createTray: {
468
+ args: [
469
+ FFIType.cstring,
470
+ FFIType.cstring,
471
+ FFIType.bool,
472
+ FFIType.u32,
473
+ FFIType.u32,
474
+ FFIType.function,
475
+ ],
476
+ returns: FFIType.u32,
477
+ },
478
+ showTray: {
479
+ args: [FFIType.u32],
480
+ returns: FFIType.bool,
481
+ },
482
+ hideTray: {
483
+ args: [FFIType.u32],
484
+ returns: FFIType.void,
485
+ },
486
+ setTrayTitle: {
487
+ args: [FFIType.u32, FFIType.cstring],
488
+ returns: FFIType.void,
489
+ },
490
+ setTrayImage: {
491
+ args: [FFIType.u32, FFIType.cstring],
492
+ returns: FFIType.void,
493
+ },
494
+ setTrayMenu: {
495
+ args: [FFIType.u32, FFIType.cstring],
496
+ returns: FFIType.void,
497
+ },
498
+ removeTray: {
499
+ args: [FFIType.u32],
500
+ returns: FFIType.void,
501
+ },
502
+ getTrayBounds: {
503
+ args: [FFIType.u32],
504
+ returns: FFIType.cstring,
505
+ },
506
+ setApplicationMenu: {
507
+ args: [FFIType.cstring, FFIType.function],
508
+ returns: FFIType.void,
509
+ },
510
+ showContextMenu: {
511
+ args: [FFIType.cstring, FFIType.function],
512
+ returns: FFIType.void,
513
+ },
514
+ moveToTrash: {
515
+ args: [FFIType.cstring],
516
+ returns: FFIType.bool,
517
+ },
518
+ showItemInFolder: {
519
+ args: [FFIType.cstring],
520
+ returns: FFIType.void,
521
+ },
522
+ openExternal: {
523
+ args: [FFIType.cstring],
524
+ returns: FFIType.bool,
525
+ },
526
+ openPath: {
527
+ args: [FFIType.cstring],
528
+ returns: FFIType.bool,
529
+ },
530
+ showNotification: {
531
+ args: [
532
+ FFIType.cstring,
533
+ FFIType.cstring,
534
+ FFIType.cstring,
535
+ FFIType.bool,
536
+ ],
537
+ returns: FFIType.void,
538
+ },
539
+ getAllDisplays: {
540
+ args: [],
541
+ returns: FFIType.cstring,
542
+ },
543
+ getPrimaryDisplay: {
544
+ args: [],
545
+ returns: FFIType.cstring,
546
+ },
547
+ getCursorScreenPoint: {
548
+ args: [],
549
+ returns: FFIType.cstring,
550
+ },
551
+ getMouseButtons: {
552
+ args: [],
553
+ returns: FFIType.u64,
554
+ },
555
+ openFileDialog: {
556
+ args: [
557
+ FFIType.cstring,
558
+ FFIType.cstring,
559
+ FFIType.int,
560
+ FFIType.int,
561
+ FFIType.int,
562
+ ],
563
+ returns: FFIType.cstring,
564
+ },
565
+ showMessageBox: {
566
+ args: [
567
+ FFIType.cstring,
568
+ FFIType.cstring,
569
+ FFIType.cstring,
570
+ FFIType.cstring,
571
+ FFIType.cstring,
572
+ FFIType.int,
573
+ FFIType.int,
574
+ ],
575
+ returns: FFIType.int,
576
+ },
577
+ clipboardReadText: {
578
+ args: [],
579
+ returns: FFIType.cstring,
580
+ },
581
+ clipboardWriteText: {
582
+ args: [FFIType.cstring],
583
+ returns: FFIType.void,
584
+ },
585
+ clipboardReadImage: {
586
+ args: [FFIType.ptr],
587
+ returns: FFIType.ptr,
588
+ },
589
+ clipboardWriteImage: {
590
+ args: [FFIType.ptr, FFIType.u64],
591
+ returns: FFIType.void,
592
+ },
593
+ clipboardClear: {
594
+ args: [],
595
+ returns: FFIType.void,
596
+ },
597
+ clipboardAvailableFormats: {
598
+ args: [],
599
+ returns: FFIType.cstring,
600
+ },
601
+ setDockIconVisible: {
602
+ args: [FFIType.bool],
603
+ returns: FFIType.void,
604
+ },
605
+ isDockIconVisible: {
606
+ args: [],
607
+ returns: FFIType.bool,
608
+ },
609
+ setExitOnLastWindowClosed: {
610
+ args: [FFIType.bool],
611
+ returns: FFIType.void,
612
+ },
613
+ setQuitRequestedHandler: {
614
+ args: [FFIType.function],
615
+ returns: FFIType.void,
616
+ },
617
+ quitGracefully: {
618
+ args: [FFIType.i32, FFIType.i32],
619
+ returns: FFIType.void,
620
+ },
621
+ });
622
+ } catch {
623
+ return null;
624
+ }
625
+ })();
626
+
627
+ export const native = (() => {
628
+ try {
629
+ const binDir = dirname(process.argv0);
630
+ const nativeWrapperPath = join(binDir, `libNativeWrapper.${suffix}`);
631
+ return dlopen(nativeWrapperPath, {
632
+ // webview
633
+ initWebview: {
634
+ args: [
635
+ FFIType.u32, // webviewId
636
+ FFIType.ptr, // windowPtr
637
+ FFIType.cstring, // renderer
638
+ FFIType.cstring, // url
639
+ FFIType.f64,
640
+ FFIType.f64, // x, y
641
+ FFIType.f64,
642
+ FFIType.f64, // width, height
643
+ FFIType.bool, // autoResize
644
+ FFIType.cstring, // partition
645
+ FFIType.function, // decideNavigation: *const fn (u32, [*:0]const u8) callconv(.C) bool,
646
+ FFIType.function, // webviewEventHandler: *const fn (u32, [*:0]const u8, [*:0]const u8) callconv(.C) void,
647
+ FFIType.function, // eventBridgeHandler: *const fn (u32, [*:0]const u8) callconv(.C) void (events only, always active)
648
+ FFIType.function, // hostBridgePostmessageHandler: *const fn (u32, [*:0]const u8) callconv(.C) void (user RPC, disabled in sandbox)
649
+ FFIType.function, // internalBridgeHandler: *const fn (u32, [*:0]const u8) callconv(.C) void (internal RPC, disabled in sandbox)
650
+ FFIType.cstring, // sparkbunPreloadScript
651
+ FFIType.cstring, // customPreloadScript
652
+ FFIType.cstring, // viewsRoot
653
+ FFIType.bool, // transparent
654
+ FFIType.bool, // sandbox - when true, hostBridge and internalBridge are not set up
655
+ ],
656
+ returns: FFIType.ptr,
657
+ },
658
+ initWGPUView: {
659
+ args: [
660
+ FFIType.u32, // viewId
661
+ FFIType.ptr, // windowPtr
662
+ FFIType.f64,
663
+ FFIType.f64, // x, y
664
+ FFIType.f64,
665
+ FFIType.f64, // width, height
666
+ FFIType.bool, // autoResize
667
+ FFIType.bool, // startTransparent
668
+ FFIType.bool, // startPassthrough
669
+ ],
670
+ returns: FFIType.ptr,
671
+ },
672
+ // Pre-set flags for the next initWebview call (workaround for FFI param count limits)
673
+ setNextWebviewFlags: {
674
+ args: [
675
+ FFIType.bool, // startTransparent
676
+ FFIType.bool, // startPassthrough
677
+ ],
678
+ returns: FFIType.void,
679
+ },
680
+
681
+ // webviewtag
682
+ webviewCanGoBack: {
683
+ args: [FFIType.ptr],
684
+ returns: FFIType.bool,
685
+ },
686
+
687
+ webviewCanGoForward: {
688
+ args: [FFIType.ptr],
689
+ returns: FFIType.bool,
690
+ },
691
+ // Note: callAsyncJavaScript not implemented - CEF doesn't support this directly.
692
+ // Users can use RPC for JavaScript execution.
693
+ resizeWebview: {
694
+ args: [
695
+ FFIType.ptr, // webview handle
696
+ FFIType.f64, // x
697
+ FFIType.f64, // y
698
+ FFIType.f64, // width
699
+ FFIType.f64, // height
700
+ FFIType.cstring, // maskJson
701
+ ],
702
+ returns: FFIType.void,
703
+ },
704
+
705
+ loadURLInWebView: {
706
+ args: [FFIType.ptr, FFIType.cstring],
707
+ returns: FFIType.void,
708
+ },
709
+ loadHTMLInWebView: {
710
+ args: [FFIType.ptr, FFIType.cstring],
711
+ returns: FFIType.void,
712
+ },
713
+
714
+ updatePreloadScriptToWebView: {
715
+ args: [
716
+ FFIType.ptr, // webview handle
717
+ FFIType.cstring, // script identifier
718
+ FFIType.cstring, // script
719
+ FFIType.bool, // allframes
720
+ ],
721
+ returns: FFIType.void,
722
+ },
723
+ webviewGoBack: {
724
+ args: [FFIType.ptr],
725
+ returns: FFIType.void,
726
+ },
727
+ webviewGoForward: {
728
+ args: [FFIType.ptr],
729
+ returns: FFIType.void,
730
+ },
731
+ webviewReload: {
732
+ args: [FFIType.ptr],
733
+ returns: FFIType.void,
734
+ },
735
+ webviewRemove: {
736
+ args: [FFIType.ptr],
737
+ returns: FFIType.void,
738
+ },
739
+ setWebviewHTMLContent: {
740
+ args: [FFIType.u32, FFIType.cstring],
741
+ returns: FFIType.void,
742
+ },
743
+ startWindowMove: {
744
+ args: [FFIType.ptr],
745
+ returns: FFIType.void,
746
+ },
747
+ stopWindowMove: {
748
+ args: [],
749
+ returns: FFIType.void,
750
+ },
751
+ webviewSetTransparent: {
752
+ args: [FFIType.ptr, FFIType.bool],
753
+ returns: FFIType.void,
754
+ },
755
+ webviewSetPassthrough: {
756
+ args: [FFIType.ptr, FFIType.bool],
757
+ returns: FFIType.void,
758
+ },
759
+ webviewSetHidden: {
760
+ args: [FFIType.ptr, FFIType.bool],
761
+ returns: FFIType.void,
762
+ },
763
+ setWebviewNavigationRules: {
764
+ args: [FFIType.ptr, FFIType.cstring],
765
+ returns: FFIType.void,
766
+ },
767
+ webviewFindInPage: {
768
+ args: [FFIType.ptr, FFIType.cstring, FFIType.bool, FFIType.bool],
769
+ returns: FFIType.void,
770
+ },
771
+ webviewStopFind: {
772
+ args: [FFIType.ptr],
773
+ returns: FFIType.void,
774
+ },
775
+ evaluateJavaScriptWithNoCompletion: {
776
+ args: [FFIType.ptr, FFIType.cstring],
777
+ returns: FFIType.void,
778
+ },
779
+ webviewOpenDevTools: {
780
+ args: [FFIType.ptr],
781
+ returns: FFIType.void,
782
+ },
783
+ webviewCloseDevTools: {
784
+ args: [FFIType.ptr],
785
+ returns: FFIType.void,
786
+ },
787
+ webviewToggleDevTools: {
788
+ args: [FFIType.ptr],
789
+ returns: FFIType.void,
790
+ },
791
+ webviewSetPageZoom: {
792
+ args: [FFIType.ptr, FFIType.f64],
793
+ returns: FFIType.void,
794
+ },
795
+ webviewGetPageZoom: {
796
+ args: [FFIType.ptr],
797
+ returns: FFIType.f64,
798
+ },
799
+ wgpuViewSetFrame: {
800
+ args: [
801
+ FFIType.ptr,
802
+ FFIType.f64,
803
+ FFIType.f64,
804
+ FFIType.f64,
805
+ FFIType.f64,
806
+ ],
807
+ returns: FFIType.void,
808
+ },
809
+ wgpuViewSetTransparent: {
810
+ args: [FFIType.ptr, FFIType.bool],
811
+ returns: FFIType.void,
812
+ },
813
+ wgpuViewSetPassthrough: {
814
+ args: [FFIType.ptr, FFIType.bool],
815
+ returns: FFIType.void,
816
+ },
817
+ wgpuViewSetHidden: {
818
+ args: [FFIType.ptr, FFIType.bool],
819
+ returns: FFIType.void,
820
+ },
821
+ wgpuViewRemove: {
822
+ args: [FFIType.ptr],
823
+ returns: FFIType.void,
824
+ },
825
+ wgpuViewGetNativeHandle: {
826
+ args: [FFIType.ptr],
827
+ returns: FFIType.ptr,
828
+ },
829
+ wgpuInstanceCreateSurfaceMainThread: {
830
+ args: [FFIType.ptr, FFIType.ptr],
831
+ returns: FFIType.ptr,
832
+ },
833
+ wgpuSurfaceConfigureMainThread: {
834
+ args: [FFIType.ptr, FFIType.ptr],
835
+ returns: FFIType.void,
836
+ },
837
+ wgpuSurfaceGetCurrentTextureMainThread: {
838
+ args: [FFIType.ptr, FFIType.ptr],
839
+ returns: FFIType.void,
840
+ },
841
+ wgpuSurfacePresentMainThread: {
842
+ args: [FFIType.ptr],
843
+ returns: FFIType.i32,
844
+ },
845
+ wgpuQueueOnSubmittedWorkDoneShim: {
846
+ args: [FFIType.ptr, FFIType.ptr],
847
+ returns: FFIType.u64,
848
+ },
849
+ wgpuBufferMapAsyncShim: {
850
+ args: [FFIType.ptr, FFIType.u64, FFIType.u64, FFIType.u64, FFIType.ptr],
851
+ returns: FFIType.u64,
852
+ },
853
+ wgpuInstanceWaitAnyShim: {
854
+ args: [FFIType.ptr, FFIType.u64, FFIType.u64],
855
+ returns: FFIType.i32,
856
+ },
857
+ wgpuBufferReadSyncShim: {
858
+ args: [FFIType.ptr, FFIType.ptr, FFIType.u64, FFIType.u64, FFIType.u64, FFIType.ptr],
859
+ returns: FFIType.ptr,
860
+ },
861
+ wgpuBufferReadSyncIntoShim: {
862
+ args: [FFIType.ptr, FFIType.ptr, FFIType.u64, FFIType.u64, FFIType.u64, FFIType.ptr],
863
+ returns: FFIType.i32,
864
+ },
865
+ wgpuBufferReadbackBeginShim: {
866
+ args: [FFIType.ptr, FFIType.u64, FFIType.u64, FFIType.ptr],
867
+ returns: FFIType.ptr,
868
+ },
869
+ wgpuBufferReadbackStatusShim: {
870
+ args: [FFIType.ptr],
871
+ returns: FFIType.i32,
872
+ },
873
+ wgpuBufferReadbackFreeShim: {
874
+ args: [FFIType.ptr],
875
+ returns: FFIType.void,
876
+ },
877
+ wgpuRunGPUTest: {
878
+ args: [FFIType.ptr],
879
+ returns: FFIType.void,
880
+ },
881
+ wgpuCreateAdapterDeviceMainThread: {
882
+ args: [FFIType.ptr, FFIType.ptr, FFIType.ptr],
883
+ returns: FFIType.void,
884
+ },
885
+ wgpuCreateSurfaceForView: {
886
+ args: [FFIType.ptr, FFIType.ptr],
887
+ returns: FFIType.ptr,
888
+ },
889
+ // Global keyboard shortcuts
890
+ setGlobalShortcutCallback: {
891
+ args: [FFIType.function],
892
+ returns: FFIType.void,
893
+ },
894
+ registerGlobalShortcut: {
895
+ args: [FFIType.cstring],
896
+ returns: FFIType.bool,
897
+ },
898
+ unregisterGlobalShortcut: {
899
+ args: [FFIType.cstring],
900
+ returns: FFIType.bool,
901
+ },
902
+ unregisterAllGlobalShortcuts: {
903
+ args: [],
904
+ returns: FFIType.void,
905
+ },
906
+ isGlobalShortcutRegistered: {
907
+ args: [FFIType.cstring],
908
+ returns: FFIType.bool,
909
+ },
910
+
911
+ // Session/Cookie API
912
+ sessionGetCookies: {
913
+ args: [FFIType.cstring, FFIType.cstring],
914
+ returns: FFIType.cstring,
915
+ },
916
+ sessionSetCookie: {
917
+ args: [FFIType.cstring, FFIType.cstring],
918
+ returns: FFIType.bool,
919
+ },
920
+ sessionRemoveCookie: {
921
+ args: [FFIType.cstring, FFIType.cstring, FFIType.cstring],
922
+ returns: FFIType.bool,
923
+ },
924
+ sessionClearCookies: {
925
+ args: [FFIType.cstring],
926
+ returns: FFIType.void,
927
+ },
928
+ sessionClearStorageData: {
929
+ args: [FFIType.cstring, FFIType.cstring],
930
+ returns: FFIType.void,
931
+ },
932
+
933
+ // URL scheme handler (macOS only)
934
+ setURLOpenHandler: {
935
+ args: [FFIType.function], // handler callback
936
+ returns: FFIType.void,
937
+ },
938
+ setAppReopenHandler: {
939
+ args: [FFIType.function],
940
+ returns: FFIType.void,
941
+ },
942
+
943
+ // JSCallback utils for native code to use
944
+ setJSUtils: {
945
+ args: [
946
+ FFIType.function, // get Mimetype from url/filename
947
+ FFIType.function, // get html property from webview
948
+ ],
949
+ returns: FFIType.void,
950
+ },
951
+ setWindowIcon: {
952
+ args: [
953
+ FFIType.ptr, // window pointer
954
+ FFIType.cstring, // icon path
955
+ ],
956
+ returns: FFIType.void,
957
+ },
958
+ killApp: {
959
+ args: [],
960
+ returns: FFIType.void,
961
+ },
962
+ stopEventLoop: {
963
+ args: [],
964
+ returns: FFIType.void,
965
+ },
966
+ waitForShutdownComplete: {
967
+ args: [FFIType.i32],
968
+ returns: FFIType.void,
969
+ },
970
+ forceExit: {
971
+ args: [FFIType.i32],
972
+ returns: FFIType.void,
973
+ },
974
+ setQuitRequestedHandler: {
975
+ args: [FFIType.function],
976
+ returns: FFIType.void,
977
+ },
978
+ testFFI2: {
979
+ args: [FFIType.function],
980
+ returns: FFIType.void,
981
+ },
982
+ });
983
+ } catch (err) {
984
+ throw new Error(`Failed to load native FFI libraries: ${err}`);
985
+ }
986
+ })();
987
+
988
+ // NOTE: Bun seems to hit limits on args or arg types. eg: trying to send 12 bools results
989
+ // in only about 8 going through then params after that. I think it may be similar to
990
+ // a zig bug I ran into last year. So check number of args in a signature when alignment issues occur.
991
+
992
+ // Non-null accessor for use inside _ffiImpl — these methods are only called when hasFFI is true.
993
+ const core_ = core!;
994
+ const native_ = native!;
995
+ const queuedHostMessageWebviewIdBuf = new Uint32Array(1);
996
+
997
+ const drainQueuedHostMessages = () => {
998
+ if (!core) {
999
+ return;
1000
+ }
1001
+
1002
+ for (;;) {
1003
+ const messagePtr = core_.symbols.popNextQueuedHostMessage(
1004
+ ptr(queuedHostMessageWebviewIdBuf),
1005
+ ) as Pointer | null;
1006
+
1007
+ if (!messagePtr) {
1008
+ return;
1009
+ }
1010
+
1011
+ try {
1012
+ const rawMessage = new CString(messagePtr).toString();
1013
+ if (!rawMessage) {
1014
+ continue;
1015
+ }
1016
+
1017
+ const webview = BrowserView.ensureWrapped(
1018
+ queuedHostMessageWebviewIdBuf[0]!,
1019
+ );
1020
+ if (!webview) {
1021
+ continue;
1022
+ }
1023
+
1024
+ webview.rpcHandler?.(JSON.parse(rawMessage));
1025
+ } catch (err) {
1026
+ console.error("error draining queued host message:", err);
1027
+ } finally {
1028
+ core_.symbols.freeCoreString(messagePtr);
1029
+ }
1030
+ }
1031
+ };
1032
+
1033
+ if (core) {
1034
+ const wakeupReadFd = core_.symbols.getHostMessageWakeupReadFD();
1035
+
1036
+ if (typeof wakeupReadFd === "number" && wakeupReadFd >= 0) {
1037
+ try {
1038
+ const wakeupStream = createReadStream("/dev/null", {
1039
+ fd: wakeupReadFd,
1040
+ autoClose: false,
1041
+ });
1042
+ wakeupStream.on("data", () => {
1043
+ drainQueuedHostMessages();
1044
+ });
1045
+ wakeupStream.on("error", (error) => {
1046
+ console.error("host message wakeup stream failed, falling back to polling:", error);
1047
+ setInterval(drainQueuedHostMessages, 16);
1048
+ });
1049
+ } catch (error) {
1050
+ console.error("failed to start host message wakeup stream, falling back to polling:", error);
1051
+ setInterval(drainQueuedHostMessages, 16);
1052
+ }
1053
+ } else {
1054
+ setInterval(drainQueuedHostMessages, 16);
1055
+ }
1056
+
1057
+ drainQueuedHostMessages();
1058
+ }
1059
+
1060
+ const _ffiImpl = {
1061
+ request: {
1062
+ createWindow: (params: {
1063
+ url: string | null;
1064
+ title: string;
1065
+ frame: {
1066
+ width: number;
1067
+ height: number;
1068
+ x: number;
1069
+ y: number;
1070
+ };
1071
+ styleMask: {
1072
+ Borderless: boolean;
1073
+ Titled: boolean;
1074
+ Closable: boolean;
1075
+ Miniaturizable: boolean;
1076
+ Resizable: boolean;
1077
+ UnifiedTitleAndToolbar: boolean;
1078
+ FullScreen: boolean;
1079
+ FullSizeContentView: boolean;
1080
+ UtilityWindow: boolean;
1081
+ DocModalWindow: boolean;
1082
+ NonactivatingPanel: boolean;
1083
+ HUDWindow: boolean;
1084
+ };
1085
+ titleBarStyle: string;
1086
+ transparent: boolean;
1087
+ hidden?: boolean;
1088
+ activate?: boolean;
1089
+ trafficLightOffset?: {
1090
+ x: number;
1091
+ y: number;
1092
+ };
1093
+ }): number => {
1094
+ const {
1095
+ url: _url,
1096
+ title,
1097
+ frame: { x, y, width, height },
1098
+ styleMask: {
1099
+ Borderless,
1100
+ Titled,
1101
+ Closable,
1102
+ Miniaturizable,
1103
+ Resizable,
1104
+ UnifiedTitleAndToolbar,
1105
+ FullScreen,
1106
+ FullSizeContentView,
1107
+ UtilityWindow,
1108
+ DocModalWindow,
1109
+ NonactivatingPanel,
1110
+ HUDWindow,
1111
+ },
1112
+ titleBarStyle,
1113
+ transparent,
1114
+ hidden = false,
1115
+ activate = true,
1116
+ trafficLightOffset = { x: 0, y: 0 },
1117
+ } = params;
1118
+
1119
+ const styleMask = core_.symbols.getWindowStyle(
1120
+ Borderless,
1121
+ Titled,
1122
+ Closable,
1123
+ Miniaturizable,
1124
+ Resizable,
1125
+ UnifiedTitleAndToolbar,
1126
+ FullScreen,
1127
+ FullSizeContentView,
1128
+ UtilityWindow,
1129
+ DocModalWindow,
1130
+ NonactivatingPanel,
1131
+ HUDWindow,
1132
+ );
1133
+
1134
+ const windowId = core_.symbols.createWindow(
1135
+ // frame
1136
+ x,
1137
+ y,
1138
+ width,
1139
+ height,
1140
+ styleMask,
1141
+ // style
1142
+ toCString(titleBarStyle),
1143
+ transparent,
1144
+ toCString(title),
1145
+ hidden,
1146
+ activate,
1147
+ trafficLightOffset.x,
1148
+ trafficLightOffset.y,
1149
+ // callbacks
1150
+ windowCloseCallback,
1151
+ windowMoveCallback,
1152
+ windowResizeCallback,
1153
+ windowFocusCallback,
1154
+ windowBlurCallback,
1155
+ windowKeyCallback,
1156
+ );
1157
+
1158
+ if (!windowId) {
1159
+ throw getCoreLastError() || "Failed to create window";
1160
+ }
1161
+
1162
+ return windowId;
1163
+ },
1164
+ getWindowPointer: (params: { winId: number }): Pointer | null => {
1165
+ return getWindowPtr(params.winId);
1166
+ },
1167
+ setTitle: (params: { winId: number; title: string }) => {
1168
+ const { winId, title } = params;
1169
+ const windowPtr = getWindowPtr(winId);
1170
+
1171
+ if (!windowPtr) {
1172
+ throw `Can't set window title. Window no longer exists`;
1173
+ }
1174
+
1175
+ core_.symbols.setWindowTitle(winId, toCString(title));
1176
+ },
1177
+
1178
+ closeWindow: (params: { winId: number }) => {
1179
+ const { winId } = params;
1180
+ const windowPtr = getWindowPtr(winId);
1181
+
1182
+ if (!windowPtr) {
1183
+ // Window already closed — silently ignore the race condition
1184
+ return;
1185
+ }
1186
+
1187
+ core_.symbols.closeWindow(winId);
1188
+ // Note: Cleanup of BrowserWindowMap happens in the windowCloseCallback
1189
+ },
1190
+
1191
+ showWindow: (params: { winId: number; activate?: boolean }) => {
1192
+ const { winId } = params;
1193
+ const windowPtr = getWindowPtr(winId);
1194
+
1195
+ if (!windowPtr) {
1196
+ throw `Can't show window. Window no longer exists`;
1197
+ }
1198
+
1199
+ core_.symbols.showWindow(winId, params.activate ?? true);
1200
+ },
1201
+
1202
+ activateWindow: (params: { winId: number }) => {
1203
+ const { winId } = params;
1204
+ const windowPtr = getWindowPtr(winId);
1205
+
1206
+ if (!windowPtr) {
1207
+ throw `Can't activate window. Window no longer exists`;
1208
+ }
1209
+
1210
+ core_.symbols.activateWindow(winId);
1211
+ },
1212
+
1213
+ hideWindow: (params: { winId: number }) => {
1214
+ const { winId } = params;
1215
+ const windowPtr = getWindowPtr(winId);
1216
+
1217
+ if (!windowPtr) {
1218
+ throw `Can't hide window. Window no longer exists`;
1219
+ }
1220
+
1221
+ core_.symbols.hideWindow(winId);
1222
+ },
1223
+
1224
+ minimizeWindow: (params: { winId: number }) => {
1225
+ const { winId } = params;
1226
+ const windowPtr = getWindowPtr(winId);
1227
+
1228
+ if (!windowPtr) {
1229
+ throw `Can't minimize window. Window no longer exists`;
1230
+ }
1231
+
1232
+ core_.symbols.minimizeWindow(winId);
1233
+ },
1234
+
1235
+ restoreWindow: (params: { winId: number }) => {
1236
+ const { winId } = params;
1237
+ const windowPtr = getWindowPtr(winId);
1238
+
1239
+ if (!windowPtr) {
1240
+ throw `Can't restore window. Window no longer exists`;
1241
+ }
1242
+
1243
+ core_.symbols.restoreWindow(winId);
1244
+ },
1245
+
1246
+ isWindowMinimized: (params: { winId: number }): boolean => {
1247
+ const { winId } = params;
1248
+ const windowPtr = getWindowPtr(winId);
1249
+
1250
+ if (!windowPtr) {
1251
+ return false;
1252
+ }
1253
+
1254
+ return core_.symbols.isWindowMinimized(winId);
1255
+ },
1256
+
1257
+ maximizeWindow: (params: { winId: number }) => {
1258
+ const { winId } = params;
1259
+ const windowPtr = getWindowPtr(winId);
1260
+
1261
+ if (!windowPtr) {
1262
+ throw `Can't maximize window. Window no longer exists`;
1263
+ }
1264
+
1265
+ core_.symbols.maximizeWindow(winId);
1266
+ },
1267
+
1268
+ unmaximizeWindow: (params: { winId: number }) => {
1269
+ const { winId } = params;
1270
+ const windowPtr = getWindowPtr(winId);
1271
+
1272
+ if (!windowPtr) {
1273
+ throw `Can't unmaximize window. Window no longer exists`;
1274
+ }
1275
+
1276
+ core_.symbols.unmaximizeWindow(winId);
1277
+ },
1278
+
1279
+ isWindowMaximized: (params: { winId: number }): boolean => {
1280
+ const { winId } = params;
1281
+ const windowPtr = getWindowPtr(winId);
1282
+
1283
+ if (!windowPtr) {
1284
+ return false;
1285
+ }
1286
+
1287
+ return core_.symbols.isWindowMaximized(winId);
1288
+ },
1289
+
1290
+ setWindowFullScreen: (params: { winId: number; fullScreen: boolean }) => {
1291
+ const { winId, fullScreen } = params;
1292
+ const windowPtr = getWindowPtr(winId);
1293
+
1294
+ if (!windowPtr) {
1295
+ throw `Can't set fullscreen. Window no longer exists`;
1296
+ }
1297
+
1298
+ core_.symbols.setWindowFullScreen(winId, fullScreen);
1299
+ },
1300
+
1301
+ isWindowFullScreen: (params: { winId: number }): boolean => {
1302
+ const { winId } = params;
1303
+ const windowPtr = getWindowPtr(winId);
1304
+
1305
+ if (!windowPtr) {
1306
+ return false;
1307
+ }
1308
+
1309
+ return core_.symbols.isWindowFullScreen(winId);
1310
+ },
1311
+
1312
+ setWindowAlwaysOnTop: (params: { winId: number; alwaysOnTop: boolean }) => {
1313
+ const { winId, alwaysOnTop } = params;
1314
+ const windowPtr = getWindowPtr(winId);
1315
+
1316
+ if (!windowPtr) {
1317
+ throw `Can't set always on top. Window no longer exists`;
1318
+ }
1319
+
1320
+ core_.symbols.setWindowAlwaysOnTop(winId, alwaysOnTop);
1321
+ },
1322
+
1323
+ isWindowAlwaysOnTop: (params: { winId: number }): boolean => {
1324
+ const { winId } = params;
1325
+ const windowPtr = getWindowPtr(winId);
1326
+
1327
+ if (!windowPtr) {
1328
+ return false;
1329
+ }
1330
+
1331
+ return core_.symbols.isWindowAlwaysOnTop(winId);
1332
+ },
1333
+
1334
+ setWindowVisibleOnAllWorkspaces: (params: {
1335
+ winId: number;
1336
+ visibleOnAllWorkspaces: boolean;
1337
+ }) => {
1338
+ const { winId, visibleOnAllWorkspaces } = params;
1339
+ const windowPtr = getWindowPtr(winId);
1340
+
1341
+ if (!windowPtr) {
1342
+ throw `Can't set visible on all workspaces. Window no longer exists`;
1343
+ }
1344
+
1345
+ core_.symbols.setWindowVisibleOnAllWorkspaces(
1346
+ winId,
1347
+ visibleOnAllWorkspaces,
1348
+ );
1349
+ },
1350
+
1351
+ isWindowVisibleOnAllWorkspaces: (params: { winId: number }): boolean => {
1352
+ const { winId } = params;
1353
+ const windowPtr = getWindowPtr(winId);
1354
+
1355
+ if (!windowPtr) {
1356
+ return false;
1357
+ }
1358
+
1359
+ return core_.symbols.isWindowVisibleOnAllWorkspaces(winId);
1360
+ },
1361
+
1362
+ setWindowPosition: (params: { winId: number; x: number; y: number }) => {
1363
+ const { winId, x, y } = params;
1364
+ const windowPtr = getWindowPtr(winId);
1365
+
1366
+ if (!windowPtr) {
1367
+ throw `Can't set window position. Window no longer exists`;
1368
+ }
1369
+
1370
+ core_.symbols.setWindowPosition(winId, x, y);
1371
+ },
1372
+
1373
+ setWindowButtonPosition: (params: { winId: number; x: number; y: number }) => {
1374
+ const { winId, x, y } = params;
1375
+ const windowPtr = getWindowPtr(winId);
1376
+
1377
+ if (!windowPtr) {
1378
+ throw `Can't set window button position. Window no longer exists`;
1379
+ }
1380
+
1381
+ core_.symbols.setWindowButtonPosition(winId, x, y);
1382
+ },
1383
+
1384
+ setWindowSize: (params: {
1385
+ winId: number;
1386
+ width: number;
1387
+ height: number;
1388
+ }) => {
1389
+ const { winId, width, height } = params;
1390
+ const windowPtr = getWindowPtr(winId);
1391
+
1392
+ if (!windowPtr) {
1393
+ throw `Can't set window size. Window no longer exists`;
1394
+ }
1395
+
1396
+ core_.symbols.setWindowSize(winId, width, height);
1397
+ },
1398
+
1399
+ setWindowFrame: (params: {
1400
+ winId: number;
1401
+ x: number;
1402
+ y: number;
1403
+ width: number;
1404
+ height: number;
1405
+ }) => {
1406
+ const { winId, x, y, width, height } = params;
1407
+ const windowPtr = getWindowPtr(winId);
1408
+
1409
+ if (!windowPtr) {
1410
+ throw `Can't set window frame. Window no longer exists`;
1411
+ }
1412
+
1413
+ core_.symbols.setWindowFrame(winId, x, y, width, height);
1414
+ },
1415
+
1416
+ getWindowFrame: (params: {
1417
+ winId: number;
1418
+ }): { x: number; y: number; width: number; height: number } => {
1419
+ const { winId } = params;
1420
+ const windowPtr = getWindowPtr(winId);
1421
+
1422
+ if (!windowPtr) {
1423
+ return { x: 0, y: 0, width: 0, height: 0 };
1424
+ }
1425
+
1426
+ // Create buffers to receive the output values
1427
+ const xBuf = new Float64Array(1);
1428
+ const yBuf = new Float64Array(1);
1429
+ const widthBuf = new Float64Array(1);
1430
+ const heightBuf = new Float64Array(1);
1431
+
1432
+ core_.symbols.getWindowFrame(
1433
+ winId,
1434
+ ptr(xBuf),
1435
+ ptr(yBuf),
1436
+ ptr(widthBuf),
1437
+ ptr(heightBuf),
1438
+ );
1439
+
1440
+ return {
1441
+ x: xBuf[0]!,
1442
+ y: yBuf[0]!,
1443
+ width: widthBuf[0]!,
1444
+ height: heightBuf[0]!,
1445
+ };
1446
+ },
1447
+ createWebview: (params: {
1448
+ windowId: number;
1449
+ hostWebviewId: number | null;
1450
+ renderer: "cef" | "native";
1451
+ secretKey: string;
1452
+ url: string | null;
1453
+ partition: string | null;
1454
+ preload: string | null;
1455
+ viewsRoot: string | null;
1456
+ frame: {
1457
+ x: number;
1458
+ y: number;
1459
+ width: number;
1460
+ height: number;
1461
+ };
1462
+ autoResize: boolean;
1463
+ navigationRules: string | null;
1464
+ sandbox: boolean;
1465
+ startTransparent: boolean;
1466
+ startPassthrough: boolean;
1467
+ }): number => {
1468
+ const {
1469
+ windowId,
1470
+ hostWebviewId,
1471
+ renderer,
1472
+ secretKey,
1473
+ url,
1474
+ partition,
1475
+ preload,
1476
+ viewsRoot,
1477
+ frame: { x, y, width, height },
1478
+ autoResize,
1479
+ sandbox,
1480
+ startTransparent,
1481
+ startPassthrough,
1482
+ } = params;
1483
+ ensureWebviewRuntimeConfigured();
1484
+
1485
+ const webviewId = core_.symbols.createWebview(
1486
+ windowId,
1487
+ hostWebviewId || 0,
1488
+ toCString(renderer),
1489
+ toCString(url || ""),
1490
+ x,
1491
+ y,
1492
+ width,
1493
+ height,
1494
+ autoResize,
1495
+ toCString(partition || "persist:default"),
1496
+ webviewDecideNavigation,
1497
+ webviewEventJSCallback,
1498
+ eventBridgeHandler,
1499
+ hostBridgePostmessageHandler,
1500
+ internalBridgeHandler,
1501
+ toCString(secretKey),
1502
+ toCString(preload || ""),
1503
+ toCString(viewsRoot || ""),
1504
+ sandbox, // When true, hostBridge and internalBridge are not set up in native code
1505
+ startTransparent,
1506
+ startPassthrough,
1507
+ );
1508
+
1509
+ if (!webviewId) {
1510
+ throw getCoreLastError() || "Failed to create webview";
1511
+ }
1512
+
1513
+ return webviewId;
1514
+ },
1515
+ getWebviewPointer: (params: { id: number }): Pointer | null => {
1516
+ return core_.symbols.getWebviewPointer(params.id) || null;
1517
+ },
1518
+ resizeWebview: (params: {
1519
+ id: number;
1520
+ frame: { x: number; y: number; width: number; height: number };
1521
+ masks?: string;
1522
+ }) => {
1523
+ const { id, frame: { x, y, width, height }, masks = "[]" } = params;
1524
+ core_.symbols.resizeWebview(id, x, y, width, height, toCString(masks));
1525
+ },
1526
+ loadURLInWebView: (params: { id: number; url: string }) => {
1527
+ core_.symbols.loadURLInWebView(params.id, toCString(params.url));
1528
+ },
1529
+ loadHTMLInWebView: (params: { id: number; html: string }) => {
1530
+ core_.symbols.loadHTMLInWebView(params.id, toCString(params.html));
1531
+ },
1532
+ updatePreloadScriptToWebView: (params: {
1533
+ id: number;
1534
+ scriptIdentifier: string;
1535
+ script: string;
1536
+ allFrames: boolean;
1537
+ }) => {
1538
+ core_.symbols.updatePreloadScriptToWebView(
1539
+ params.id,
1540
+ toCString(params.scriptIdentifier),
1541
+ toCString(params.script),
1542
+ params.allFrames,
1543
+ );
1544
+ },
1545
+ webviewCanGoBack: (params: { id: number }) => {
1546
+ return core_.symbols.webviewCanGoBack(params.id);
1547
+ },
1548
+ webviewCanGoForward: (params: { id: number }) => {
1549
+ return core_.symbols.webviewCanGoForward(params.id);
1550
+ },
1551
+ webviewGoBack: (params: { id: number }) => {
1552
+ core_.symbols.webviewGoBack(params.id);
1553
+ },
1554
+ webviewGoForward: (params: { id: number }) => {
1555
+ core_.symbols.webviewGoForward(params.id);
1556
+ },
1557
+ webviewReload: (params: { id: number }) => {
1558
+ core_.symbols.webviewReload(params.id);
1559
+ },
1560
+ webviewRemove: (params: { id: number }) => {
1561
+ core_.symbols.webviewRemove(params.id);
1562
+ },
1563
+ setWebviewHTMLContent: (params: { id: number; html: string }) => {
1564
+ core_.symbols.setWebviewHTMLContent(params.id, toCString(params.html));
1565
+ },
1566
+ webviewSetTransparent: (params: { id: number; transparent: boolean }) => {
1567
+ core_.symbols.webviewSetTransparent(params.id, params.transparent);
1568
+ },
1569
+ webviewSetPassthrough: (params: { id: number; passthrough: boolean }) => {
1570
+ core_.symbols.webviewSetPassthrough(params.id, params.passthrough);
1571
+ },
1572
+ webviewSetHidden: (params: { id: number; hidden: boolean }) => {
1573
+ core_.symbols.webviewSetHidden(params.id, params.hidden);
1574
+ },
1575
+ setWebviewNavigationRules: (params: { id: number; rulesJson: string }) => {
1576
+ core_.symbols.setWebviewNavigationRules(params.id, toCString(params.rulesJson));
1577
+ },
1578
+ webviewFindInPage: (params: {
1579
+ id: number;
1580
+ searchText: string;
1581
+ forward: boolean;
1582
+ matchCase: boolean;
1583
+ }) => {
1584
+ core_.symbols.webviewFindInPage(
1585
+ params.id,
1586
+ toCString(params.searchText),
1587
+ params.forward,
1588
+ params.matchCase,
1589
+ );
1590
+ },
1591
+ webviewStopFind: (params: { id: number }) => {
1592
+ core_.symbols.webviewStopFind(params.id);
1593
+ },
1594
+
1595
+ createWGPUView: (params: {
1596
+ windowId: number;
1597
+ frame: {
1598
+ x: number;
1599
+ y: number;
1600
+ width: number;
1601
+ height: number;
1602
+ };
1603
+ autoResize: boolean;
1604
+ startTransparent: boolean;
1605
+ startPassthrough: boolean;
1606
+ }): number => {
1607
+ const {
1608
+ windowId,
1609
+ frame: { x, y, width, height },
1610
+ autoResize,
1611
+ startTransparent,
1612
+ startPassthrough,
1613
+ } = params;
1614
+
1615
+ const viewId = core_.symbols.createWGPUView(
1616
+ windowId,
1617
+ x,
1618
+ y,
1619
+ width,
1620
+ height,
1621
+ autoResize,
1622
+ startTransparent,
1623
+ startPassthrough,
1624
+ );
1625
+
1626
+ if (!viewId) {
1627
+ throw "Failed to create WGPUView";
1628
+ }
1629
+
1630
+ return viewId;
1631
+ },
1632
+ getWGPUViewPointer: (params: { id: number }): Pointer | null => {
1633
+ return core_.symbols.getWGPUViewPointer(params.id) || null;
1634
+ },
1635
+
1636
+ wgpuViewSetFrame: (params: {
1637
+ id: number;
1638
+ x: number;
1639
+ y: number;
1640
+ width: number;
1641
+ height: number;
1642
+ }) => {
1643
+ core_.symbols.setWGPUViewFrame(
1644
+ params.id,
1645
+ params.x,
1646
+ params.y,
1647
+ params.width,
1648
+ params.height,
1649
+ );
1650
+ },
1651
+
1652
+ wgpuViewSetTransparent: (params: { id: number; transparent: boolean }) => {
1653
+ core_.symbols.setWGPUViewTransparent(params.id, params.transparent);
1654
+ },
1655
+
1656
+ wgpuViewSetPassthrough: (params: {
1657
+ id: number;
1658
+ passthrough: boolean;
1659
+ }) => {
1660
+ core_.symbols.setWGPUViewPassthrough(params.id, params.passthrough);
1661
+ },
1662
+
1663
+ wgpuViewSetHidden: (params: { id: number; hidden: boolean }) => {
1664
+ core_.symbols.setWGPUViewHidden(params.id, params.hidden);
1665
+ },
1666
+
1667
+ wgpuViewRemove: (params: { id: number }) => {
1668
+ core_.symbols.removeWGPUView(params.id);
1669
+ },
1670
+ wgpuViewGetNativeHandle: (params: { id: number }): Pointer | null => {
1671
+ return core_.symbols.getWGPUViewNativeHandle(params.id) || null;
1672
+ },
1673
+ runWGPUViewTest: (params: { id: number }) => {
1674
+ core_.symbols.runWGPUViewTest(params.id);
1675
+ },
1676
+
1677
+ evaluateJavascriptWithNoCompletion: (params: {
1678
+ id: number;
1679
+ js: string;
1680
+ }) => {
1681
+ core_.symbols.evaluateJavaScriptWithNoCompletion(
1682
+ params.id,
1683
+ toCString(params.js),
1684
+ );
1685
+ },
1686
+ sendHostMessageToWebviewViaTransport: (params: {
1687
+ id: number;
1688
+ messageJson: string;
1689
+ }): boolean => {
1690
+ return core_.symbols.sendHostMessageToWebviewViaTransport(
1691
+ params.id,
1692
+ toCString(params.messageJson),
1693
+ );
1694
+ },
1695
+ clearWebviewHostTransport: (params: { id: number }) => {
1696
+ core_.symbols.clearWebviewHostTransport(params.id);
1697
+ },
1698
+ webviewOpenDevTools: (params: { id: number }) => {
1699
+ core_.symbols.webviewOpenDevTools(params.id);
1700
+ },
1701
+ webviewCloseDevTools: (params: { id: number }) => {
1702
+ core_.symbols.webviewCloseDevTools(params.id);
1703
+ },
1704
+ webviewToggleDevTools: (params: { id: number }) => {
1705
+ core_.symbols.webviewToggleDevTools(params.id);
1706
+ },
1707
+ webviewSetPageZoom: (params: { id: number; zoomLevel: number }) => {
1708
+ core_.symbols.webviewSetPageZoom(params.id, params.zoomLevel);
1709
+ },
1710
+ webviewGetPageZoom: (params: { id: number }): number => {
1711
+ return core_.symbols.webviewGetPageZoom(params.id);
1712
+ },
1713
+ setExitOnLastWindowClosed: (params: { enabled: boolean }) => {
1714
+ core_.symbols.setExitOnLastWindowClosed(params.enabled);
1715
+ },
1716
+ quitGracefully: (params: { code: number; timeoutMs: number }) => {
1717
+ core_.symbols.quitGracefully(params.code, params.timeoutMs);
1718
+ },
1719
+
1720
+ createTray: (params: {
1721
+ title: string;
1722
+ image: string;
1723
+ template: boolean;
1724
+ width: number;
1725
+ height: number;
1726
+ }): number => {
1727
+ const { title, image, template, width, height } = params;
1728
+
1729
+ const trayId = core_.symbols.createTray(
1730
+ toCString(title),
1731
+ toCString(image),
1732
+ template,
1733
+ width,
1734
+ height,
1735
+ trayItemHandler,
1736
+ );
1737
+
1738
+ if (!trayId) {
1739
+ throw "Failed to create tray";
1740
+ }
1741
+
1742
+ return trayId;
1743
+ },
1744
+ showTray: (params: { id: number }): boolean => {
1745
+ return core_.symbols.showTray(params.id);
1746
+ },
1747
+ hideTray: (params: { id: number }): void => {
1748
+ core_.symbols.hideTray(params.id);
1749
+ },
1750
+ setTrayTitle: (params: { id: number; title: string }): void => {
1751
+ const { id, title } = params;
1752
+ core_.symbols.setTrayTitle(id, toCString(title));
1753
+ },
1754
+ setTrayImage: (params: { id: number; image: string }): void => {
1755
+ const { id, image } = params;
1756
+ core_.symbols.setTrayImage(id, toCString(image));
1757
+ },
1758
+ setTrayMenu: (params: {
1759
+ id: number;
1760
+ // json string of config
1761
+ menuConfig: string;
1762
+ }): void => {
1763
+ const { id, menuConfig } = params;
1764
+ core_.symbols.setTrayMenu(id, toCString(menuConfig));
1765
+ },
1766
+
1767
+ removeTray: (params: { id: number }): void => {
1768
+ core_.symbols.removeTray(params.id);
1769
+ },
1770
+ getTrayBounds: (params: { id: number }): Rectangle => {
1771
+ const jsonStr = core_.symbols.getTrayBounds(params.id);
1772
+ if (!jsonStr) {
1773
+ return { x: 0, y: 0, width: 0, height: 0 };
1774
+ }
1775
+
1776
+ try {
1777
+ return JSON.parse(jsonStr.toString());
1778
+ } catch {
1779
+ return { x: 0, y: 0, width: 0, height: 0 };
1780
+ }
1781
+ },
1782
+ setApplicationMenu: (params: { menuConfig: string }): void => {
1783
+ const { menuConfig } = params;
1784
+
1785
+ core_.symbols.setApplicationMenu(
1786
+ toCString(menuConfig),
1787
+ applicationMenuHandler,
1788
+ );
1789
+ },
1790
+ showContextMenu: (params: { menuConfig: string }): void => {
1791
+ const { menuConfig } = params;
1792
+
1793
+ core_.symbols.showContextMenu(toCString(menuConfig), contextMenuHandler);
1794
+ },
1795
+ moveToTrash: (params: { path: string }): boolean => {
1796
+ const { path } = params;
1797
+
1798
+ return core_.symbols.moveToTrash(toCString(path));
1799
+ },
1800
+ showItemInFolder: (params: { path: string }): void => {
1801
+ const { path } = params;
1802
+
1803
+ core_.symbols.showItemInFolder(toCString(path));
1804
+ },
1805
+ openExternal: (params: { url: string }): boolean => {
1806
+ const { url } = params;
1807
+ return core_.symbols.openExternal(toCString(url));
1808
+ },
1809
+ openPath: (params: { path: string }): boolean => {
1810
+ const { path } = params;
1811
+ return core_.symbols.openPath(toCString(path));
1812
+ },
1813
+ showNotification: (params: {
1814
+ title: string;
1815
+ body?: string;
1816
+ subtitle?: string;
1817
+ silent?: boolean;
1818
+ }): void => {
1819
+ const { title, body = "", subtitle = "", silent = false } = params;
1820
+ core_.symbols.showNotification(
1821
+ toCString(title),
1822
+ toCString(body),
1823
+ toCString(subtitle),
1824
+ silent,
1825
+ );
1826
+ },
1827
+ setDockIconVisible: (params: { visible: boolean }): void => {
1828
+ core_.symbols.setDockIconVisible(params.visible);
1829
+ },
1830
+ isDockIconVisible: (): boolean => {
1831
+ return core_.symbols.isDockIconVisible();
1832
+ },
1833
+ openFileDialog: (params: {
1834
+ startingFolder: string;
1835
+ allowedFileTypes: string;
1836
+ canChooseFiles: boolean;
1837
+ canChooseDirectory: boolean;
1838
+ allowsMultipleSelection: boolean;
1839
+ }): string => {
1840
+ const {
1841
+ startingFolder,
1842
+ allowedFileTypes,
1843
+ canChooseFiles,
1844
+ canChooseDirectory,
1845
+ allowsMultipleSelection,
1846
+ } = params;
1847
+ const filePath = core_.symbols.openFileDialog(
1848
+ toCString(startingFolder),
1849
+ toCString(allowedFileTypes),
1850
+ canChooseFiles ? 1 : 0,
1851
+ canChooseDirectory ? 1 : 0,
1852
+ allowsMultipleSelection ? 1 : 0,
1853
+ );
1854
+
1855
+ return filePath.toString();
1856
+ },
1857
+ showMessageBox: (params: {
1858
+ type?: string;
1859
+ title?: string;
1860
+ message?: string;
1861
+ detail?: string;
1862
+ buttons?: string[];
1863
+ defaultId?: number;
1864
+ cancelId?: number;
1865
+ }): number => {
1866
+ const {
1867
+ type = "info",
1868
+ title = "",
1869
+ message = "",
1870
+ detail = "",
1871
+ buttons = ["OK"],
1872
+ defaultId = 0,
1873
+ cancelId = -1,
1874
+ } = params;
1875
+ // Convert buttons array to comma-separated string
1876
+ const buttonsStr = buttons.join(",");
1877
+ return core_.symbols.showMessageBox(
1878
+ toCString(type),
1879
+ toCString(title),
1880
+ toCString(message),
1881
+ toCString(detail),
1882
+ toCString(buttonsStr),
1883
+ defaultId,
1884
+ cancelId,
1885
+ );
1886
+ },
1887
+
1888
+ // Clipboard API
1889
+ clipboardReadText: (): string | null => {
1890
+ const result = core_.symbols.clipboardReadText();
1891
+ if (!result) return null;
1892
+ return result.toString();
1893
+ },
1894
+ clipboardWriteText: (params: { text: string }): void => {
1895
+ core_.symbols.clipboardWriteText(toCString(params.text));
1896
+ },
1897
+ clipboardReadImage: (): Uint8Array | null => {
1898
+ // Allocate a buffer for the size output
1899
+ const sizeBuffer = new BigUint64Array(1);
1900
+ const dataPtr = core_.symbols.clipboardReadImage(ptr(sizeBuffer));
1901
+
1902
+ if (!dataPtr) return null;
1903
+
1904
+ const size = Number(sizeBuffer[0]);
1905
+ if (size === 0) return null;
1906
+
1907
+ // Copy the data to a Uint8Array
1908
+ const result = new Uint8Array(size);
1909
+ const sourceView = new Uint8Array(toArrayBuffer(dataPtr, 0, size));
1910
+ result.set(sourceView);
1911
+
1912
+ // Note: The native code allocated this memory with malloc
1913
+ // We should free it, but Bun's FFI doesn't expose free directly
1914
+ // The memory will be reclaimed when the process exits
1915
+
1916
+ return result;
1917
+ },
1918
+ clipboardWriteImage: (params: { pngData: Uint8Array }): void => {
1919
+ const { pngData } = params;
1920
+ core_.symbols.clipboardWriteImage(ptr(pngData), BigInt(pngData.length));
1921
+ },
1922
+ clipboardClear: (): void => {
1923
+ core_.symbols.clipboardClear();
1924
+ },
1925
+ clipboardAvailableFormats: (): string[] => {
1926
+ const result = core_.symbols.clipboardAvailableFormats();
1927
+ if (!result) return [];
1928
+ const formatsStr = result.toString();
1929
+ if (!formatsStr) return [];
1930
+ return formatsStr.split(",").filter((f) => f.length > 0);
1931
+ },
1932
+
1933
+ // ffifunc: (params: {}): void => {
1934
+ // const {
1935
+
1936
+ // } = params;
1937
+
1938
+ // native_.symbols.ffifunc(
1939
+
1940
+ // );
1941
+ // },
1942
+ },
1943
+ // Internal functions for menu data management
1944
+ internal: {
1945
+ storeMenuData,
1946
+ getMenuData,
1947
+ clearMenuData,
1948
+ serializeMenuAction,
1949
+ deserializeMenuAction,
1950
+ },
1951
+ };
1952
+
1953
+ export const ffi = {
1954
+ request: _ffiImpl.request,
1955
+ internal: _ffiImpl.internal,
1956
+ };
1957
+
1958
+ export const WGPUBridge = {
1959
+ available: !!native?.symbols?.wgpuInstanceCreateSurfaceMainThread,
1960
+ instanceCreateSurface: (instancePtr: Pointer, descriptorPtr: Pointer): Pointer =>
1961
+ native_.symbols.wgpuInstanceCreateSurfaceMainThread(
1962
+ instancePtr as any,
1963
+ descriptorPtr as any,
1964
+ ) as Pointer,
1965
+ surfaceConfigure: (surfacePtr: Pointer, configPtr: Pointer) =>
1966
+ native_.symbols.wgpuSurfaceConfigureMainThread(
1967
+ surfacePtr as any,
1968
+ configPtr as any,
1969
+ ),
1970
+ surfaceGetCurrentTexture: (surfacePtr: Pointer, surfaceTexturePtr: Pointer) =>
1971
+ native_.symbols.wgpuSurfaceGetCurrentTextureMainThread(
1972
+ surfacePtr as any,
1973
+ surfaceTexturePtr as any,
1974
+ ),
1975
+ surfacePresent: (surfacePtr: Pointer): number =>
1976
+ native_.symbols.wgpuSurfacePresentMainThread(surfacePtr as any),
1977
+ queueOnSubmittedWorkDone: (queuePtr: Pointer, callbackInfoPtr: Pointer): bigint =>
1978
+ native_.symbols.wgpuQueueOnSubmittedWorkDoneShim(
1979
+ queuePtr as any,
1980
+ callbackInfoPtr as any,
1981
+ ),
1982
+ bufferMapAsync: (
1983
+ bufferPtr: Pointer,
1984
+ mode: bigint,
1985
+ offset: bigint,
1986
+ size: bigint,
1987
+ callbackInfoPtr: Pointer,
1988
+ ): bigint =>
1989
+ native_.symbols.wgpuBufferMapAsyncShim(
1990
+ bufferPtr as any,
1991
+ mode as any,
1992
+ offset as any,
1993
+ size as any,
1994
+ callbackInfoPtr as any,
1995
+ ),
1996
+ instanceWaitAny: (
1997
+ instancePtr: Pointer,
1998
+ futureId: bigint,
1999
+ timeoutNs: bigint,
2000
+ ): number =>
2001
+ native_.symbols.wgpuInstanceWaitAnyShim(
2002
+ instancePtr as any,
2003
+ futureId as any,
2004
+ timeoutNs as any,
2005
+ ),
2006
+ bufferReadSync: (
2007
+ instancePtr: Pointer,
2008
+ bufferPtr: Pointer,
2009
+ offset: bigint,
2010
+ size: bigint,
2011
+ timeoutNs: bigint,
2012
+ outSizePtr: Pointer,
2013
+ ): Pointer =>
2014
+ native_.symbols.wgpuBufferReadSyncShim(
2015
+ instancePtr as any,
2016
+ bufferPtr as any,
2017
+ offset as any,
2018
+ size as any,
2019
+ timeoutNs as any,
2020
+ outSizePtr as any,
2021
+ ) as Pointer,
2022
+ bufferReadSyncInto: (
2023
+ instancePtr: Pointer,
2024
+ bufferPtr: Pointer,
2025
+ offset: bigint,
2026
+ size: bigint,
2027
+ timeoutNs: bigint,
2028
+ dstPtr: Pointer,
2029
+ ): number =>
2030
+ native_.symbols.wgpuBufferReadSyncIntoShim(
2031
+ instancePtr as any,
2032
+ bufferPtr as any,
2033
+ offset as any,
2034
+ size as any,
2035
+ timeoutNs as any,
2036
+ dstPtr as any,
2037
+ ),
2038
+ bufferReadbackBegin: (
2039
+ bufferPtr: Pointer,
2040
+ offset: bigint,
2041
+ size: bigint,
2042
+ dstPtr: Pointer,
2043
+ ): Pointer =>
2044
+ native_.symbols.wgpuBufferReadbackBeginShim(
2045
+ bufferPtr as any,
2046
+ offset as any,
2047
+ size as any,
2048
+ dstPtr as any,
2049
+ ) as Pointer,
2050
+ bufferReadbackStatus: (jobPtr: Pointer): number =>
2051
+ native_.symbols.wgpuBufferReadbackStatusShim(jobPtr as any),
2052
+ bufferReadbackFree: (jobPtr: Pointer) =>
2053
+ native_.symbols.wgpuBufferReadbackFreeShim(jobPtr as any),
2054
+ runTest: (viewId: number) => {
2055
+ const view = WGPUView.getById(viewId);
2056
+ if (!view?.ptr) {
2057
+ console.error(`wgpuRunGPUTest: WGPUView not found for id ${viewId}`);
2058
+ return;
2059
+ }
2060
+ if (!native?.symbols?.wgpuRunGPUTest) {
2061
+ console.error("wgpuRunGPUTest not available");
2062
+ return;
2063
+ }
2064
+ native_.symbols.wgpuRunGPUTest(view.ptr);
2065
+ },
2066
+ createAdapterDeviceMainThread: (
2067
+ instancePtr: Pointer,
2068
+ surfacePtr: Pointer,
2069
+ outAdapterDevicePtr: Pointer,
2070
+ ) =>
2071
+ native_.symbols.wgpuCreateAdapterDeviceMainThread(
2072
+ instancePtr as any,
2073
+ surfacePtr as any,
2074
+ outAdapterDevicePtr as any,
2075
+ ),
2076
+ createSurfaceForView: (instancePtr: Pointer, viewPtr: Pointer): Pointer | null => {
2077
+ if (!native?.symbols?.wgpuCreateSurfaceForView) return null;
2078
+ return native_.symbols.wgpuCreateSurfaceForView(instancePtr as any, viewPtr as any) as Pointer;
2079
+ },
2080
+ };
2081
+
2082
+
2083
+ // Worker management. Move to a different file
2084
+ process.on("uncaughtException", (err) => {
2085
+ console.error("Uncaught exception in worker:", err);
2086
+ if (native) {
2087
+ native_.symbols.stopEventLoop();
2088
+ native_.symbols.waitForShutdownComplete(5000);
2089
+ native_.symbols.forceExit(1);
2090
+ } else {
2091
+ process.exit(1);
2092
+ }
2093
+ });
2094
+
2095
+ process.on("unhandledRejection", (reason, _promise) => {
2096
+ console.error("Unhandled rejection in worker:", reason);
2097
+ });
2098
+
2099
+ process.on("SIGINT", () => {
2100
+ console.log("[sparkbun] Received SIGINT, running quit sequence...");
2101
+ const { quit } = require("../core/Utils");
2102
+ quit();
2103
+ });
2104
+
2105
+ process.on("SIGTERM", () => {
2106
+ console.log("[sparkbun] Received SIGTERM, running quit sequence...");
2107
+ const { quit } = require("../core/Utils");
2108
+ quit();
2109
+ });
2110
+
2111
+ // const testCallback = new JSCallback(
2112
+ // (windowId, x, y) => {
2113
+ // console.log(`TEST FFI Callback reffed GLOBALLY in js`);
2114
+ // // Your window move handler implementation
2115
+ // },
2116
+ // {
2117
+ // args: [],
2118
+ // returns: "void",
2119
+ // threadsafe: true,
2120
+
2121
+ // }
2122
+ // );
2123
+
2124
+ const windowCloseCallback = new JSCallback(
2125
+ (id) => {
2126
+ const handler = sparkBunEventEmitter.events.window.close;
2127
+ const event = handler({
2128
+ id,
2129
+ });
2130
+
2131
+ // emit specific event first so user per-window handlers run
2132
+ // before the global handler (e.g. exitOnLastWindowClosed)
2133
+ sparkBunEventEmitter.emitEvent(event, id);
2134
+ sparkBunEventEmitter.emitEvent(event);
2135
+ },
2136
+ {
2137
+ args: ["u32"],
2138
+ returns: "void",
2139
+ threadsafe: true,
2140
+ },
2141
+ );
2142
+
2143
+ const windowMoveCallback = new JSCallback(
2144
+ (id, x, y) => {
2145
+ const handler = sparkBunEventEmitter.events.window.move;
2146
+ const event = handler({
2147
+ id,
2148
+ x,
2149
+ y,
2150
+ });
2151
+
2152
+ // global event
2153
+ sparkBunEventEmitter.emitEvent(event);
2154
+ sparkBunEventEmitter.emitEvent(event, id);
2155
+ },
2156
+ {
2157
+ args: ["u32", "f64", "f64"],
2158
+ returns: "void",
2159
+ threadsafe: true,
2160
+ },
2161
+ );
2162
+
2163
+ const windowResizeCallback = new JSCallback(
2164
+ (id, x, y, width, height) => {
2165
+ const handler = sparkBunEventEmitter.events.window.resize;
2166
+ const event = handler({
2167
+ id,
2168
+ x,
2169
+ y,
2170
+ width,
2171
+ height,
2172
+ });
2173
+
2174
+ // global event
2175
+ sparkBunEventEmitter.emitEvent(event);
2176
+ sparkBunEventEmitter.emitEvent(event, id);
2177
+ },
2178
+ {
2179
+ args: ["u32", "f64", "f64", "f64", "f64"],
2180
+ returns: "void",
2181
+ threadsafe: true,
2182
+ },
2183
+ );
2184
+
2185
+ const windowFocusCallback = new JSCallback(
2186
+ (id) => {
2187
+ const handler = sparkBunEventEmitter.events.window.focus;
2188
+ const event = handler({
2189
+ id,
2190
+ });
2191
+
2192
+ // global event
2193
+ sparkBunEventEmitter.emitEvent(event);
2194
+ sparkBunEventEmitter.emitEvent(event, id);
2195
+ },
2196
+ {
2197
+ args: ["u32"],
2198
+ returns: "void",
2199
+ threadsafe: true,
2200
+ },
2201
+ );
2202
+
2203
+ const windowBlurCallback = new JSCallback(
2204
+ (id) => {
2205
+ const handler = sparkBunEventEmitter.events.window.blur;
2206
+ const event = handler({
2207
+ id,
2208
+ });
2209
+
2210
+ // global event
2211
+ sparkBunEventEmitter.emitEvent(event);
2212
+ sparkBunEventEmitter.emitEvent(event, id);
2213
+ },
2214
+ {
2215
+ args: ["u32"],
2216
+ returns: "void",
2217
+ threadsafe: true,
2218
+ },
2219
+ );
2220
+
2221
+ // global event
2222
+ const windowKeyCallback = new JSCallback(
2223
+ (id, keyCode, modifiers, isDown, isRepeat) => {
2224
+ const handler = isDown
2225
+ ? sparkBunEventEmitter.events.window.keyDown
2226
+ : sparkBunEventEmitter.events.window.keyUp;
2227
+ const event = handler({
2228
+ id,
2229
+ keyCode,
2230
+ modifiers,
2231
+ isRepeat: !!isRepeat,
2232
+ });
2233
+ sparkBunEventEmitter.emitEvent(event);
2234
+ sparkBunEventEmitter.emitEvent(event, id);
2235
+ },
2236
+ {
2237
+ args: ["u32", "u32", "u32", "u32", "u32"],
2238
+ returns: "void",
2239
+ threadsafe: true,
2240
+ },
2241
+ );
2242
+
2243
+ const getMimeType = new JSCallback(
2244
+ (filePath) => {
2245
+ const _filePath = new CString(filePath).toString();
2246
+ const mimeType = Bun.file(_filePath).type; // || "application/octet-stream";
2247
+
2248
+ // For this usecase we generally don't want the charset included in the mimetype
2249
+ // otherwise it can break. eg: for html with text/javascript;charset=utf-8 browsers
2250
+ // will tend to render the code/text instead of interpreting the html.
2251
+
2252
+ return toCString(mimeType.split(";")[0]!);
2253
+ },
2254
+ {
2255
+ args: [FFIType.cstring],
2256
+ returns: FFIType.cstring,
2257
+ // threadsafe: true
2258
+ },
2259
+ );
2260
+
2261
+ const getHTMLForWebviewSync = new JSCallback(
2262
+ (webviewId) => {
2263
+ const webview = BrowserView.ensureWrapped(webviewId);
2264
+
2265
+ return toCString(webview?.html || "");
2266
+ },
2267
+ {
2268
+ args: [FFIType.u32],
2269
+ returns: FFIType.cstring,
2270
+ // threadsafe: true
2271
+ },
2272
+ );
2273
+
2274
+ if (native) native_.symbols.setJSUtils(getMimeType, getHTMLForWebviewSync);
2275
+
2276
+ // Native-only init: URL scheme handlers, quit handler, global shortcuts.
2277
+ // Skipped when running without FFI (carrot mode).
2278
+ const globalShortcutHandlers = new Map<string, () => void>();
2279
+
2280
+ if (native) {
2281
+ const urlOpenCallback = new JSCallback(
2282
+ (urlPtr) => {
2283
+ const url = new CString(urlPtr).toString();
2284
+ const handler = sparkBunEventEmitter.events.app.openUrl;
2285
+ const event = handler({ url });
2286
+ sparkBunEventEmitter.emitEvent(event);
2287
+ },
2288
+ { args: [FFIType.cstring], returns: "void", threadsafe: true },
2289
+ );
2290
+ if (process.platform === "darwin") {
2291
+ native_.symbols.setURLOpenHandler(urlOpenCallback);
2292
+ }
2293
+
2294
+ const appReopenCallback = new JSCallback(
2295
+ () => {
2296
+ if (process.platform === "darwin") {
2297
+ core_.symbols.setDockIconVisible(true);
2298
+ }
2299
+ const handler = sparkBunEventEmitter.events.app.reopen;
2300
+ const event = handler({});
2301
+ sparkBunEventEmitter.emitEvent(event);
2302
+ },
2303
+ { args: [], returns: "void", threadsafe: true },
2304
+ );
2305
+ if (process.platform === "darwin") {
2306
+ native_.symbols.setAppReopenHandler(appReopenCallback);
2307
+ }
2308
+
2309
+ const quitRequestedCallback = new JSCallback(
2310
+ () => {
2311
+ const { quit } = require("../core/Utils");
2312
+ quit();
2313
+ },
2314
+ { args: [], returns: "void", threadsafe: true },
2315
+ );
2316
+ core_.symbols.setQuitRequestedHandler(quitRequestedCallback);
2317
+
2318
+ const globalShortcutCallback = new JSCallback(
2319
+ (acceleratorPtr) => {
2320
+ const accelerator = new CString(acceleratorPtr).toString();
2321
+ const handler = globalShortcutHandlers.get(accelerator);
2322
+ if (handler) handler();
2323
+ },
2324
+ { args: [FFIType.cstring], returns: "void", threadsafe: true },
2325
+ );
2326
+ native_.symbols.setGlobalShortcutCallback(globalShortcutCallback);
2327
+ }
2328
+
2329
+ // GlobalShortcut module for external use
2330
+ export const GlobalShortcut = {
2331
+ /**
2332
+ * Register a global keyboard shortcut
2333
+ * @param accelerator - The shortcut string (e.g., "CommandOrControl+Shift+Space")
2334
+ * @param callback - Function to call when the shortcut is triggered
2335
+ * @returns true if registered successfully, false otherwise
2336
+ */
2337
+ register: (accelerator: string, callback: () => void): boolean => {
2338
+ if (!native || globalShortcutHandlers.has(accelerator)) return false;
2339
+ const result = native_.symbols.registerGlobalShortcut(toCString(accelerator));
2340
+ if (result) globalShortcutHandlers.set(accelerator, callback);
2341
+ return result;
2342
+ },
2343
+ unregister: (accelerator: string): boolean => {
2344
+ if (!native) return false;
2345
+ const result = native_.symbols.unregisterGlobalShortcut(toCString(accelerator));
2346
+ if (result) globalShortcutHandlers.delete(accelerator);
2347
+ return result;
2348
+ },
2349
+ unregisterAll: (): void => {
2350
+ if (native) native_.symbols.unregisterAllGlobalShortcuts();
2351
+ globalShortcutHandlers.clear();
2352
+ },
2353
+ isRegistered: (accelerator: string): boolean => {
2354
+ if (!native) return false;
2355
+ return native_.symbols.isGlobalShortcutRegistered(toCString(accelerator));
2356
+ },
2357
+ };
2358
+
2359
+ // Types for Screen API
2360
+ export interface Rectangle {
2361
+ x: number;
2362
+ y: number;
2363
+ width: number;
2364
+ height: number;
2365
+ }
2366
+
2367
+ export interface Display {
2368
+ id: number;
2369
+ bounds: Rectangle;
2370
+ workArea: Rectangle;
2371
+ scaleFactor: number;
2372
+ isPrimary: boolean;
2373
+ }
2374
+
2375
+ export interface Point {
2376
+ x: number;
2377
+ y: number;
2378
+ }
2379
+
2380
+ // Screen module for display and cursor information
2381
+ export const Screen = {
2382
+ /**
2383
+ * Get the primary display
2384
+ * @returns Display object for the primary monitor
2385
+ */
2386
+ getPrimaryDisplay: (): Display => {
2387
+ const jsonStr = hasFFI ? core_.symbols.getPrimaryDisplay() : null;
2388
+ if (!jsonStr) {
2389
+ return {
2390
+ id: 0,
2391
+ bounds: { x: 0, y: 0, width: 0, height: 0 },
2392
+ workArea: { x: 0, y: 0, width: 0, height: 0 },
2393
+ scaleFactor: 1,
2394
+ isPrimary: true,
2395
+ };
2396
+ }
2397
+ try {
2398
+ return JSON.parse(jsonStr.toString());
2399
+ } catch {
2400
+ return {
2401
+ id: 0,
2402
+ bounds: { x: 0, y: 0, width: 0, height: 0 },
2403
+ workArea: { x: 0, y: 0, width: 0, height: 0 },
2404
+ scaleFactor: 1,
2405
+ isPrimary: true,
2406
+ };
2407
+ }
2408
+ },
2409
+
2410
+ /**
2411
+ * Get all connected displays
2412
+ * @returns Array of Display objects
2413
+ */
2414
+ getAllDisplays: (): Display[] => {
2415
+ const jsonStr = hasFFI ? core_.symbols.getAllDisplays() : null;
2416
+ if (!jsonStr) {
2417
+ return [];
2418
+ }
2419
+ try {
2420
+ return JSON.parse(jsonStr.toString());
2421
+ } catch {
2422
+ return [];
2423
+ }
2424
+ },
2425
+
2426
+ /**
2427
+ * Get the current cursor position in screen coordinates
2428
+ * @returns Point with x and y coordinates
2429
+ */
2430
+ getCursorScreenPoint: (): Point => {
2431
+ const jsonStr = hasFFI ? core_.symbols.getCursorScreenPoint() : null;
2432
+ if (!jsonStr) {
2433
+ return { x: 0, y: 0 };
2434
+ }
2435
+ try {
2436
+ return JSON.parse(jsonStr.toString());
2437
+ } catch {
2438
+ return { x: 0, y: 0 };
2439
+ }
2440
+ },
2441
+
2442
+ /**
2443
+ * Get current mouse button bitmask (bit 0 = left, bit 1 = right, bit 2 = middle)
2444
+ */
2445
+ getMouseButtons: (): bigint => {
2446
+ try {
2447
+ return hasFFI ? core_.symbols.getMouseButtons() : BigInt(0);
2448
+ } catch {
2449
+ return 0n;
2450
+ }
2451
+ },
2452
+ };
2453
+
2454
+ // Types for Session/Cookie API
2455
+ export interface Cookie {
2456
+ name: string;
2457
+ value: string;
2458
+ domain?: string;
2459
+ path?: string;
2460
+ secure?: boolean;
2461
+ httpOnly?: boolean;
2462
+ sameSite?: "no_restriction" | "lax" | "strict";
2463
+ expirationDate?: number; // Unix timestamp in seconds
2464
+ }
2465
+
2466
+ export interface CookieFilter {
2467
+ url?: string;
2468
+ name?: string;
2469
+ domain?: string;
2470
+ path?: string;
2471
+ secure?: boolean;
2472
+ session?: boolean;
2473
+ }
2474
+
2475
+ export type StorageType =
2476
+ | "cookies"
2477
+ | "localStorage"
2478
+ | "sessionStorage"
2479
+ | "indexedDB"
2480
+ | "webSQL"
2481
+ | "cache"
2482
+ | "all";
2483
+
2484
+ // Cookies API for a session
2485
+ class SessionCookies {
2486
+ private partitionId: string;
2487
+
2488
+ constructor(partitionId: string) {
2489
+ this.partitionId = partitionId;
2490
+ }
2491
+
2492
+ /**
2493
+ * Get cookies matching the filter criteria
2494
+ * @param filter - Optional filter to match cookies
2495
+ * @returns Array of matching cookies
2496
+ */
2497
+ get(filter?: CookieFilter): Cookie[] {
2498
+ const filterJson = JSON.stringify(filter || {});
2499
+ const result = native_.symbols.sessionGetCookies(
2500
+ toCString(this.partitionId),
2501
+ toCString(filterJson),
2502
+ );
2503
+ if (!result) return [];
2504
+ try {
2505
+ return JSON.parse(result.toString());
2506
+ } catch {
2507
+ return [];
2508
+ }
2509
+ }
2510
+
2511
+ /**
2512
+ * Set a cookie
2513
+ * @param cookie - The cookie to set
2514
+ * @returns true if the cookie was set successfully
2515
+ */
2516
+ set(cookie: Cookie): boolean {
2517
+ const cookieJson = JSON.stringify(cookie);
2518
+ return native_.symbols.sessionSetCookie(
2519
+ toCString(this.partitionId),
2520
+ toCString(cookieJson),
2521
+ );
2522
+ }
2523
+
2524
+ /**
2525
+ * Remove a specific cookie
2526
+ * @param url - The URL associated with the cookie
2527
+ * @param name - The name of the cookie
2528
+ * @returns true if the cookie was removed successfully
2529
+ */
2530
+ remove(url: string, name: string): boolean {
2531
+ return native_.symbols.sessionRemoveCookie(
2532
+ toCString(this.partitionId),
2533
+ toCString(url),
2534
+ toCString(name),
2535
+ );
2536
+ }
2537
+
2538
+ /**
2539
+ * Clear all cookies for this session
2540
+ */
2541
+ clear(): void {
2542
+ native_.symbols.sessionClearCookies(toCString(this.partitionId));
2543
+ }
2544
+ }
2545
+
2546
+ // Session class representing a storage partition
2547
+ class SessionInstance {
2548
+ readonly partition: string;
2549
+ readonly cookies: SessionCookies;
2550
+
2551
+ constructor(partition: string) {
2552
+ this.partition = partition;
2553
+ this.cookies = new SessionCookies(partition);
2554
+ }
2555
+
2556
+ /**
2557
+ * Clear storage data for this session
2558
+ * @param types - Array of storage types to clear, or 'all' to clear everything
2559
+ */
2560
+ clearStorageData(types: StorageType[] | "all" = "all"): void {
2561
+ const typesArray = types === "all" ? ["all"] : types;
2562
+ native_.symbols.sessionClearStorageData(
2563
+ toCString(this.partition),
2564
+ toCString(JSON.stringify(typesArray)),
2565
+ );
2566
+ }
2567
+ }
2568
+
2569
+ // Cache of session instances
2570
+ const sessionCache = new Map<string, SessionInstance>();
2571
+
2572
+ // Session module for storage/cookie management
2573
+ export const Session = {
2574
+ /**
2575
+ * Get or create a session for a given partition
2576
+ * @param partition - The partition identifier (e.g., "persist:myapp" or "ephemeral")
2577
+ * @returns Session instance for the partition
2578
+ */
2579
+ fromPartition: (partition: string): SessionInstance => {
2580
+ let session = sessionCache.get(partition);
2581
+ if (!session) {
2582
+ session = new SessionInstance(partition);
2583
+ sessionCache.set(partition, session);
2584
+ }
2585
+ return session;
2586
+ },
2587
+
2588
+ /**
2589
+ * Get the default session (persist:default partition)
2590
+ */
2591
+ get defaultSession(): SessionInstance {
2592
+ return Session.fromPartition("persist:default");
2593
+ },
2594
+ };
2595
+
2596
+ // Stub: native side accepts this parameter but never calls it.
2597
+ // Remove when native wrappers are recompiled without it.
2598
+ const webviewDecideNavigation = null;
2599
+
2600
+ const webviewEventHandler = (id: number, eventName: string, detail: string) => {
2601
+ BrowserView.ensureWrapped(id);
2602
+
2603
+ core_.symbols.dispatchHostWebviewEvent(
2604
+ id,
2605
+ toCString(eventName),
2606
+ toCString(detail),
2607
+ );
2608
+
2609
+ const eventMap: Record<string, string> = {
2610
+ "will-navigate": "willNavigate",
2611
+ "did-navigate": "didNavigate",
2612
+ "did-navigate-in-page": "didNavigateInPage",
2613
+ "did-commit-navigation": "didCommitNavigation",
2614
+ "dom-ready": "domReady",
2615
+ "new-window-open": "newWindowOpen",
2616
+ "host-message": "hostMessage",
2617
+ "download-started": "downloadStarted",
2618
+ "download-progress": "downloadProgress",
2619
+ "download-completed": "downloadCompleted",
2620
+ "download-failed": "downloadFailed",
2621
+ "load-started": "loadStarted",
2622
+ "load-committed": "loadCommitted",
2623
+ "load-finished": "loadFinished",
2624
+ };
2625
+
2626
+ const mappedName = eventMap[eventName];
2627
+ const handler = mappedName
2628
+ ? (sparkBunEventEmitter.events.webview as Record<string, unknown>)[
2629
+ mappedName
2630
+ ]
2631
+ : undefined;
2632
+
2633
+ if (!handler) {
2634
+ // console.error(
2635
+ // "[webviewEventHandler] No handler found for event:",
2636
+ // eventName,
2637
+ // "(mapped to:",
2638
+ // mappedName,
2639
+ // ")",
2640
+ // );
2641
+ return { success: false };
2642
+ }
2643
+
2644
+ // Parse JSON data for events that send JSON
2645
+ let parsedDetail = detail;
2646
+ if (
2647
+ eventName === "new-window-open" ||
2648
+ eventName === "host-message" ||
2649
+ eventName === "download-started" ||
2650
+ eventName === "download-progress" ||
2651
+ eventName === "download-completed" ||
2652
+ eventName === "download-failed"
2653
+ ) {
2654
+ try {
2655
+ parsedDetail = JSON.parse(detail);
2656
+ } catch (e) {
2657
+ console.error("[webviewEventHandler] Failed to parse JSON:", e);
2658
+ // Fallback to string if parsing fails (backward compatibility)
2659
+ parsedDetail = detail;
2660
+ }
2661
+ }
2662
+
2663
+ const event = (
2664
+ handler as (data: { detail: string }) => SparkBunEvent<unknown, unknown>
2665
+ )({
2666
+ detail: parsedDetail,
2667
+ });
2668
+
2669
+ // global event
2670
+ sparkBunEventEmitter.emitEvent(event);
2671
+ sparkBunEventEmitter.emitEvent(event, id);
2672
+ };
2673
+
2674
+ const webviewEventJSCallback = new JSCallback(
2675
+ (id, _eventName, _detail) => {
2676
+ let eventName = "";
2677
+ let detail = "";
2678
+
2679
+ try {
2680
+ // Convert cstring pointers to actual strings
2681
+ eventName = new CString(_eventName).toString();
2682
+ detail = new CString(_detail).toString();
2683
+ } catch (err) {
2684
+ console.error("[webviewEventJSCallback] Error converting strings:", err);
2685
+ console.error("[webviewEventJSCallback] Raw values:", {
2686
+ _eventName,
2687
+ _detail,
2688
+ });
2689
+ return;
2690
+ }
2691
+
2692
+ webviewEventHandler(id, eventName, detail);
2693
+ },
2694
+ {
2695
+ args: [FFIType.u32, FFIType.cstring, FFIType.cstring],
2696
+ returns: FFIType.void,
2697
+ threadsafe: true,
2698
+ },
2699
+ );
2700
+
2701
+ const hostBridgePostmessageHandler = new JSCallback(
2702
+ (id, msg) => {
2703
+ try {
2704
+ const msgStr = new CString(msg);
2705
+
2706
+ if (!msgStr.length) {
2707
+ return;
2708
+ }
2709
+ const rawMessage = msgStr.toString().trim();
2710
+ if (!rawMessage || (rawMessage[0] !== "{" && rawMessage[0] !== "[")) {
2711
+ return;
2712
+ }
2713
+ const msgJson = JSON.parse(rawMessage);
2714
+
2715
+ const webview = BrowserView.ensureWrapped(id);
2716
+ if (!webview) {
2717
+ return;
2718
+ }
2719
+
2720
+ webview.rpcHandler?.(msgJson);
2721
+ } catch (err) {
2722
+ console.error("error sending message to host: ", err);
2723
+ }
2724
+ },
2725
+ {
2726
+ args: [FFIType.u32, FFIType.cstring],
2727
+ returns: FFIType.void,
2728
+ threadsafe: true,
2729
+ },
2730
+ );
2731
+
2732
+ // internalRPC (bun <-> browser internal stuff)
2733
+ // BrowserView.rpc (user defined bun <-> browser rpc unique to each webview)
2734
+ // nativeRPC (internal bun <-> native rpc)
2735
+
2736
+ // eventBridgeHandler: handles ONLY webview events (dom-ready, navigation, etc.)
2737
+ // This is available on ALL webviews including sandboxed ones.
2738
+ // It cannot process RPC requests - only event emission.
2739
+ const eventBridgeHandler = new JSCallback(
2740
+ (_id: number, msg: number) => {
2741
+ try {
2742
+ const message = new CString(msg as unknown as Pointer);
2743
+ const rawMessage = message.toString().trim();
2744
+ if (!rawMessage || (rawMessage[0] !== "{" && rawMessage[0] !== "[")) {
2745
+ return;
2746
+ }
2747
+ const jsonMessage = JSON.parse(rawMessage);
2748
+
2749
+ // Only handle webviewEvent messages - no RPC
2750
+ if (jsonMessage.id === "webviewEvent") {
2751
+ const { payload } = jsonMessage;
2752
+ webviewEventHandler(payload.id, payload.eventName, payload.detail);
2753
+ }
2754
+ // Silently ignore any other message types - sandboxed webviews shouldn't send them
2755
+ } catch (err) {
2756
+ console.error("error in eventBridgeHandler: ", err);
2757
+ }
2758
+ },
2759
+ {
2760
+ args: [FFIType.u32, FFIType.cstring],
2761
+ returns: FFIType.void,
2762
+ threadsafe: true,
2763
+ },
2764
+ );
2765
+
2766
+ // internalBridgeHandler: handles internal RPC (webview tags, drag regions, etc.)
2767
+ // This is only available on trusted (non-sandboxed) webviews.
2768
+ const internalBridgeHandler = new JSCallback(
2769
+ (_id: number, msg: number) => {
2770
+ try {
2771
+ const batchMessage = new CString(msg as unknown as Pointer);
2772
+ const jsonBatch = JSON.parse(batchMessage.toString());
2773
+
2774
+ if (jsonBatch.id === "webviewEvent") {
2775
+ // Note: Some WebviewEvents from inside the webview are routed through here
2776
+ // Others call the JSCallback directly from native code.
2777
+ const { payload } = jsonBatch;
2778
+ webviewEventHandler(payload.id, payload.eventName, payload.detail);
2779
+ return;
2780
+ }
2781
+
2782
+ jsonBatch.forEach((msgStr: string) => {
2783
+ // if (!msgStr.length) {
2784
+ // console.error('WEBVIEW EVENT SENT TO WEBVIEW TAG BRIDGE HANDLER?', )
2785
+ // return;
2786
+ // }
2787
+ const msgJson = JSON.parse(msgStr);
2788
+
2789
+ if (msgJson.type === "message") {
2790
+ const handler = (
2791
+ internalRpcHandlers.message as Record<
2792
+ string,
2793
+ (params: unknown) => void
2794
+ >
2795
+ )[msgJson.id];
2796
+ handler?.(msgJson.payload);
2797
+ } else if (msgJson.type === "request") {
2798
+ const handler = (
2799
+ internalRpcHandlers.request as Record<
2800
+ string,
2801
+ (params: unknown) => unknown
2802
+ >
2803
+ )[msgJson.method];
2804
+
2805
+ const payload = handler?.(msgJson.params);
2806
+
2807
+ const resultObj = {
2808
+ type: "response",
2809
+ id: msgJson.id,
2810
+ success: true,
2811
+ payload,
2812
+ };
2813
+ core_.symbols.sendInternalMessageToWebview(
2814
+ msgJson.hostWebviewId,
2815
+ toCString(JSON.stringify(resultObj)),
2816
+ );
2817
+ }
2818
+ });
2819
+ } catch (err) {
2820
+ console.error("error in internalBridgeHandler: ", err);
2821
+ // console.log('msgStr: ', id, new CString(msg));
2822
+ }
2823
+ },
2824
+ {
2825
+ args: [FFIType.u32, FFIType.cstring],
2826
+ returns: FFIType.void,
2827
+ threadsafe: true,
2828
+ },
2829
+ );
2830
+
2831
+ const trayItemHandler = new JSCallback(
2832
+ (id, action) => {
2833
+ // Note: Some invisible character that doesn't appear in .length
2834
+ // is causing issues
2835
+ const actionString = (new CString(action).toString() || "").trim();
2836
+
2837
+ // Use shared deserialization method
2838
+ const { action: actualAction, data } = deserializeMenuAction(actionString);
2839
+
2840
+ const event = sparkBunEventEmitter.events.tray.trayClicked({
2841
+ id,
2842
+ action: actualAction,
2843
+ data, // Always include data property (undefined if no data)
2844
+ });
2845
+
2846
+ // global event
2847
+ sparkBunEventEmitter.emitEvent(event);
2848
+ sparkBunEventEmitter.emitEvent(event, id);
2849
+ },
2850
+ {
2851
+ args: [FFIType.u32, FFIType.cstring],
2852
+ returns: FFIType.void,
2853
+ threadsafe: true,
2854
+ },
2855
+ );
2856
+
2857
+ const applicationMenuHandler = new JSCallback(
2858
+ (id, action) => {
2859
+ const actionString = new CString(action).toString();
2860
+
2861
+ // Use shared deserialization method
2862
+ const { action: actualAction, data } = deserializeMenuAction(actionString);
2863
+
2864
+ const event = sparkBunEventEmitter.events.app.applicationMenuClicked({
2865
+ id,
2866
+ action: actualAction,
2867
+ data, // Always include data property (undefined if no data)
2868
+ });
2869
+
2870
+ // global event
2871
+ sparkBunEventEmitter.emitEvent(event);
2872
+ },
2873
+ {
2874
+ args: [FFIType.u32, FFIType.cstring],
2875
+ returns: FFIType.void,
2876
+ threadsafe: true,
2877
+ },
2878
+ );
2879
+
2880
+ const contextMenuHandler = new JSCallback(
2881
+ (_id, action) => {
2882
+ const actionString = new CString(action).toString();
2883
+
2884
+ // Use shared deserialization method
2885
+ const { action: actualAction, data } = deserializeMenuAction(actionString);
2886
+
2887
+ const event = sparkBunEventEmitter.events.app.contextMenuClicked({
2888
+ action: actualAction,
2889
+ data, // Always include data property (undefined if no data)
2890
+ });
2891
+
2892
+ sparkBunEventEmitter.emitEvent(event);
2893
+ },
2894
+ {
2895
+ args: [FFIType.u32, FFIType.cstring],
2896
+ returns: FFIType.void,
2897
+ threadsafe: true,
2898
+ },
2899
+ );
2900
+
2901
+ // Note: When passed over FFI JS will GC the buffer/pointer. Make sure to use strdup() or something
2902
+ // on the c side to duplicate the string so objc/c++ gc can own it
2903
+ export function toCString(
2904
+ jsString: string,
2905
+ addNullTerminator: boolean = true,
2906
+ ): CString {
2907
+ let appendWith = "";
2908
+
2909
+ if (addNullTerminator && !jsString.endsWith("\0")) {
2910
+ appendWith = "\0";
2911
+ }
2912
+ const buff = Buffer.from(jsString + appendWith, "utf8");
2913
+
2914
+ // @ts-ignore - This is valid in Bun
2915
+ return ptr(buff);
2916
+ }
2917
+
2918
+ type WebviewTagInitParams = {
2919
+ url: string | null;
2920
+ html: string | null;
2921
+ preload: string | null;
2922
+ renderer: "native" | "cef";
2923
+ partition: string | null;
2924
+ frame: { x: number; y: number; width: number; height: number };
2925
+ hostWebviewId: number;
2926
+ windowId: number;
2927
+ navigationRules: string | null;
2928
+ sandbox: boolean;
2929
+ transparent: boolean;
2930
+ passthrough: boolean;
2931
+ };
2932
+
2933
+ type WgpuTagInitParams = {
2934
+ windowId: number;
2935
+ frame: { x: number; y: number; width: number; height: number };
2936
+ transparent: boolean;
2937
+ passthrough: boolean;
2938
+ };
2939
+
2940
+ export const internalRpcHandlers = {
2941
+ request: {
2942
+ // todo: this shouldn't be getting method, just params.
2943
+ webviewTagInit: (params: WebviewTagInitParams) => {
2944
+ const {
2945
+ hostWebviewId,
2946
+ windowId,
2947
+ renderer,
2948
+ html,
2949
+ preload,
2950
+ partition,
2951
+ frame,
2952
+ navigationRules,
2953
+ sandbox,
2954
+ transparent,
2955
+ passthrough,
2956
+ } = params;
2957
+
2958
+ const url = !params.url && !html ? "https://sparkbun.dev" : params.url;
2959
+
2960
+ const webviewForTag = new BrowserView({
2961
+ url,
2962
+ html,
2963
+ preload,
2964
+ partition,
2965
+ frame,
2966
+ hostWebviewId,
2967
+ autoResize: false,
2968
+ windowId,
2969
+ renderer, //: "cef",
2970
+ navigationRules,
2971
+ sandbox,
2972
+ startTransparent: transparent,
2973
+ startPassthrough: passthrough,
2974
+ });
2975
+
2976
+ return webviewForTag.id;
2977
+ },
2978
+ wgpuTagInit: (params: WgpuTagInitParams) => {
2979
+ const {
2980
+ windowId,
2981
+ frame,
2982
+ transparent,
2983
+ passthrough,
2984
+ } = params;
2985
+
2986
+ const viewForTag = new WGPUView({
2987
+ windowId,
2988
+ frame,
2989
+ autoResize: false,
2990
+ startTransparent: transparent,
2991
+ startPassthrough: passthrough,
2992
+ });
2993
+
2994
+ return viewForTag.id;
2995
+ },
2996
+ webviewTagCanGoBack: (params: { id: number }) => {
2997
+ return core_.symbols.webviewCanGoBack(params.id);
2998
+ },
2999
+ webviewTagCanGoForward: (params: { id: number }) => {
3000
+ return core_.symbols.webviewCanGoForward(params.id);
3001
+ },
3002
+ },
3003
+ message: {
3004
+ webviewTagResize: (params: {
3005
+ id: number;
3006
+ frame: { x: number; y: number; width: number; height: number };
3007
+ masks: string;
3008
+ }) => {
3009
+ const { x, y, width, height } = params.frame;
3010
+ core_.symbols.resizeWebview(
3011
+ params.id,
3012
+ x,
3013
+ y,
3014
+ width,
3015
+ height,
3016
+ toCString(params.masks ?? "[]"),
3017
+ );
3018
+ },
3019
+ wgpuTagResize: (params: {
3020
+ id: number;
3021
+ frame: { x: number; y: number; width: number; height: number };
3022
+ masks: string;
3023
+ }) => {
3024
+ const view = WGPUView.getById(params.id);
3025
+ if (!view?.ptr) {
3026
+ console.error(
3027
+ `wgpuTagResize: WGPUView not found or has no ptr for id ${params.id}`,
3028
+ );
3029
+ return;
3030
+ }
3031
+
3032
+ const { x, y, width, height } = params.frame;
3033
+ native_.symbols.resizeWebview(
3034
+ view.ptr,
3035
+ x,
3036
+ y,
3037
+ width,
3038
+ height,
3039
+ toCString(params.masks ?? "[]"),
3040
+ );
3041
+ },
3042
+ webviewTagUpdateSrc: (params: { id: number; url: string }) => {
3043
+ const webview = BrowserView.ensureWrapped(params.id);
3044
+ if (webview) {
3045
+ webview.url = params.url;
3046
+ }
3047
+ core_.symbols.loadURLInWebView(params.id, toCString(params.url));
3048
+ },
3049
+ webviewTagUpdateHtml: (params: { id: number; html: string }) => {
3050
+ const webview = BrowserView.ensureWrapped(params.id);
3051
+ if (!webview) {
3052
+ console.error(`webviewTagUpdateHtml: BrowserView not found for id ${params.id}`);
3053
+ return;
3054
+ }
3055
+
3056
+ webview.loadHTML(params.html);
3057
+ webview.html = params.html;
3058
+ },
3059
+ webviewTagUpdatePreload: (params: { id: number; preload: string }) => {
3060
+ const webview = BrowserView.ensureWrapped(params.id);
3061
+ if (webview) {
3062
+ webview.preload = params.preload;
3063
+ }
3064
+ core_.symbols.updatePreloadScriptToWebView(
3065
+ params.id,
3066
+ toCString("sparkbun_custom_preload_script"),
3067
+ toCString(params.preload),
3068
+ true,
3069
+ );
3070
+ },
3071
+ webviewTagGoBack: (params: { id: number }) => {
3072
+ core_.symbols.webviewGoBack(params.id);
3073
+ },
3074
+ webviewTagGoForward: (params: { id: number }) => {
3075
+ core_.symbols.webviewGoForward(params.id);
3076
+ },
3077
+ webviewTagReload: (params: { id: number }) => {
3078
+ core_.symbols.webviewReload(params.id);
3079
+ },
3080
+ webviewTagRemove: (params: { id: number }) => {
3081
+ const webview = BrowserView.ensureWrapped(params.id);
3082
+ if (!webview) {
3083
+ console.error(`webviewTagRemove: BrowserView not found for id ${params.id}`);
3084
+ return;
3085
+ }
3086
+ webview.remove();
3087
+ },
3088
+ startWindowMove: (params: { id: number }) => {
3089
+ const windowPtr = getWindowPtr(params.id);
3090
+ if (!windowPtr) return;
3091
+ native_.symbols.startWindowMove(windowPtr);
3092
+ },
3093
+ stopWindowMove: (_params: unknown) => {
3094
+ native_.symbols.stopWindowMove();
3095
+ },
3096
+ webviewTagSetTransparent: (params: {
3097
+ id: number;
3098
+ transparent: boolean;
3099
+ }) => {
3100
+ core_.symbols.webviewSetTransparent(params.id, params.transparent);
3101
+ },
3102
+ wgpuTagSetTransparent: (params: {
3103
+ id: number;
3104
+ transparent: boolean;
3105
+ }) => {
3106
+ const view = WGPUView.getById(params.id);
3107
+ if (!view?.ptr) {
3108
+ console.error(
3109
+ `wgpuTagSetTransparent: WGPUView not found or has no ptr for id ${params.id}`,
3110
+ );
3111
+ return;
3112
+ }
3113
+ native_.symbols.wgpuViewSetTransparent(view.ptr, params.transparent);
3114
+ },
3115
+ webviewTagSetPassthrough: (params: {
3116
+ id: number;
3117
+ enablePassthrough: boolean;
3118
+ }) => {
3119
+ core_.symbols.webviewSetPassthrough(params.id, params.enablePassthrough);
3120
+ },
3121
+ wgpuTagSetPassthrough: (params: { id: number; passthrough: boolean }) => {
3122
+ const view = WGPUView.getById(params.id);
3123
+ if (!view?.ptr) {
3124
+ console.error(
3125
+ `wgpuTagSetPassthrough: WGPUView not found or has no ptr for id ${params.id}`,
3126
+ );
3127
+ return;
3128
+ }
3129
+ native_.symbols.wgpuViewSetPassthrough(view.ptr, params.passthrough);
3130
+ },
3131
+ webviewTagSetHidden: (params: { id: number; hidden: boolean }) => {
3132
+ core_.symbols.webviewSetHidden(params.id, params.hidden);
3133
+ },
3134
+ wgpuTagSetHidden: (params: { id: number; hidden: boolean }) => {
3135
+ const view = WGPUView.getById(params.id);
3136
+ if (!view?.ptr) {
3137
+ console.error(
3138
+ `wgpuTagSetHidden: WGPUView not found or has no ptr for id ${params.id}`,
3139
+ );
3140
+ return;
3141
+ }
3142
+ native_.symbols.wgpuViewSetHidden(view.ptr, params.hidden);
3143
+ },
3144
+ wgpuTagRemove: (params: { id: number }) => {
3145
+ const view = WGPUView.getById(params.id);
3146
+ if (!view?.ptr) {
3147
+ console.error(
3148
+ `wgpuTagRemove: WGPUView not found or has no ptr for id ${params.id}`,
3149
+ );
3150
+ return;
3151
+ }
3152
+ view.remove();
3153
+ },
3154
+ wgpuTagRunTest: (params: { id: number }) => {
3155
+ const view = WGPUView.getById(params.id);
3156
+ if (!view?.ptr) {
3157
+ console.error(
3158
+ `wgpuTagRunTest: WGPUView not found or has no ptr for id ${params.id}`,
3159
+ );
3160
+ return;
3161
+ }
3162
+ if (!native?.symbols?.wgpuRunGPUTest) {
3163
+ console.error("wgpuTagRunTest: wgpuRunGPUTest not available");
3164
+ return;
3165
+ }
3166
+ native_.symbols.wgpuRunGPUTest(view.ptr);
3167
+ },
3168
+ webviewTagSetNavigationRules: (params: { id: number; rules: string[] }) => {
3169
+ const rulesJson = JSON.stringify(params.rules);
3170
+ const webview = BrowserView.ensureWrapped(params.id);
3171
+ if (webview) {
3172
+ webview.navigationRules = rulesJson;
3173
+ }
3174
+ core_.symbols.setWebviewNavigationRules(params.id, toCString(rulesJson));
3175
+ },
3176
+ webviewTagFindInPage: (params: {
3177
+ id: number;
3178
+ searchText: string;
3179
+ forward: boolean;
3180
+ matchCase: boolean;
3181
+ }) => {
3182
+ core_.symbols.webviewFindInPage(
3183
+ params.id,
3184
+ toCString(params.searchText),
3185
+ params.forward,
3186
+ params.matchCase,
3187
+ );
3188
+ },
3189
+ webviewTagStopFind: (params: { id: number }) => {
3190
+ core_.symbols.webviewStopFind(params.id);
3191
+ },
3192
+ webviewTagOpenDevTools: (params: { id: number }) => {
3193
+ core_.symbols.webviewOpenDevTools(params.id);
3194
+ },
3195
+ webviewTagCloseDevTools: (params: { id: number }) => {
3196
+ core_.symbols.webviewCloseDevTools(params.id);
3197
+ },
3198
+ webviewTagToggleDevTools: (params: { id: number }) => {
3199
+ core_.symbols.webviewToggleDevTools(params.id);
3200
+ },
3201
+ webviewTagExecuteJavascript: (params: { id: number; js: string }) => {
3202
+ core_.symbols.evaluateJavaScriptWithNoCompletion(
3203
+ params.id,
3204
+ toCString(params.js),
3205
+ );
3206
+ },
3207
+ webviewEvent: (params: unknown) => {
3208
+ console.log("-----------------+webviewEvent", params);
3209
+ },
3210
+ },
3211
+ };
3212
+
3213
+ // todo: consider renaming to TrayMenuItemConfig
3214
+ export type MenuItemConfig =
3215
+ | { type: "divider" | "separator" }
3216
+ | {
3217
+ type: "normal";
3218
+ label: string;
3219
+ tooltip?: string;
3220
+ action?: string;
3221
+ data?: any;
3222
+ submenu?: Array<MenuItemConfig>;
3223
+ enabled?: boolean;
3224
+ checked?: boolean;
3225
+ hidden?: boolean;
3226
+ };
3227
+
3228
+ export type ApplicationMenuItemConfig =
3229
+ | { type: "divider" | "separator" }
3230
+ | {
3231
+ type?: "normal";
3232
+ label: string;
3233
+ tooltip?: string;
3234
+ action?: string;
3235
+ data?: any;
3236
+ submenu?: Array<ApplicationMenuItemConfig>;
3237
+ enabled?: boolean;
3238
+ checked?: boolean;
3239
+ hidden?: boolean;
3240
+ accelerator?: string;
3241
+ }
3242
+ | {
3243
+ type?: "normal";
3244
+ label?: string;
3245
+ tooltip?: string;
3246
+ role?: string;
3247
+ data?: any;
3248
+ submenu?: Array<ApplicationMenuItemConfig>;
3249
+ enabled?: boolean;
3250
+ checked?: boolean;
3251
+ hidden?: boolean;
3252
+ accelerator?: string;
3253
+ };