electrobun 1.18.4-beta.6 → 2.0.1-beta.13

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