electrobun 1.18.1 → 1.18.4-beta.5

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.
@@ -1,9 +1,9 @@
1
- import { join } from "path";
1
+ import { dirname, join } from "path";
2
+ import { createReadStream } from "node:fs";
2
3
  import electrobunEventEmitter from "../events/eventEmitter";
3
4
  import ElectrobunEvent from "../events/event";
4
5
  import { BrowserView } from "../core/BrowserView";
5
6
  import { WGPUView } from "../core/WGPUView";
6
- import { Tray } from "../core/Tray";
7
7
  import {
8
8
  preloadScript,
9
9
  preloadScriptSandboxed,
@@ -12,7 +12,6 @@ import {
12
12
  // Menu data reference system to avoid serialization overhead
13
13
  const menuDataRegistry = new Map<string, any>();
14
14
  let menuDataCounter = 0;
15
-
16
15
  function storeMenuData(data: any): string {
17
16
  const id = `menuData_${++menuDataCounter}`;
18
17
  menuDataRegistry.set(id, data);
@@ -70,142 +69,586 @@ import {
70
69
  toArrayBuffer,
71
70
  type Pointer,
72
71
  } from "bun:ffi";
73
- import { BrowserWindow } from "../core/BrowserWindow";
74
- import { GpuWindow } from "../core/GpuWindow";
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
+ }
75
96
 
76
97
  function getWindowPtr(winId: number) {
77
- return (
78
- BrowserWindow.getById(winId)?.ptr ?? GpuWindow.getById(winId)?.ptr ?? null
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),
79
122
  );
123
+
124
+ if (!configured) {
125
+ throw getCoreLastError() || "Failed to configure webview runtime";
126
+ }
127
+
128
+ webviewRuntimeConfigured = true;
80
129
  }
81
130
 
82
- export const native = (() => {
131
+ const core = (() => {
83
132
  try {
84
- // Use absolute path to native wrapper DLL to avoid working directory issues
85
- // On Windows shortcuts, the working directory may not be set correctly
86
- const nativeWrapperPath = join(process.cwd(), `libNativeWrapper.${suffix}`);
87
- return dlopen(nativeWrapperPath, {
88
- // window
89
- createWindowWithFrameAndStyleFromWorker: {
90
- // Pass each parameter individually
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: {
91
160
  args: [
92
- FFIType.u32, // windowId
93
161
  FFIType.f64,
94
- FFIType.f64, // x, y
95
162
  FFIType.f64,
96
- FFIType.f64, // width, height
97
- FFIType.u32, // styleMask
98
- FFIType.cstring, // titleBarStyle
99
- FFIType.bool, // transparent
100
- FFIType.f64, // trafficLightOffsetX
101
- FFIType.f64, // trafficLightOffsetY
102
- FFIType.function, // closeHandler
103
- FFIType.function, // moveHandler
104
- FFIType.function, // resizeHandler
105
- FFIType.function, // focusHandler
106
- FFIType.function, // blurHandler
107
- FFIType.function, // keyHandler
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,
108
179
  ],
180
+ returns: FFIType.u32,
181
+ },
182
+ getWindowPointer: {
183
+ args: [FFIType.u32],
109
184
  returns: FFIType.ptr,
110
185
  },
111
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: {
112
275
  args: [
113
- FFIType.ptr, // window ptr
114
- FFIType.cstring, // title
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,
115
320
  ],
116
321
  returns: FFIType.void,
117
322
  },
118
- showWindow: {
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: {
119
440
  args: [
120
- FFIType.ptr, // window ptr
121
- FFIType.bool, // activate
441
+ FFIType.u32,
442
+ FFIType.f64,
443
+ FFIType.f64,
444
+ FFIType.f64,
445
+ FFIType.f64,
446
+ FFIType.bool,
447
+ FFIType.bool,
448
+ FFIType.bool,
122
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],
123
470
  returns: FFIType.void,
124
471
  },
125
- activateWindow: {
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: {
126
577
  args: [
127
- FFIType.ptr, // window ptr
578
+ FFIType.cstring,
579
+ FFIType.cstring,
580
+ FFIType.int,
581
+ FFIType.int,
582
+ FFIType.int,
128
583
  ],
129
- returns: FFIType.void,
130
- },
131
- hideWindow: {
132
- args: [FFIType.ptr],
133
- returns: FFIType.void,
584
+ returns: FFIType.cstring,
134
585
  },
135
- closeWindow: {
586
+ showMessageBox: {
136
587
  args: [
137
- FFIType.ptr, // window ptr
588
+ FFIType.cstring,
589
+ FFIType.cstring,
590
+ FFIType.cstring,
591
+ FFIType.cstring,
592
+ FFIType.cstring,
593
+ FFIType.int,
594
+ FFIType.int,
138
595
  ],
139
- returns: FFIType.void,
596
+ returns: FFIType.int,
140
597
  },
141
- minimizeWindow: {
142
- args: [FFIType.ptr],
143
- returns: FFIType.void,
598
+ clipboardReadText: {
599
+ args: [],
600
+ returns: FFIType.cstring,
144
601
  },
145
- restoreWindow: {
146
- args: [FFIType.ptr],
602
+ clipboardWriteText: {
603
+ args: [FFIType.cstring],
147
604
  returns: FFIType.void,
148
605
  },
149
- isWindowMinimized: {
150
- args: [FFIType.ptr],
151
- returns: FFIType.bool,
152
- },
153
- maximizeWindow: {
606
+ clipboardReadImage: {
154
607
  args: [FFIType.ptr],
155
- returns: FFIType.void,
608
+ returns: FFIType.ptr,
156
609
  },
157
- unmaximizeWindow: {
158
- args: [FFIType.ptr],
610
+ clipboardWriteImage: {
611
+ args: [FFIType.ptr, FFIType.u64],
159
612
  returns: FFIType.void,
160
613
  },
161
- isWindowMaximized: {
162
- args: [FFIType.ptr],
163
- returns: FFIType.bool,
164
- },
165
- setWindowFullScreen: {
166
- args: [FFIType.ptr, FFIType.bool],
614
+ clipboardClear: {
615
+ args: [],
167
616
  returns: FFIType.void,
168
617
  },
169
- isWindowFullScreen: {
170
- args: [FFIType.ptr],
171
- returns: FFIType.bool,
618
+ clipboardAvailableFormats: {
619
+ args: [],
620
+ returns: FFIType.cstring,
172
621
  },
173
- setWindowAlwaysOnTop: {
174
- args: [FFIType.ptr, FFIType.bool],
622
+ setDockIconVisible: {
623
+ args: [FFIType.bool],
175
624
  returns: FFIType.void,
176
625
  },
177
- isWindowAlwaysOnTop: {
178
- args: [FFIType.ptr],
626
+ isDockIconVisible: {
627
+ args: [],
179
628
  returns: FFIType.bool,
180
629
  },
181
- setWindowVisibleOnAllWorkspaces: {
182
- args: [FFIType.ptr, FFIType.bool],
630
+ setExitOnLastWindowClosed: {
631
+ args: [FFIType.bool],
183
632
  returns: FFIType.void,
184
633
  },
185
- isWindowVisibleOnAllWorkspaces: {
186
- args: [FFIType.ptr],
187
- returns: FFIType.bool,
188
- },
189
- setWindowPosition: {
190
- args: [FFIType.ptr, FFIType.f64, FFIType.f64],
191
- returns: FFIType.void,
192
- },
193
- setWindowButtonPosition: {
194
- args: [FFIType.ptr, FFIType.f64, FFIType.f64],
195
- returns: FFIType.void,
196
- },
197
- setWindowSize: {
198
- args: [FFIType.ptr, FFIType.f64, FFIType.f64],
199
- returns: FFIType.void,
200
- },
201
- setWindowFrame: {
202
- args: [FFIType.ptr, FFIType.f64, FFIType.f64, FFIType.f64, FFIType.f64],
634
+ setQuitRequestedHandler: {
635
+ args: [FFIType.function],
203
636
  returns: FFIType.void,
204
637
  },
205
- getWindowFrame: {
206
- args: [FFIType.ptr, FFIType.ptr, FFIType.ptr, FFIType.ptr, FFIType.ptr],
638
+ quitGracefully: {
639
+ args: [FFIType.i32, FFIType.i32],
207
640
  returns: FFIType.void,
208
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, {
209
652
  // webview
210
653
  initWebview: {
211
654
  args: [
@@ -222,13 +665,13 @@ export const native = (() => {
222
665
  FFIType.function, // decideNavigation: *const fn (u32, [*:0]const u8) callconv(.C) bool,
223
666
  FFIType.function, // webviewEventHandler: *const fn (u32, [*:0]const u8, [*:0]const u8) callconv(.C) void,
224
667
  FFIType.function, // eventBridgeHandler: *const fn (u32, [*:0]const u8) callconv(.C) void (events only, always active)
225
- FFIType.function, // bunBridgePostmessageHandler: *const fn (u32, [*:0]const u8) callconv(.C) void (user RPC, disabled in sandbox)
668
+ FFIType.function, // hostBridgePostmessageHandler: *const fn (u32, [*:0]const u8) callconv(.C) void (user RPC, disabled in sandbox)
226
669
  FFIType.function, // internalBridgeHandler: *const fn (u32, [*:0]const u8) callconv(.C) void (internal RPC, disabled in sandbox)
227
670
  FFIType.cstring, // electrobunPreloadScript
228
671
  FFIType.cstring, // customPreloadScript
229
672
  FFIType.cstring, // viewsRoot
230
673
  FFIType.bool, // transparent
231
- FFIType.bool, // sandbox - when true, bunBridge and internalBridge are not set up
674
+ FFIType.bool, // sandbox - when true, hostBridge and internalBridge are not set up
232
675
  ],
233
676
  returns: FFIType.ptr,
234
677
  },
@@ -463,73 +906,6 @@ export const native = (() => {
463
906
  args: [FFIType.ptr, FFIType.ptr],
464
907
  returns: FFIType.ptr,
465
908
  },
466
- // Tray
467
- createTray: {
468
- args: [
469
- FFIType.u32, // id
470
- FFIType.cstring, // title
471
- FFIType.cstring, // pathToImage
472
- FFIType.bool, // isTemplate
473
- FFIType.u32, // width
474
- FFIType.u32, //height
475
- FFIType.function, // trayItemHandler
476
- ],
477
- returns: FFIType.ptr,
478
- },
479
- setTrayTitle: {
480
- args: [FFIType.ptr, FFIType.cstring],
481
- returns: FFIType.void,
482
- },
483
- setTrayImage: {
484
- args: [FFIType.ptr, FFIType.cstring],
485
- returns: FFIType.void,
486
- },
487
- setTrayMenu: {
488
- args: [FFIType.ptr, FFIType.cstring],
489
- returns: FFIType.void,
490
- },
491
- removeTray: {
492
- args: [FFIType.ptr],
493
- returns: FFIType.void,
494
- },
495
- getTrayBounds: {
496
- args: [FFIType.ptr],
497
- returns: FFIType.cstring,
498
- },
499
- setApplicationMenu: {
500
- args: [FFIType.cstring, FFIType.function],
501
- returns: FFIType.void,
502
- },
503
- showContextMenu: {
504
- args: [FFIType.cstring, FFIType.function],
505
- returns: FFIType.void,
506
- },
507
- moveToTrash: {
508
- args: [FFIType.cstring],
509
- returns: FFIType.bool,
510
- },
511
- showItemInFolder: {
512
- args: [FFIType.cstring],
513
- returns: FFIType.void,
514
- },
515
- openExternal: {
516
- args: [FFIType.cstring],
517
- returns: FFIType.bool,
518
- },
519
- openPath: {
520
- args: [FFIType.cstring],
521
- returns: FFIType.bool,
522
- },
523
- showNotification: {
524
- args: [
525
- FFIType.cstring, // title
526
- FFIType.cstring, // body
527
- FFIType.cstring, // subtitle
528
- FFIType.bool, // silent
529
- ],
530
- returns: FFIType.void,
531
- },
532
-
533
909
  // Global keyboard shortcuts
534
910
  setGlobalShortcutCallback: {
535
911
  args: [FFIType.function],
@@ -552,73 +928,6 @@ export const native = (() => {
552
928
  returns: FFIType.bool,
553
929
  },
554
930
 
555
- // Screen API
556
- getAllDisplays: {
557
- args: [],
558
- returns: FFIType.cstring,
559
- },
560
- getPrimaryDisplay: {
561
- args: [],
562
- returns: FFIType.cstring,
563
- },
564
- getCursorScreenPoint: {
565
- args: [],
566
- returns: FFIType.cstring,
567
- },
568
- getMouseButtons: {
569
- args: [],
570
- returns: FFIType.u64,
571
- },
572
-
573
- openFileDialog: {
574
- args: [
575
- FFIType.cstring,
576
- FFIType.cstring,
577
- FFIType.int,
578
- FFIType.int,
579
- FFIType.int,
580
- ],
581
- returns: FFIType.cstring,
582
- },
583
- showMessageBox: {
584
- args: [
585
- FFIType.cstring, // type
586
- FFIType.cstring, // title
587
- FFIType.cstring, // message
588
- FFIType.cstring, // detail
589
- FFIType.cstring, // buttons (comma-separated)
590
- FFIType.int, // defaultId
591
- FFIType.int, // cancelId
592
- ],
593
- returns: FFIType.int,
594
- },
595
-
596
- // Clipboard API
597
- clipboardReadText: {
598
- args: [],
599
- returns: FFIType.cstring,
600
- },
601
- clipboardWriteText: {
602
- args: [FFIType.cstring],
603
- returns: FFIType.void,
604
- },
605
- clipboardReadImage: {
606
- args: [FFIType.ptr], // pointer to size_t for output size
607
- returns: FFIType.ptr, // pointer to PNG data
608
- },
609
- clipboardWriteImage: {
610
- args: [FFIType.ptr, FFIType.u64], // PNG data pointer, size
611
- returns: FFIType.void,
612
- },
613
- clipboardClear: {
614
- args: [],
615
- returns: FFIType.void,
616
- },
617
- clipboardAvailableFormats: {
618
- args: [],
619
- returns: FFIType.cstring,
620
- },
621
-
622
931
  // Session/Cookie API
623
932
  sessionGetCookies: {
624
933
  args: [FFIType.cstring, FFIType.cstring],
@@ -650,33 +959,7 @@ export const native = (() => {
650
959
  args: [FFIType.function],
651
960
  returns: FFIType.void,
652
961
  },
653
- setDockIconVisible: {
654
- args: [FFIType.bool],
655
- returns: FFIType.void,
656
- },
657
- isDockIconVisible: {
658
- args: [],
659
- returns: FFIType.bool,
660
- },
661
962
 
662
- // Window style utilities
663
- getWindowStyle: {
664
- args: [
665
- FFIType.bool,
666
- FFIType.bool,
667
- FFIType.bool,
668
- FFIType.bool,
669
- FFIType.bool,
670
- FFIType.bool,
671
- FFIType.bool,
672
- FFIType.bool,
673
- FFIType.bool,
674
- FFIType.bool,
675
- FFIType.bool,
676
- FFIType.bool,
677
- ],
678
- returns: FFIType.u32,
679
- },
680
963
  // JSCallback utils for native code to use
681
964
  setJSUtils: {
682
965
  args: [
@@ -727,7 +1010,7 @@ export const native = (() => {
727
1010
  }
728
1011
  })();
729
1012
 
730
- export const hasFFI = native !== null;
1013
+ export const hasFFI = native !== null && core !== null;
731
1014
 
732
1015
  // PostMessage bridge for carrot workers (inter-carrot communication, host events).
733
1016
  // Created when __bunnyCarrotBootstrap exists, regardless of FFI availability.
@@ -809,7 +1092,14 @@ function createFfiRequestProxy(ffiRequest: Record<string, Function>): Record<str
809
1092
  return new Proxy(ffiRequest, {
810
1093
  get(target, method: string) {
811
1094
  if (typeof method !== "string") return target[method];
812
- return (params?: unknown) => bridge!.requestHost(method, params);
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
+ };
813
1103
  },
814
1104
  });
815
1105
  }
@@ -821,12 +1111,76 @@ function createFfiRequestProxy(ffiRequest: Record<string, Function>): Record<str
821
1111
  // a zig bug I ran into last year. So check number of args in a signature when alignment issues occur.
822
1112
 
823
1113
  // Non-null accessor for use inside _ffiImpl — these methods are only called when hasFFI is true.
1114
+ const core_ = core!;
824
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
+ }
825
1180
 
826
1181
  const _ffiImpl = {
827
1182
  request: {
828
1183
  createWindow: (params: {
829
- id: number;
830
1184
  url: string | null;
831
1185
  title: string;
832
1186
  frame: {
@@ -857,9 +1211,8 @@ const _ffiImpl = {
857
1211
  x: number;
858
1212
  y: number;
859
1213
  };
860
- }): FFIType.ptr => {
1214
+ }): number => {
861
1215
  const {
862
- id,
863
1216
  url: _url,
864
1217
  title,
865
1218
  frame: { x, y, width, height },
@@ -884,7 +1237,7 @@ const _ffiImpl = {
884
1237
  trafficLightOffset = { x: 0, y: 0 },
885
1238
  } = params;
886
1239
 
887
- const styleMask = native_.symbols.getWindowStyle(
1240
+ const styleMask = core_.symbols.getWindowStyle(
888
1241
  Borderless,
889
1242
  Titled,
890
1243
  Closable,
@@ -899,8 +1252,7 @@ const _ffiImpl = {
899
1252
  HUDWindow,
900
1253
  );
901
1254
 
902
- const windowPtr = native_.symbols.createWindowWithFrameAndStyleFromWorker(
903
- id,
1255
+ const windowId = core_.symbols.createWindow(
904
1256
  // frame
905
1257
  x,
906
1258
  y,
@@ -910,6 +1262,9 @@ const _ffiImpl = {
910
1262
  // style
911
1263
  toCString(titleBarStyle),
912
1264
  transparent,
1265
+ toCString(title),
1266
+ hidden,
1267
+ activate,
913
1268
  trafficLightOffset.x,
914
1269
  trafficLightOffset.y,
915
1270
  // callbacks
@@ -921,26 +1276,24 @@ const _ffiImpl = {
921
1276
  windowKeyCallback,
922
1277
  );
923
1278
 
924
- if (!windowPtr) {
925
- throw "Failed to create window";
926
- }
927
-
928
- native_.symbols.setWindowTitle(windowPtr, toCString(title));
929
- if (!hidden) {
930
- native_.symbols.showWindow(windowPtr, activate);
1279
+ if (!windowId) {
1280
+ throw getCoreLastError() || "Failed to create window";
931
1281
  }
932
1282
 
933
- return windowPtr;
1283
+ return windowId;
1284
+ },
1285
+ getWindowPointer: (params: { winId: number }): Pointer | null => {
1286
+ return getWindowPtr(params.winId);
934
1287
  },
935
1288
  setTitle: (params: { winId: number; title: string }) => {
936
1289
  const { winId, title } = params;
937
1290
  const windowPtr = getWindowPtr(winId);
938
1291
 
939
1292
  if (!windowPtr) {
940
- throw `Can't add webview to window. window no longer exists`;
1293
+ throw `Can't set window title. Window no longer exists`;
941
1294
  }
942
1295
 
943
- native_.symbols.setWindowTitle(windowPtr, toCString(title));
1296
+ core_.symbols.setWindowTitle(winId, toCString(title));
944
1297
  },
945
1298
 
946
1299
  closeWindow: (params: { winId: number }) => {
@@ -952,7 +1305,7 @@ const _ffiImpl = {
952
1305
  return;
953
1306
  }
954
1307
 
955
- native_.symbols.closeWindow(windowPtr);
1308
+ core_.symbols.closeWindow(winId);
956
1309
  // Note: Cleanup of BrowserWindowMap happens in the windowCloseCallback
957
1310
  },
958
1311
 
@@ -964,7 +1317,7 @@ const _ffiImpl = {
964
1317
  throw `Can't show window. Window no longer exists`;
965
1318
  }
966
1319
 
967
- native_.symbols.showWindow(windowPtr, params.activate ?? true);
1320
+ core_.symbols.showWindow(winId, params.activate ?? true);
968
1321
  },
969
1322
 
970
1323
  activateWindow: (params: { winId: number }) => {
@@ -975,7 +1328,7 @@ const _ffiImpl = {
975
1328
  throw `Can't activate window. Window no longer exists`;
976
1329
  }
977
1330
 
978
- native_.symbols.activateWindow(windowPtr);
1331
+ core_.symbols.activateWindow(winId);
979
1332
  },
980
1333
 
981
1334
  hideWindow: (params: { winId: number }) => {
@@ -986,7 +1339,7 @@ const _ffiImpl = {
986
1339
  throw `Can't hide window. Window no longer exists`;
987
1340
  }
988
1341
 
989
- native_.symbols.hideWindow(windowPtr);
1342
+ core_.symbols.hideWindow(winId);
990
1343
  },
991
1344
 
992
1345
  minimizeWindow: (params: { winId: number }) => {
@@ -997,7 +1350,7 @@ const _ffiImpl = {
997
1350
  throw `Can't minimize window. Window no longer exists`;
998
1351
  }
999
1352
 
1000
- native_.symbols.minimizeWindow(windowPtr);
1353
+ core_.symbols.minimizeWindow(winId);
1001
1354
  },
1002
1355
 
1003
1356
  restoreWindow: (params: { winId: number }) => {
@@ -1008,7 +1361,7 @@ const _ffiImpl = {
1008
1361
  throw `Can't restore window. Window no longer exists`;
1009
1362
  }
1010
1363
 
1011
- native_.symbols.restoreWindow(windowPtr);
1364
+ core_.symbols.restoreWindow(winId);
1012
1365
  },
1013
1366
 
1014
1367
  isWindowMinimized: (params: { winId: number }): boolean => {
@@ -1019,7 +1372,7 @@ const _ffiImpl = {
1019
1372
  return false;
1020
1373
  }
1021
1374
 
1022
- return native_.symbols.isWindowMinimized(windowPtr);
1375
+ return core_.symbols.isWindowMinimized(winId);
1023
1376
  },
1024
1377
 
1025
1378
  maximizeWindow: (params: { winId: number }) => {
@@ -1030,7 +1383,7 @@ const _ffiImpl = {
1030
1383
  throw `Can't maximize window. Window no longer exists`;
1031
1384
  }
1032
1385
 
1033
- native_.symbols.maximizeWindow(windowPtr);
1386
+ core_.symbols.maximizeWindow(winId);
1034
1387
  },
1035
1388
 
1036
1389
  unmaximizeWindow: (params: { winId: number }) => {
@@ -1041,7 +1394,7 @@ const _ffiImpl = {
1041
1394
  throw `Can't unmaximize window. Window no longer exists`;
1042
1395
  }
1043
1396
 
1044
- native_.symbols.unmaximizeWindow(windowPtr);
1397
+ core_.symbols.unmaximizeWindow(winId);
1045
1398
  },
1046
1399
 
1047
1400
  isWindowMaximized: (params: { winId: number }): boolean => {
@@ -1052,7 +1405,7 @@ const _ffiImpl = {
1052
1405
  return false;
1053
1406
  }
1054
1407
 
1055
- return native_.symbols.isWindowMaximized(windowPtr);
1408
+ return core_.symbols.isWindowMaximized(winId);
1056
1409
  },
1057
1410
 
1058
1411
  setWindowFullScreen: (params: { winId: number; fullScreen: boolean }) => {
@@ -1063,7 +1416,7 @@ const _ffiImpl = {
1063
1416
  throw `Can't set fullscreen. Window no longer exists`;
1064
1417
  }
1065
1418
 
1066
- native_.symbols.setWindowFullScreen(windowPtr, fullScreen);
1419
+ core_.symbols.setWindowFullScreen(winId, fullScreen);
1067
1420
  },
1068
1421
 
1069
1422
  isWindowFullScreen: (params: { winId: number }): boolean => {
@@ -1074,7 +1427,7 @@ const _ffiImpl = {
1074
1427
  return false;
1075
1428
  }
1076
1429
 
1077
- return native_.symbols.isWindowFullScreen(windowPtr);
1430
+ return core_.symbols.isWindowFullScreen(winId);
1078
1431
  },
1079
1432
 
1080
1433
  setWindowAlwaysOnTop: (params: { winId: number; alwaysOnTop: boolean }) => {
@@ -1085,7 +1438,7 @@ const _ffiImpl = {
1085
1438
  throw `Can't set always on top. Window no longer exists`;
1086
1439
  }
1087
1440
 
1088
- native_.symbols.setWindowAlwaysOnTop(windowPtr, alwaysOnTop);
1441
+ core_.symbols.setWindowAlwaysOnTop(winId, alwaysOnTop);
1089
1442
  },
1090
1443
 
1091
1444
  isWindowAlwaysOnTop: (params: { winId: number }): boolean => {
@@ -1096,7 +1449,7 @@ const _ffiImpl = {
1096
1449
  return false;
1097
1450
  }
1098
1451
 
1099
- return native_.symbols.isWindowAlwaysOnTop(windowPtr);
1452
+ return core_.symbols.isWindowAlwaysOnTop(winId);
1100
1453
  },
1101
1454
 
1102
1455
  setWindowVisibleOnAllWorkspaces: (params: {
@@ -1104,55 +1457,55 @@ const _ffiImpl = {
1104
1457
  visibleOnAllWorkspaces: boolean;
1105
1458
  }) => {
1106
1459
  const { winId, visibleOnAllWorkspaces } = params;
1107
- const windowPtr = BrowserWindow.getById(winId)?.ptr;
1460
+ const windowPtr = getWindowPtr(winId);
1108
1461
 
1109
1462
  if (!windowPtr) {
1110
1463
  throw `Can't set visible on all workspaces. Window no longer exists`;
1111
1464
  }
1112
1465
 
1113
- native_.symbols.setWindowVisibleOnAllWorkspaces(
1114
- windowPtr,
1466
+ core_.symbols.setWindowVisibleOnAllWorkspaces(
1467
+ winId,
1115
1468
  visibleOnAllWorkspaces,
1116
1469
  );
1117
1470
  },
1118
1471
 
1119
1472
  isWindowVisibleOnAllWorkspaces: (params: { winId: number }): boolean => {
1120
1473
  const { winId } = params;
1121
- const windowPtr = BrowserWindow.getById(winId)?.ptr;
1474
+ const windowPtr = getWindowPtr(winId);
1122
1475
 
1123
1476
  if (!windowPtr) {
1124
1477
  return false;
1125
1478
  }
1126
1479
 
1127
- return native_.symbols.isWindowVisibleOnAllWorkspaces(windowPtr);
1480
+ return core_.symbols.isWindowVisibleOnAllWorkspaces(winId);
1128
1481
  },
1129
1482
 
1130
- setWindowPosition: (params: { winId: number; x: number; y: number }) => {
1131
- const { winId, x, y } = params;
1132
- const windowPtr = getWindowPtr(winId);
1483
+ setWindowPosition: (params: { winId: number; x: number; y: number }) => {
1484
+ const { winId, x, y } = params;
1485
+ const windowPtr = getWindowPtr(winId);
1133
1486
 
1134
- if (!windowPtr) {
1135
- throw `Can't set window position. Window no longer exists`;
1136
- }
1487
+ if (!windowPtr) {
1488
+ throw `Can't set window position. Window no longer exists`;
1489
+ }
1137
1490
 
1138
- native_.symbols.setWindowPosition(windowPtr, x, y);
1139
- },
1491
+ core_.symbols.setWindowPosition(winId, x, y);
1492
+ },
1140
1493
 
1141
- setWindowButtonPosition: (params: { winId: number; x: number; y: number }) => {
1142
- const { winId, x, y } = params;
1143
- const windowPtr = getWindowPtr(winId);
1494
+ setWindowButtonPosition: (params: { winId: number; x: number; y: number }) => {
1495
+ const { winId, x, y } = params;
1496
+ const windowPtr = getWindowPtr(winId);
1144
1497
 
1145
- if (!windowPtr) {
1146
- throw `Can't set window button position. Window no longer exists`;
1147
- }
1498
+ if (!windowPtr) {
1499
+ throw `Can't set window button position. Window no longer exists`;
1500
+ }
1148
1501
 
1149
- native_.symbols.setWindowButtonPosition(windowPtr, x, y);
1150
- },
1502
+ core_.symbols.setWindowButtonPosition(winId, x, y);
1503
+ },
1151
1504
 
1152
- setWindowSize: (params: {
1153
- winId: number;
1154
- width: number;
1155
- height: number;
1505
+ setWindowSize: (params: {
1506
+ winId: number;
1507
+ width: number;
1508
+ height: number;
1156
1509
  }) => {
1157
1510
  const { winId, width, height } = params;
1158
1511
  const windowPtr = getWindowPtr(winId);
@@ -1161,7 +1514,7 @@ const _ffiImpl = {
1161
1514
  throw `Can't set window size. Window no longer exists`;
1162
1515
  }
1163
1516
 
1164
- native_.symbols.setWindowSize(windowPtr, width, height);
1517
+ core_.symbols.setWindowSize(winId, width, height);
1165
1518
  },
1166
1519
 
1167
1520
  setWindowFrame: (params: {
@@ -1178,7 +1531,7 @@ const _ffiImpl = {
1178
1531
  throw `Can't set window frame. Window no longer exists`;
1179
1532
  }
1180
1533
 
1181
- native_.symbols.setWindowFrame(windowPtr, x, y, width, height);
1534
+ core_.symbols.setWindowFrame(winId, x, y, width, height);
1182
1535
  },
1183
1536
 
1184
1537
  getWindowFrame: (params: {
@@ -1197,8 +1550,8 @@ const _ffiImpl = {
1197
1550
  const widthBuf = new Float64Array(1);
1198
1551
  const heightBuf = new Float64Array(1);
1199
1552
 
1200
- native_.symbols.getWindowFrame(
1201
- windowPtr,
1553
+ core_.symbols.getWindowFrame(
1554
+ winId,
1202
1555
  ptr(xBuf),
1203
1556
  ptr(yBuf),
1204
1557
  ptr(widthBuf),
@@ -1212,17 +1565,12 @@ const _ffiImpl = {
1212
1565
  height: heightBuf[0]!,
1213
1566
  };
1214
1567
  },
1215
-
1216
1568
  createWebview: (params: {
1217
- id: number;
1218
1569
  windowId: number;
1570
+ hostWebviewId: number | null;
1219
1571
  renderer: "cef" | "native";
1220
- rpcPort: number;
1221
1572
  secretKey: string;
1222
- hostWebviewId: number | null;
1223
- pipePrefix: string;
1224
1573
  url: string | null;
1225
- html: string | null;
1226
1574
  partition: string | null;
1227
1575
  preload: string | null;
1228
1576
  viewsRoot: string | null;
@@ -1237,17 +1585,13 @@ const _ffiImpl = {
1237
1585
  sandbox: boolean;
1238
1586
  startTransparent: boolean;
1239
1587
  startPassthrough: boolean;
1240
- }): FFIType.ptr => {
1588
+ }): number => {
1241
1589
  const {
1242
- id,
1243
1590
  windowId,
1591
+ hostWebviewId,
1244
1592
  renderer,
1245
- rpcPort,
1246
1593
  secretKey,
1247
- // hostWebviewId: number | null;
1248
- // pipePrefix: string;
1249
1594
  url,
1250
- // html: string | null;
1251
1595
  partition,
1252
1596
  preload,
1253
1597
  viewsRoot,
@@ -1257,62 +1601,11 @@ const _ffiImpl = {
1257
1601
  startTransparent,
1258
1602
  startPassthrough,
1259
1603
  } = params;
1604
+ ensureWebviewRuntimeConfigured();
1260
1605
 
1261
- const parentWindow = BrowserWindow.getById(windowId);
1262
- const windowPtr = parentWindow?.ptr;
1263
- // Get transparent flag from parent window
1264
- const transparent = parentWindow?.transparent ?? false;
1265
-
1266
- if (!windowPtr) {
1267
- throw `Can't add webview to window. window no longer exists`;
1268
- }
1269
-
1270
- // Dynamic setup per-webview (variables that change for each webview)
1271
- // EventBridge is available for ALL webviews (including sandboxed) for event emission
1272
- // InternalBridge and BunBridge are only available for trusted (non-sandboxed) webviews
1273
- let dynamicPreload: string;
1274
- let selectedPreloadScript: string;
1275
-
1276
- if (sandbox) {
1277
- // Sandboxed webview: minimal preload with only event emission capability
1278
- // Note: We set up internalBridge for event emission fallback (until native code
1279
- // adds dedicated eventBridge handler). The security is enforced because:
1280
- // 1. Sandboxed preload has NO RPC code - it can only emit events
1281
- // 2. No bunBridge is set up - no user RPC communication
1282
- // 3. No secretKey/rpcPort - no encrypted socket RPC
1283
- // 4. No webview tag support - can't create OOPIFs
1284
- // Note: Check existing value first to preserve bridges already set by CEF's OnContextCreated
1285
- dynamicPreload = `
1286
- window.__electrobunWebviewId = ${id};
1287
- window.__electrobunWindowId = ${windowId};
1288
- window.__electrobunEventBridge = window.__electrobunEventBridge || window.webkit?.messageHandlers?.eventBridge || window.eventBridge || window.chrome?.webview?.hostObjects?.eventBridge;
1289
- window.__electrobunInternalBridge = window.__electrobunInternalBridge || window.webkit?.messageHandlers?.internalBridge || window.internalBridge || window.chrome?.webview?.hostObjects?.internalBridge;
1290
- `;
1291
- selectedPreloadScript = preloadScriptSandboxed;
1292
- } else {
1293
- // Trusted webview: all bridges, full preload
1294
- // Note: Check existing value first to preserve bridges already set by CEF's OnContextCreated
1295
- dynamicPreload = `
1296
- window.__electrobunWebviewId = ${id};
1297
- window.__electrobunWindowId = ${windowId};
1298
- window.__electrobunRpcSocketPort = ${rpcPort};
1299
- window.__electrobunSecretKeyBytes = [${secretKey}];
1300
- window.__electrobunEventBridge = window.__electrobunEventBridge || window.webkit?.messageHandlers?.eventBridge || window.eventBridge || window.chrome?.webview?.hostObjects?.eventBridge;
1301
- window.__electrobunInternalBridge = window.__electrobunInternalBridge || window.webkit?.messageHandlers?.internalBridge || window.internalBridge || window.chrome?.webview?.hostObjects?.internalBridge;
1302
- window.__electrobunBunBridge = window.__electrobunBunBridge || window.webkit?.messageHandlers?.bunBridge || window.bunBridge || window.chrome?.webview?.hostObjects?.bunBridge;
1303
- `;
1304
- selectedPreloadScript = preloadScript;
1305
- }
1306
-
1307
- const electrobunPreload = dynamicPreload + selectedPreloadScript;
1308
-
1309
- const customPreload = preload;
1310
-
1311
- // Pre-set flags before initWebview (workaround for FFI param count limits)
1312
- native_.symbols.setNextWebviewFlags(startTransparent, startPassthrough);
1313
- const webviewPtr = native_.symbols.initWebview(
1314
- id,
1315
- windowPtr,
1606
+ const webviewId = core_.symbols.createWebview(
1607
+ windowId,
1608
+ hostWebviewId || 0,
1316
1609
  toCString(renderer),
1317
1610
  toCString(url || ""),
1318
1611
  x,
@@ -1323,25 +1616,104 @@ window.__electrobunBunBridge = window.__electrobunBunBridge || window.webkit?.me
1323
1616
  toCString(partition || "persist:default"),
1324
1617
  webviewDecideNavigation,
1325
1618
  webviewEventJSCallback,
1326
- eventBridgeHandler, // Event-only bridge (always active, for dom-ready, navigation, etc.)
1327
- bunBridgePostmessageHandler, // User RPC bridge (disabled in sandbox mode)
1328
- internalBridgeHandler, // Internal RPC bridge (disabled in sandbox mode)
1329
- toCString(electrobunPreload),
1330
- toCString(customPreload || ""),
1619
+ eventBridgeHandler,
1620
+ hostBridgePostmessageHandler,
1621
+ internalBridgeHandler,
1622
+ toCString(secretKey),
1623
+ toCString(preload || ""),
1331
1624
  toCString(viewsRoot || ""),
1332
- transparent,
1333
- sandbox, // When true, bunBridge and internalBridge are not set up in native code
1625
+ sandbox, // When true, hostBridge and internalBridge are not set up in native code
1626
+ startTransparent,
1627
+ startPassthrough,
1334
1628
  );
1335
1629
 
1336
- if (!webviewPtr) {
1337
- throw "Failed to create webview";
1630
+ if (!webviewId) {
1631
+ throw getCoreLastError() || "Failed to create webview";
1338
1632
  }
1339
1633
 
1340
- return webviewPtr;
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);
1341
1714
  },
1342
1715
 
1343
1716
  createWGPUView: (params: {
1344
- id: number;
1345
1717
  windowId: number;
1346
1718
  frame: {
1347
1719
  x: number;
@@ -1352,9 +1724,8 @@ window.__electrobunBunBridge = window.__electrobunBunBridge || window.webkit?.me
1352
1724
  autoResize: boolean;
1353
1725
  startTransparent: boolean;
1354
1726
  startPassthrough: boolean;
1355
- }): FFIType.ptr => {
1727
+ }): number => {
1356
1728
  const {
1357
- id,
1358
1729
  windowId,
1359
1730
  frame: { x, y, width, height },
1360
1731
  autoResize,
@@ -1362,14 +1733,8 @@ window.__electrobunBunBridge = window.__electrobunBunBridge || window.webkit?.me
1362
1733
  startPassthrough,
1363
1734
  } = params;
1364
1735
 
1365
- const windowPtr = getWindowPtr(windowId);
1366
- if (!windowPtr) {
1367
- throw `Can't add WGPUView to window. window no longer exists`;
1368
- }
1369
-
1370
- const viewPtr = native_.symbols.initWGPUView(
1371
- id,
1372
- windowPtr,
1736
+ const viewId = core_.symbols.createWGPUView(
1737
+ windowId,
1373
1738
  x,
1374
1739
  y,
1375
1740
  width,
@@ -1379,11 +1744,14 @@ window.__electrobunBunBridge = window.__electrobunBunBridge || window.webkit?.me
1379
1744
  startPassthrough,
1380
1745
  );
1381
1746
 
1382
- if (!viewPtr) {
1747
+ if (!viewId) {
1383
1748
  throw "Failed to create WGPUView";
1384
1749
  }
1385
1750
 
1386
- return viewPtr;
1751
+ return viewId;
1752
+ },
1753
+ getWGPUViewPointer: (params: { id: number }): Pointer | null => {
1754
+ return core_.symbols.getWGPUViewPointer(params.id) || null;
1387
1755
  },
1388
1756
 
1389
1757
  wgpuViewSetFrame: (params: {
@@ -1393,16 +1761,8 @@ window.__electrobunBunBridge = window.__electrobunBunBridge || window.webkit?.me
1393
1761
  width: number;
1394
1762
  height: number;
1395
1763
  }) => {
1396
- const view = WGPUView.getById(params.id);
1397
- if (!view?.ptr) {
1398
- console.error(
1399
- `wgpuViewSetFrame: WGPUView not found or has no ptr for id ${params.id}`,
1400
- );
1401
- return;
1402
- }
1403
-
1404
- native_.symbols.wgpuViewSetFrame(
1405
- view.ptr,
1764
+ core_.symbols.setWGPUViewFrame(
1765
+ params.id,
1406
1766
  params.x,
1407
1767
  params.y,
1408
1768
  params.width,
@@ -1411,97 +1771,83 @@ window.__electrobunBunBridge = window.__electrobunBunBridge || window.webkit?.me
1411
1771
  },
1412
1772
 
1413
1773
  wgpuViewSetTransparent: (params: { id: number; transparent: boolean }) => {
1414
- const view = WGPUView.getById(params.id);
1415
- if (!view?.ptr) {
1416
- console.error(
1417
- `wgpuViewSetTransparent: WGPUView not found or has no ptr for id ${params.id}`,
1418
- );
1419
- return;
1420
- }
1421
-
1422
- native_.symbols.wgpuViewSetTransparent(view.ptr, params.transparent);
1774
+ core_.symbols.setWGPUViewTransparent(params.id, params.transparent);
1423
1775
  },
1424
1776
 
1425
1777
  wgpuViewSetPassthrough: (params: {
1426
1778
  id: number;
1427
1779
  passthrough: boolean;
1428
1780
  }) => {
1429
- const view = WGPUView.getById(params.id);
1430
- if (!view?.ptr) {
1431
- console.error(
1432
- `wgpuViewSetPassthrough: WGPUView not found or has no ptr for id ${params.id}`,
1433
- );
1434
- return;
1435
- }
1436
-
1437
- native_.symbols.wgpuViewSetPassthrough(view.ptr, params.passthrough);
1781
+ core_.symbols.setWGPUViewPassthrough(params.id, params.passthrough);
1438
1782
  },
1439
1783
 
1440
1784
  wgpuViewSetHidden: (params: { id: number; hidden: boolean }) => {
1441
- const view = WGPUView.getById(params.id);
1442
- if (!view?.ptr) {
1443
- console.error(
1444
- `wgpuViewSetHidden: WGPUView not found or has no ptr for id ${params.id}`,
1445
- );
1446
- return;
1447
- }
1448
-
1449
- native_.symbols.wgpuViewSetHidden(view.ptr, params.hidden);
1785
+ core_.symbols.setWGPUViewHidden(params.id, params.hidden);
1450
1786
  },
1451
1787
 
1452
1788
  wgpuViewRemove: (params: { id: number }) => {
1453
- const view = WGPUView.getById(params.id);
1454
- if (!view?.ptr) {
1455
- console.error(
1456
- `wgpuViewRemove: WGPUView not found or has no ptr for id ${params.id}`,
1457
- );
1458
- return;
1459
- }
1460
-
1461
- native_.symbols.wgpuViewRemove(view.ptr);
1789
+ core_.symbols.removeWGPUView(params.id);
1462
1790
  },
1463
1791
  wgpuViewGetNativeHandle: (params: { id: number }): Pointer | null => {
1464
- const view = WGPUView.getById(params.id);
1465
- if (!view?.ptr) {
1466
- console.error(
1467
- `wgpuViewGetNativeHandle: WGPUView not found or has no ptr for id ${params.id}`,
1468
- );
1469
- return null;
1470
- }
1471
-
1472
- const handle = native_.symbols.wgpuViewGetNativeHandle(view.ptr);
1473
- return handle || null;
1792
+ return core_.symbols.getWGPUViewNativeHandle(params.id) || null;
1793
+ },
1794
+ runWGPUViewTest: (params: { id: number }) => {
1795
+ core_.symbols.runWGPUViewTest(params.id);
1474
1796
  },
1475
1797
 
1476
1798
  evaluateJavascriptWithNoCompletion: (params: {
1477
1799
  id: number;
1478
1800
  js: string;
1479
1801
  }) => {
1480
- const { id, js } = params;
1481
- const webview = BrowserView.getById(id);
1482
-
1483
- if (!webview?.ptr) {
1484
- return;
1485
- }
1486
-
1487
- native_.symbols.evaluateJavaScriptWithNoCompletion(
1488
- webview.ptr,
1489
- toCString(js),
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),
1490
1814
  );
1491
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
+ },
1492
1840
 
1493
1841
  createTray: (params: {
1494
- id: number;
1495
1842
  title: string;
1496
1843
  image: string;
1497
1844
  template: boolean;
1498
1845
  width: number;
1499
1846
  height: number;
1500
- }): FFIType.ptr => {
1501
- const { id, title, image, template, width, height } = params;
1847
+ }): number => {
1848
+ const { title, image, template, width, height } = params;
1502
1849
 
1503
- const trayPtr = native_.symbols.createTray(
1504
- id,
1850
+ const trayId = core_.symbols.createTray(
1505
1851
  toCString(title),
1506
1852
  toCString(image),
1507
1853
  template,
@@ -1510,27 +1856,25 @@ window.__electrobunBunBridge = window.__electrobunBunBridge || window.webkit?.me
1510
1856
  trayItemHandler,
1511
1857
  );
1512
1858
 
1513
- if (!trayPtr) {
1859
+ if (!trayId) {
1514
1860
  throw "Failed to create tray";
1515
1861
  }
1516
1862
 
1517
- return trayPtr;
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);
1518
1870
  },
1519
1871
  setTrayTitle: (params: { id: number; title: string }): void => {
1520
1872
  const { id, title } = params;
1521
-
1522
- const tray = Tray.getById(id);
1523
- if (!tray) return;
1524
-
1525
- native_.symbols.setTrayTitle(tray.ptr, toCString(title));
1873
+ core_.symbols.setTrayTitle(id, toCString(title));
1526
1874
  },
1527
1875
  setTrayImage: (params: { id: number; image: string }): void => {
1528
1876
  const { id, image } = params;
1529
-
1530
- const tray = Tray.getById(id);
1531
- if (!tray) return;
1532
-
1533
- native_.symbols.setTrayImage(tray.ptr, toCString(image));
1877
+ core_.symbols.setTrayImage(id, toCString(image));
1534
1878
  },
1535
1879
  setTrayMenu: (params: {
1536
1880
  id: number;
@@ -1538,31 +1882,14 @@ window.__electrobunBunBridge = window.__electrobunBunBridge || window.webkit?.me
1538
1882
  menuConfig: string;
1539
1883
  }): void => {
1540
1884
  const { id, menuConfig } = params;
1541
-
1542
- const tray = Tray.getById(id);
1543
- if (!tray) return;
1544
-
1545
- native_.symbols.setTrayMenu(tray.ptr, toCString(menuConfig));
1885
+ core_.symbols.setTrayMenu(id, toCString(menuConfig));
1546
1886
  },
1547
1887
 
1548
1888
  removeTray: (params: { id: number }): void => {
1549
- const { id } = params;
1550
- const tray = Tray.getById(id);
1551
-
1552
- if (!tray) {
1553
- throw `Can't remove tray. Tray no longer exists`;
1554
- }
1555
-
1556
- native_.symbols.removeTray(tray.ptr);
1557
- // The Tray class will handle removing from TrayMap
1889
+ core_.symbols.removeTray(params.id);
1558
1890
  },
1559
1891
  getTrayBounds: (params: { id: number }): Rectangle => {
1560
- const tray = Tray.getById(params.id);
1561
- if (!tray?.ptr) {
1562
- return { x: 0, y: 0, width: 0, height: 0 };
1563
- }
1564
-
1565
- const jsonStr = native_.symbols.getTrayBounds(tray.ptr);
1892
+ const jsonStr = core_.symbols.getTrayBounds(params.id);
1566
1893
  if (!jsonStr) {
1567
1894
  return { x: 0, y: 0, width: 0, height: 0 };
1568
1895
  }
@@ -1576,7 +1903,7 @@ window.__electrobunBunBridge = window.__electrobunBunBridge || window.webkit?.me
1576
1903
  setApplicationMenu: (params: { menuConfig: string }): void => {
1577
1904
  const { menuConfig } = params;
1578
1905
 
1579
- native_.symbols.setApplicationMenu(
1906
+ core_.symbols.setApplicationMenu(
1580
1907
  toCString(menuConfig),
1581
1908
  applicationMenuHandler,
1582
1909
  );
@@ -1584,25 +1911,25 @@ window.__electrobunBunBridge = window.__electrobunBunBridge || window.webkit?.me
1584
1911
  showContextMenu: (params: { menuConfig: string }): void => {
1585
1912
  const { menuConfig } = params;
1586
1913
 
1587
- native_.symbols.showContextMenu(toCString(menuConfig), contextMenuHandler);
1914
+ core_.symbols.showContextMenu(toCString(menuConfig), contextMenuHandler);
1588
1915
  },
1589
1916
  moveToTrash: (params: { path: string }): boolean => {
1590
1917
  const { path } = params;
1591
1918
 
1592
- return native_.symbols.moveToTrash(toCString(path));
1919
+ return core_.symbols.moveToTrash(toCString(path));
1593
1920
  },
1594
1921
  showItemInFolder: (params: { path: string }): void => {
1595
1922
  const { path } = params;
1596
1923
 
1597
- native_.symbols.showItemInFolder(toCString(path));
1924
+ core_.symbols.showItemInFolder(toCString(path));
1598
1925
  },
1599
1926
  openExternal: (params: { url: string }): boolean => {
1600
1927
  const { url } = params;
1601
- return native_.symbols.openExternal(toCString(url));
1928
+ return core_.symbols.openExternal(toCString(url));
1602
1929
  },
1603
1930
  openPath: (params: { path: string }): boolean => {
1604
1931
  const { path } = params;
1605
- return native_.symbols.openPath(toCString(path));
1932
+ return core_.symbols.openPath(toCString(path));
1606
1933
  },
1607
1934
  showNotification: (params: {
1608
1935
  title: string;
@@ -1611,7 +1938,7 @@ window.__electrobunBunBridge = window.__electrobunBunBridge || window.webkit?.me
1611
1938
  silent?: boolean;
1612
1939
  }): void => {
1613
1940
  const { title, body = "", subtitle = "", silent = false } = params;
1614
- native_.symbols.showNotification(
1941
+ core_.symbols.showNotification(
1615
1942
  toCString(title),
1616
1943
  toCString(body),
1617
1944
  toCString(subtitle),
@@ -1619,10 +1946,10 @@ window.__electrobunBunBridge = window.__electrobunBunBridge || window.webkit?.me
1619
1946
  );
1620
1947
  },
1621
1948
  setDockIconVisible: (params: { visible: boolean }): void => {
1622
- native_.symbols.setDockIconVisible(params.visible);
1949
+ core_.symbols.setDockIconVisible(params.visible);
1623
1950
  },
1624
1951
  isDockIconVisible: (): boolean => {
1625
- return native_.symbols.isDockIconVisible();
1952
+ return core_.symbols.isDockIconVisible();
1626
1953
  },
1627
1954
  openFileDialog: (params: {
1628
1955
  startingFolder: string;
@@ -1638,7 +1965,7 @@ window.__electrobunBunBridge = window.__electrobunBunBridge || window.webkit?.me
1638
1965
  canChooseDirectory,
1639
1966
  allowsMultipleSelection,
1640
1967
  } = params;
1641
- const filePath = native_.symbols.openFileDialog(
1968
+ const filePath = core_.symbols.openFileDialog(
1642
1969
  toCString(startingFolder),
1643
1970
  toCString(allowedFileTypes),
1644
1971
  canChooseFiles ? 1 : 0,
@@ -1668,7 +1995,7 @@ window.__electrobunBunBridge = window.__electrobunBunBridge || window.webkit?.me
1668
1995
  } = params;
1669
1996
  // Convert buttons array to comma-separated string
1670
1997
  const buttonsStr = buttons.join(",");
1671
- return native_.symbols.showMessageBox(
1998
+ return core_.symbols.showMessageBox(
1672
1999
  toCString(type),
1673
2000
  toCString(title),
1674
2001
  toCString(message),
@@ -1681,17 +2008,17 @@ window.__electrobunBunBridge = window.__electrobunBunBridge || window.webkit?.me
1681
2008
 
1682
2009
  // Clipboard API
1683
2010
  clipboardReadText: (): string | null => {
1684
- const result = native_.symbols.clipboardReadText();
2011
+ const result = core_.symbols.clipboardReadText();
1685
2012
  if (!result) return null;
1686
2013
  return result.toString();
1687
2014
  },
1688
2015
  clipboardWriteText: (params: { text: string }): void => {
1689
- native_.symbols.clipboardWriteText(toCString(params.text));
2016
+ core_.symbols.clipboardWriteText(toCString(params.text));
1690
2017
  },
1691
2018
  clipboardReadImage: (): Uint8Array | null => {
1692
2019
  // Allocate a buffer for the size output
1693
2020
  const sizeBuffer = new BigUint64Array(1);
1694
- const dataPtr = native_.symbols.clipboardReadImage(ptr(sizeBuffer));
2021
+ const dataPtr = core_.symbols.clipboardReadImage(ptr(sizeBuffer));
1695
2022
 
1696
2023
  if (!dataPtr) return null;
1697
2024
 
@@ -1711,13 +2038,13 @@ window.__electrobunBunBridge = window.__electrobunBunBridge || window.webkit?.me
1711
2038
  },
1712
2039
  clipboardWriteImage: (params: { pngData: Uint8Array }): void => {
1713
2040
  const { pngData } = params;
1714
- native_.symbols.clipboardWriteImage(ptr(pngData), BigInt(pngData.length));
2041
+ core_.symbols.clipboardWriteImage(ptr(pngData), BigInt(pngData.length));
1715
2042
  },
1716
2043
  clipboardClear: (): void => {
1717
- native_.symbols.clipboardClear();
2044
+ core_.symbols.clipboardClear();
1718
2045
  },
1719
2046
  clipboardAvailableFormats: (): string[] => {
1720
- const result = native_.symbols.clipboardAvailableFormats();
2047
+ const result = core_.symbols.clipboardAvailableFormats();
1721
2048
  if (!result) return [];
1722
2049
  const formatsStr = result.toString();
1723
2050
  if (!formatsStr) return [];
@@ -2054,7 +2381,7 @@ const getMimeType = new JSCallback(
2054
2381
 
2055
2382
  const getHTMLForWebviewSync = new JSCallback(
2056
2383
  (webviewId) => {
2057
- const webview = BrowserView.getById(webviewId);
2384
+ const webview = BrowserView.ensureWrapped(webviewId);
2058
2385
 
2059
2386
  return toCString(webview?.html || "");
2060
2387
  },
@@ -2088,7 +2415,7 @@ if (native) {
2088
2415
  const appReopenCallback = new JSCallback(
2089
2416
  () => {
2090
2417
  if (process.platform === "darwin") {
2091
- native_.symbols.setDockIconVisible(true);
2418
+ core_.symbols.setDockIconVisible(true);
2092
2419
  }
2093
2420
  const handler = electrobunEventEmitter.events.app.reopen;
2094
2421
  const event = handler({});
@@ -2107,7 +2434,7 @@ if (native) {
2107
2434
  },
2108
2435
  { args: [], returns: "void", threadsafe: true },
2109
2436
  );
2110
- native_.symbols.setQuitRequestedHandler(quitRequestedCallback);
2437
+ core_.symbols.setQuitRequestedHandler(quitRequestedCallback);
2111
2438
 
2112
2439
  const globalShortcutCallback = new JSCallback(
2113
2440
  (acceleratorPtr) => {
@@ -2178,7 +2505,7 @@ export const Screen = {
2178
2505
  * @returns Display object for the primary monitor
2179
2506
  */
2180
2507
  getPrimaryDisplay: (): Display => {
2181
- const jsonStr = native ? native_.symbols.getPrimaryDisplay() : null;
2508
+ const jsonStr = hasFFI ? core_.symbols.getPrimaryDisplay() : null;
2182
2509
  if (!jsonStr) {
2183
2510
  return {
2184
2511
  id: 0,
@@ -2206,7 +2533,7 @@ export const Screen = {
2206
2533
  * @returns Array of Display objects
2207
2534
  */
2208
2535
  getAllDisplays: (): Display[] => {
2209
- const jsonStr = native ? native_.symbols.getAllDisplays() : null;
2536
+ const jsonStr = hasFFI ? core_.symbols.getAllDisplays() : null;
2210
2537
  if (!jsonStr) {
2211
2538
  return [];
2212
2539
  }
@@ -2222,7 +2549,7 @@ export const Screen = {
2222
2549
  * @returns Point with x and y coordinates
2223
2550
  */
2224
2551
  getCursorScreenPoint: (): Point => {
2225
- const jsonStr = native ? native_.symbols.getCursorScreenPoint() : null;
2552
+ const jsonStr = hasFFI ? core_.symbols.getCursorScreenPoint() : null;
2226
2553
  if (!jsonStr) {
2227
2554
  return { x: 0, y: 0 };
2228
2555
  }
@@ -2238,7 +2565,7 @@ export const Screen = {
2238
2565
  */
2239
2566
  getMouseButtons: (): bigint => {
2240
2567
  try {
2241
- return native ? native_.symbols.getMouseButtons() : BigInt(0);
2568
+ return hasFFI ? core_.symbols.getMouseButtons() : BigInt(0);
2242
2569
  } catch {
2243
2570
  return 0n;
2244
2571
  }
@@ -2403,35 +2730,13 @@ const webviewDecideNavigation = new JSCallback(
2403
2730
  );
2404
2731
 
2405
2732
  const webviewEventHandler = (id: number, eventName: string, detail: string) => {
2406
- const webview = BrowserView.getById(id);
2407
- if (!webview) {
2408
- console.error("[webviewEventHandler] No webview found for id:", id);
2409
- return;
2410
- }
2411
-
2412
- if (webview.hostWebviewId) {
2413
- const hostWebview = BrowserView.getById(webview.hostWebviewId);
2414
-
2415
- if (!hostWebview) {
2416
- console.error("[webviewEventHandler] No webview found for id:", id);
2417
- return;
2418
- }
2733
+ BrowserView.ensureWrapped(id);
2419
2734
 
2420
- // This is a webviewtag so we should send the event into the parent as well
2421
- // NOTE: for new-window-open and host-message the detail is a json string that needs to be parsed
2422
- let js;
2423
- if (eventName === "new-window-open" || eventName === "host-message") {
2424
- // detail is already a JSON string that will be parsed as a JS object
2425
- js = `document.querySelector('#electrobun-webview-${id}').emit(${JSON.stringify(eventName)}, ${detail});`;
2426
- } else {
2427
- js = `document.querySelector('#electrobun-webview-${id}').emit(${JSON.stringify(eventName)}, ${JSON.stringify(detail)});`;
2428
- }
2429
-
2430
- native_.symbols.evaluateJavaScriptWithNoCompletion(
2431
- hostWebview.ptr,
2432
- toCString(js),
2433
- );
2434
- }
2735
+ core_.symbols.dispatchHostWebviewEvent(
2736
+ id,
2737
+ toCString(eventName),
2738
+ toCString(detail),
2739
+ );
2435
2740
 
2436
2741
  const eventMap: Record<string, string> = {
2437
2742
  "will-navigate": "willNavigate",
@@ -2525,7 +2830,7 @@ const webviewEventJSCallback = new JSCallback(
2525
2830
  },
2526
2831
  );
2527
2832
 
2528
- const bunBridgePostmessageHandler = new JSCallback(
2833
+ const hostBridgePostmessageHandler = new JSCallback(
2529
2834
  (id, msg) => {
2530
2835
  try {
2531
2836
  const msgStr = new CString(msg);
@@ -2539,12 +2844,14 @@ const bunBridgePostmessageHandler = new JSCallback(
2539
2844
  }
2540
2845
  const msgJson = JSON.parse(rawMessage);
2541
2846
 
2542
- const webview = BrowserView.getById(id);
2543
- if (!webview) return;
2847
+ const webview = BrowserView.ensureWrapped(id);
2848
+ if (!webview) {
2849
+ return;
2850
+ }
2544
2851
 
2545
2852
  webview.rpcHandler?.(msgJson);
2546
2853
  } catch (err) {
2547
- console.error("error sending message to bun: ", err);
2854
+ console.error("error sending message to host: ", err);
2548
2855
  }
2549
2856
  },
2550
2857
  {
@@ -2620,8 +2927,6 @@ const internalBridgeHandler = new JSCallback(
2620
2927
  )[msgJson.id];
2621
2928
  handler?.(msgJson.payload);
2622
2929
  } else if (msgJson.type === "request") {
2623
- const hostWebview = BrowserView.getById(msgJson.hostWebviewId);
2624
- // const targetWebview = BrowserView.getById(msgJson.params.params.hostWebviewId);
2625
2930
  const handler = (
2626
2931
  internalRpcHandlers.request as Record<
2627
2932
  string,
@@ -2637,15 +2942,10 @@ const internalBridgeHandler = new JSCallback(
2637
2942
  success: true,
2638
2943
  payload,
2639
2944
  };
2640
-
2641
- if (!hostWebview) {
2642
- console.log(
2643
- "--->>> internal request in bun: NO HOST WEBVIEW FOUND",
2644
- );
2645
- return;
2646
- }
2647
-
2648
- hostWebview.sendInternalMessageViaExecute(resultObj);
2945
+ core_.symbols.sendInternalMessageToWebview(
2946
+ msgJson.hostWebviewId,
2947
+ toCString(JSON.stringify(resultObj)),
2948
+ );
2649
2949
  }
2650
2950
  });
2651
2951
  } catch (err) {
@@ -2826,24 +3126,10 @@ export const internalRpcHandlers = {
2826
3126
  return viewForTag.id;
2827
3127
  },
2828
3128
  webviewTagCanGoBack: (params: { id: number }) => {
2829
- const { id } = params;
2830
- const webviewPtr = BrowserView.getById(id)?.ptr;
2831
- if (!webviewPtr) {
2832
- console.error("no webview ptr");
2833
- return false;
2834
- }
2835
-
2836
- return native_.symbols.webviewCanGoBack(webviewPtr);
3129
+ return core_.symbols.webviewCanGoBack(params.id);
2837
3130
  },
2838
3131
  webviewTagCanGoForward: (params: { id: number }) => {
2839
- const { id } = params;
2840
- const webviewPtr = BrowserView.getById(id)?.ptr;
2841
- if (!webviewPtr) {
2842
- console.error("no webview ptr");
2843
- return false;
2844
- }
2845
-
2846
- return native_.symbols.webviewCanGoForward(webviewPtr);
3132
+ return core_.symbols.webviewCanGoForward(params.id);
2847
3133
  },
2848
3134
  },
2849
3135
  message: {
@@ -2852,25 +3138,14 @@ export const internalRpcHandlers = {
2852
3138
  frame: { x: number; y: number; width: number; height: number };
2853
3139
  masks: string;
2854
3140
  }) => {
2855
- const browserView = BrowserView.getById(params.id);
2856
- const webviewPtr = browserView?.ptr;
2857
-
2858
- if (!webviewPtr) {
2859
- console.log(
2860
- "[Bun] ERROR: webviewTagResize - no webview ptr found for id:",
2861
- params.id,
2862
- );
2863
- return;
2864
- }
2865
-
2866
3141
  const { x, y, width, height } = params.frame;
2867
- native_.symbols.resizeWebview(
2868
- webviewPtr,
3142
+ core_.symbols.resizeWebview(
3143
+ params.id,
2869
3144
  x,
2870
3145
  y,
2871
3146
  width,
2872
3147
  height,
2873
- toCString(params.masks),
3148
+ toCString(params.masks ?? "[]"),
2874
3149
  );
2875
3150
  },
2876
3151
  wgpuTagResize: (params: {
@@ -2897,81 +3172,47 @@ export const internalRpcHandlers = {
2897
3172
  );
2898
3173
  },
2899
3174
  webviewTagUpdateSrc: (params: { id: number; url: string }) => {
2900
- const webview = BrowserView.getById(params.id);
2901
- if (!webview || !webview.ptr) {
2902
- console.error(
2903
- `webviewTagUpdateSrc: BrowserView not found or has no ptr for id ${params.id}`,
2904
- );
2905
- return;
3175
+ const webview = BrowserView.ensureWrapped(params.id);
3176
+ if (webview) {
3177
+ webview.url = params.url;
2906
3178
  }
2907
- native_.symbols.loadURLInWebView(webview.ptr, toCString(params.url));
3179
+ core_.symbols.loadURLInWebView(params.id, toCString(params.url));
2908
3180
  },
2909
3181
  webviewTagUpdateHtml: (params: { id: number; html: string }) => {
2910
- const webview = BrowserView.getById(params.id);
2911
- if (!webview || !webview.ptr) {
2912
- console.error(
2913
- `webviewTagUpdateHtml: BrowserView not found or has no ptr for id ${params.id}`,
2914
- );
3182
+ const webview = BrowserView.ensureWrapped(params.id);
3183
+ if (!webview) {
3184
+ console.error(`webviewTagUpdateHtml: BrowserView not found for id ${params.id}`);
2915
3185
  return;
2916
3186
  }
2917
3187
 
2918
- // Store HTML content in native map for scheme handlers
2919
- native_.symbols.setWebviewHTMLContent(webview.id, toCString(params.html));
2920
-
2921
3188
  webview.loadHTML(params.html);
2922
3189
  webview.html = params.html;
2923
3190
  },
2924
3191
  webviewTagUpdatePreload: (params: { id: number; preload: string }) => {
2925
- const webview = BrowserView.getById(params.id);
2926
- if (!webview || !webview.ptr) {
2927
- console.error(
2928
- `webviewTagUpdatePreload: BrowserView not found or has no ptr for id ${params.id}`,
2929
- );
2930
- return;
3192
+ const webview = BrowserView.ensureWrapped(params.id);
3193
+ if (webview) {
3194
+ webview.preload = params.preload;
2931
3195
  }
2932
- native_.symbols.updatePreloadScriptToWebView(
2933
- webview.ptr,
3196
+ core_.symbols.updatePreloadScriptToWebView(
3197
+ params.id,
2934
3198
  toCString("electrobun_custom_preload_script"),
2935
3199
  toCString(params.preload),
2936
3200
  true,
2937
3201
  );
2938
3202
  },
2939
3203
  webviewTagGoBack: (params: { id: number }) => {
2940
- const webview = BrowserView.getById(params.id);
2941
- if (!webview || !webview.ptr) {
2942
- console.error(
2943
- `webviewTagGoBack: BrowserView not found or has no ptr for id ${params.id}`,
2944
- );
2945
- return;
2946
- }
2947
- native_.symbols.webviewGoBack(webview.ptr);
3204
+ core_.symbols.webviewGoBack(params.id);
2948
3205
  },
2949
3206
  webviewTagGoForward: (params: { id: number }) => {
2950
- const webview = BrowserView.getById(params.id);
2951
- if (!webview || !webview.ptr) {
2952
- console.error(
2953
- `webviewTagGoForward: BrowserView not found or has no ptr for id ${params.id}`,
2954
- );
2955
- return;
2956
- }
2957
- native_.symbols.webviewGoForward(webview.ptr);
3207
+ core_.symbols.webviewGoForward(params.id);
2958
3208
  },
2959
3209
  webviewTagReload: (params: { id: number }) => {
2960
- const webview = BrowserView.getById(params.id);
2961
- if (!webview || !webview.ptr) {
2962
- console.error(
2963
- `webviewTagReload: BrowserView not found or has no ptr for id ${params.id}`,
2964
- );
2965
- return;
2966
- }
2967
- native_.symbols.webviewReload(webview.ptr);
3210
+ core_.symbols.webviewReload(params.id);
2968
3211
  },
2969
3212
  webviewTagRemove: (params: { id: number }) => {
2970
- const webview = BrowserView.getById(params.id);
2971
- if (!webview || !webview.ptr) {
2972
- console.error(
2973
- `webviewTagRemove: BrowserView not found or has no ptr for id ${params.id}`,
2974
- );
3213
+ const webview = BrowserView.ensureWrapped(params.id);
3214
+ if (!webview) {
3215
+ console.error(`webviewTagRemove: BrowserView not found for id ${params.id}`);
2975
3216
  return;
2976
3217
  }
2977
3218
  webview.remove();
@@ -2988,14 +3229,7 @@ export const internalRpcHandlers = {
2988
3229
  id: number;
2989
3230
  transparent: boolean;
2990
3231
  }) => {
2991
- const webview = BrowserView.getById(params.id);
2992
- if (!webview || !webview.ptr) {
2993
- console.error(
2994
- `webviewTagSetTransparent: BrowserView not found or has no ptr for id ${params.id}`,
2995
- );
2996
- return;
2997
- }
2998
- native_.symbols.webviewSetTransparent(webview.ptr, params.transparent);
3232
+ core_.symbols.webviewSetTransparent(params.id, params.transparent);
2999
3233
  },
3000
3234
  wgpuTagSetTransparent: (params: {
3001
3235
  id: number;
@@ -3014,17 +3248,7 @@ export const internalRpcHandlers = {
3014
3248
  id: number;
3015
3249
  enablePassthrough: boolean;
3016
3250
  }) => {
3017
- const webview = BrowserView.getById(params.id);
3018
- if (!webview || !webview.ptr) {
3019
- console.error(
3020
- `webviewTagSetPassthrough: BrowserView not found or has no ptr for id ${params.id}`,
3021
- );
3022
- return;
3023
- }
3024
- native_.symbols.webviewSetPassthrough(
3025
- webview.ptr,
3026
- params.enablePassthrough,
3027
- );
3251
+ core_.symbols.webviewSetPassthrough(params.id, params.enablePassthrough);
3028
3252
  },
3029
3253
  wgpuTagSetPassthrough: (params: { id: number; passthrough: boolean }) => {
3030
3254
  const view = WGPUView.getById(params.id);
@@ -3037,14 +3261,7 @@ export const internalRpcHandlers = {
3037
3261
  native_.symbols.wgpuViewSetPassthrough(view.ptr, params.passthrough);
3038
3262
  },
3039
3263
  webviewTagSetHidden: (params: { id: number; hidden: boolean }) => {
3040
- const webview = BrowserView.getById(params.id);
3041
- if (!webview || !webview.ptr) {
3042
- console.error(
3043
- `webviewTagSetHidden: BrowserView not found or has no ptr for id ${params.id}`,
3044
- );
3045
- return;
3046
- }
3047
- native_.symbols.webviewSetHidden(webview.ptr, params.hidden);
3264
+ core_.symbols.webviewSetHidden(params.id, params.hidden);
3048
3265
  },
3049
3266
  wgpuTagSetHidden: (params: { id: number; hidden: boolean }) => {
3050
3267
  const view = WGPUView.getById(params.id);
@@ -3081,18 +3298,12 @@ export const internalRpcHandlers = {
3081
3298
  native_.symbols.wgpuRunGPUTest(view.ptr);
3082
3299
  },
3083
3300
  webviewTagSetNavigationRules: (params: { id: number; rules: string[] }) => {
3084
- const webview = BrowserView.getById(params.id);
3085
- if (!webview || !webview.ptr) {
3086
- console.error(
3087
- `webviewTagSetNavigationRules: BrowserView not found or has no ptr for id ${params.id}`,
3088
- );
3089
- return;
3090
- }
3091
3301
  const rulesJson = JSON.stringify(params.rules);
3092
- native_.symbols.setWebviewNavigationRules(
3093
- webview.ptr,
3094
- toCString(rulesJson),
3095
- );
3302
+ const webview = BrowserView.ensureWrapped(params.id);
3303
+ if (webview) {
3304
+ webview.navigationRules = rulesJson;
3305
+ }
3306
+ core_.symbols.setWebviewNavigationRules(params.id, toCString(rulesJson));
3096
3307
  },
3097
3308
  webviewTagFindInPage: (params: {
3098
3309
  id: number;
@@ -3100,70 +3311,28 @@ export const internalRpcHandlers = {
3100
3311
  forward: boolean;
3101
3312
  matchCase: boolean;
3102
3313
  }) => {
3103
- const webview = BrowserView.getById(params.id);
3104
- if (!webview || !webview.ptr) {
3105
- console.error(
3106
- `webviewTagFindInPage: BrowserView not found or has no ptr for id ${params.id}`,
3107
- );
3108
- return;
3109
- }
3110
- native_.symbols.webviewFindInPage(
3111
- webview.ptr,
3314
+ core_.symbols.webviewFindInPage(
3315
+ params.id,
3112
3316
  toCString(params.searchText),
3113
3317
  params.forward,
3114
3318
  params.matchCase,
3115
3319
  );
3116
3320
  },
3117
3321
  webviewTagStopFind: (params: { id: number }) => {
3118
- const webview = BrowserView.getById(params.id);
3119
- if (!webview || !webview.ptr) {
3120
- console.error(
3121
- `webviewTagStopFind: BrowserView not found or has no ptr for id ${params.id}`,
3122
- );
3123
- return;
3124
- }
3125
- native_.symbols.webviewStopFind(webview.ptr);
3322
+ core_.symbols.webviewStopFind(params.id);
3126
3323
  },
3127
3324
  webviewTagOpenDevTools: (params: { id: number }) => {
3128
- const webview = BrowserView.getById(params.id);
3129
- if (!webview || !webview.ptr) {
3130
- console.error(
3131
- `webviewTagOpenDevTools: BrowserView not found or has no ptr for id ${params.id}`,
3132
- );
3133
- return;
3134
- }
3135
- native_.symbols.webviewOpenDevTools(webview.ptr);
3325
+ core_.symbols.webviewOpenDevTools(params.id);
3136
3326
  },
3137
3327
  webviewTagCloseDevTools: (params: { id: number }) => {
3138
- const webview = BrowserView.getById(params.id);
3139
- if (!webview || !webview.ptr) {
3140
- console.error(
3141
- `webviewTagCloseDevTools: BrowserView not found or has no ptr for id ${params.id}`,
3142
- );
3143
- return;
3144
- }
3145
- native_.symbols.webviewCloseDevTools(webview.ptr);
3328
+ core_.symbols.webviewCloseDevTools(params.id);
3146
3329
  },
3147
3330
  webviewTagToggleDevTools: (params: { id: number }) => {
3148
- const webview = BrowserView.getById(params.id);
3149
- if (!webview || !webview.ptr) {
3150
- console.error(
3151
- `webviewTagToggleDevTools: BrowserView not found or has no ptr for id ${params.id}`,
3152
- );
3153
- return;
3154
- }
3155
- native_.symbols.webviewToggleDevTools(webview.ptr);
3331
+ core_.symbols.webviewToggleDevTools(params.id);
3156
3332
  },
3157
3333
  webviewTagExecuteJavascript: (params: { id: number; js: string }) => {
3158
- const webview = BrowserView.getById(params.id);
3159
- if (!webview || !webview.ptr) {
3160
- console.error(
3161
- `webviewTagExecuteJavascript: BrowserView not found or has no ptr for id ${params.id}`,
3162
- );
3163
- return;
3164
- }
3165
- native_.symbols.evaluateJavaScriptWithNoCompletion(
3166
- webview.ptr,
3334
+ core_.symbols.evaluateJavaScriptWithNoCompletion(
3335
+ params.id,
3167
3336
  toCString(params.js),
3168
3337
  );
3169
3338
  },