electrobun 1.18.4-beta.3 → 1.18.4-beta.6

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 { rpcPort } from "../core/Socket";
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);
@@ -71,6 +70,30 @@ import {
71
70
  type Pointer,
72
71
  } from "bun:ffi";
73
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
+
74
97
  function getWindowPtr(winId: number) {
75
98
  return core?.symbols.getWindowPointer(winId) || null;
76
99
  }
@@ -93,7 +116,7 @@ function ensureWebviewRuntimeConfigured() {
93
116
  }
94
117
 
95
118
  const configured = core?.symbols.configureWebviewRuntime(
96
- rpcPort,
119
+ 0,
97
120
  toCString(preloadScript),
98
121
  toCString(preloadScriptSandboxed),
99
122
  );
@@ -107,13 +130,11 @@ function ensureWebviewRuntimeConfigured() {
107
130
 
108
131
  const core = (() => {
109
132
  try {
110
- const corePath = join(
111
- process.cwd(),
133
+ const coreFileName =
112
134
  process.platform === "win32"
113
135
  ? "ElectrobunCore.dll"
114
- : `libElectrobunCore.${suffix}`,
115
- );
116
- return dlopen(corePath, {
136
+ : `libElectrobunCore.${suffix}`;
137
+ return tryDlopenCandidates(coreFileName, {
117
138
  electrobun_core_last_error: {
118
139
  args: [],
119
140
  returns: FFIType.cstring,
@@ -367,6 +388,26 @@ const core = (() => {
367
388
  args: [FFIType.u32, FFIType.cstring],
368
389
  returns: FFIType.void,
369
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
+ },
370
411
  dispatchHostWebviewEvent: {
371
412
  args: [FFIType.u32, FFIType.cstring, FFIType.cstring],
372
413
  returns: FFIType.bool,
@@ -395,6 +436,55 @@ const core = (() => {
395
436
  args: [FFIType.u32],
396
437
  returns: FFIType.f64,
397
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
+ },
398
488
  createTray: {
399
489
  args: [
400
490
  FFIType.cstring,
@@ -537,6 +627,18 @@ const core = (() => {
537
627
  args: [],
538
628
  returns: FFIType.bool,
539
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
+ },
540
642
  });
541
643
  } catch {
542
644
  return null;
@@ -545,10 +647,8 @@ const core = (() => {
545
647
 
546
648
  export const native = (() => {
547
649
  try {
548
- // Use absolute path to native wrapper DLL to avoid working directory issues
549
- // On Windows shortcuts, the working directory may not be set correctly
550
- const nativeWrapperPath = join(process.cwd(), `libNativeWrapper.${suffix}`);
551
- return dlopen(nativeWrapperPath, {
650
+ const nativeWrapperFileName = `libNativeWrapper.${suffix}`;
651
+ return tryDlopenCandidates(nativeWrapperFileName, {
552
652
  // webview
553
653
  initWebview: {
554
654
  args: [
@@ -565,13 +665,13 @@ export const native = (() => {
565
665
  FFIType.function, // decideNavigation: *const fn (u32, [*:0]const u8) callconv(.C) bool,
566
666
  FFIType.function, // webviewEventHandler: *const fn (u32, [*:0]const u8, [*:0]const u8) callconv(.C) void,
567
667
  FFIType.function, // eventBridgeHandler: *const fn (u32, [*:0]const u8) callconv(.C) void (events only, always active)
568
- 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)
569
669
  FFIType.function, // internalBridgeHandler: *const fn (u32, [*:0]const u8) callconv(.C) void (internal RPC, disabled in sandbox)
570
670
  FFIType.cstring, // electrobunPreloadScript
571
671
  FFIType.cstring, // customPreloadScript
572
672
  FFIType.cstring, // viewsRoot
573
673
  FFIType.bool, // transparent
574
- 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
575
675
  ],
576
676
  returns: FFIType.ptr,
577
677
  },
@@ -992,7 +1092,14 @@ function createFfiRequestProxy(ffiRequest: Record<string, Function>): Record<str
992
1092
  return new Proxy(ffiRequest, {
993
1093
  get(target, method: string) {
994
1094
  if (typeof method !== "string") return target[method];
995
- 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
+ };
996
1103
  },
997
1104
  });
998
1105
  }
@@ -1006,6 +1113,70 @@ function createFfiRequestProxy(ffiRequest: Record<string, Function>): Record<str
1006
1113
  // Non-null accessor for use inside _ffiImpl — these methods are only called when hasFFI is true.
1007
1114
  const core_ = core!;
1008
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
+ }
1009
1180
 
1010
1181
  const _ffiImpl = {
1011
1182
  request: {
@@ -1446,12 +1617,12 @@ const _ffiImpl = {
1446
1617
  webviewDecideNavigation,
1447
1618
  webviewEventJSCallback,
1448
1619
  eventBridgeHandler,
1449
- bunBridgePostmessageHandler,
1620
+ hostBridgePostmessageHandler,
1450
1621
  internalBridgeHandler,
1451
1622
  toCString(secretKey),
1452
1623
  toCString(preload || ""),
1453
1624
  toCString(viewsRoot || ""),
1454
- 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
1455
1626
  startTransparent,
1456
1627
  startPassthrough,
1457
1628
  );
@@ -1543,7 +1714,6 @@ const _ffiImpl = {
1543
1714
  },
1544
1715
 
1545
1716
  createWGPUView: (params: {
1546
- id: number;
1547
1717
  windowId: number;
1548
1718
  frame: {
1549
1719
  x: number;
@@ -1554,9 +1724,8 @@ const _ffiImpl = {
1554
1724
  autoResize: boolean;
1555
1725
  startTransparent: boolean;
1556
1726
  startPassthrough: boolean;
1557
- }): FFIType.ptr => {
1727
+ }): number => {
1558
1728
  const {
1559
- id,
1560
1729
  windowId,
1561
1730
  frame: { x, y, width, height },
1562
1731
  autoResize,
@@ -1564,14 +1733,8 @@ const _ffiImpl = {
1564
1733
  startPassthrough,
1565
1734
  } = params;
1566
1735
 
1567
- const windowPtr = getWindowPtr(windowId);
1568
- if (!windowPtr) {
1569
- throw `Can't add WGPUView to window. window no longer exists`;
1570
- }
1571
-
1572
- const viewPtr = native_.symbols.initWGPUView(
1573
- id,
1574
- windowPtr,
1736
+ const viewId = core_.symbols.createWGPUView(
1737
+ windowId,
1575
1738
  x,
1576
1739
  y,
1577
1740
  width,
@@ -1581,11 +1744,14 @@ const _ffiImpl = {
1581
1744
  startPassthrough,
1582
1745
  );
1583
1746
 
1584
- if (!viewPtr) {
1747
+ if (!viewId) {
1585
1748
  throw "Failed to create WGPUView";
1586
1749
  }
1587
1750
 
1588
- return viewPtr;
1751
+ return viewId;
1752
+ },
1753
+ getWGPUViewPointer: (params: { id: number }): Pointer | null => {
1754
+ return core_.symbols.getWGPUViewPointer(params.id) || null;
1589
1755
  },
1590
1756
 
1591
1757
  wgpuViewSetFrame: (params: {
@@ -1595,16 +1761,8 @@ const _ffiImpl = {
1595
1761
  width: number;
1596
1762
  height: number;
1597
1763
  }) => {
1598
- const view = WGPUView.getById(params.id);
1599
- if (!view?.ptr) {
1600
- console.error(
1601
- `wgpuViewSetFrame: WGPUView not found or has no ptr for id ${params.id}`,
1602
- );
1603
- return;
1604
- }
1605
-
1606
- native_.symbols.wgpuViewSetFrame(
1607
- view.ptr,
1764
+ core_.symbols.setWGPUViewFrame(
1765
+ params.id,
1608
1766
  params.x,
1609
1767
  params.y,
1610
1768
  params.width,
@@ -1613,66 +1771,28 @@ const _ffiImpl = {
1613
1771
  },
1614
1772
 
1615
1773
  wgpuViewSetTransparent: (params: { id: number; transparent: boolean }) => {
1616
- const view = WGPUView.getById(params.id);
1617
- if (!view?.ptr) {
1618
- console.error(
1619
- `wgpuViewSetTransparent: WGPUView not found or has no ptr for id ${params.id}`,
1620
- );
1621
- return;
1622
- }
1623
-
1624
- native_.symbols.wgpuViewSetTransparent(view.ptr, params.transparent);
1774
+ core_.symbols.setWGPUViewTransparent(params.id, params.transparent);
1625
1775
  },
1626
1776
 
1627
1777
  wgpuViewSetPassthrough: (params: {
1628
1778
  id: number;
1629
1779
  passthrough: boolean;
1630
1780
  }) => {
1631
- const view = WGPUView.getById(params.id);
1632
- if (!view?.ptr) {
1633
- console.error(
1634
- `wgpuViewSetPassthrough: WGPUView not found or has no ptr for id ${params.id}`,
1635
- );
1636
- return;
1637
- }
1638
-
1639
- native_.symbols.wgpuViewSetPassthrough(view.ptr, params.passthrough);
1781
+ core_.symbols.setWGPUViewPassthrough(params.id, params.passthrough);
1640
1782
  },
1641
1783
 
1642
1784
  wgpuViewSetHidden: (params: { id: number; hidden: boolean }) => {
1643
- const view = WGPUView.getById(params.id);
1644
- if (!view?.ptr) {
1645
- console.error(
1646
- `wgpuViewSetHidden: WGPUView not found or has no ptr for id ${params.id}`,
1647
- );
1648
- return;
1649
- }
1650
-
1651
- native_.symbols.wgpuViewSetHidden(view.ptr, params.hidden);
1785
+ core_.symbols.setWGPUViewHidden(params.id, params.hidden);
1652
1786
  },
1653
1787
 
1654
1788
  wgpuViewRemove: (params: { id: number }) => {
1655
- const view = WGPUView.getById(params.id);
1656
- if (!view?.ptr) {
1657
- console.error(
1658
- `wgpuViewRemove: WGPUView not found or has no ptr for id ${params.id}`,
1659
- );
1660
- return;
1661
- }
1662
-
1663
- native_.symbols.wgpuViewRemove(view.ptr);
1789
+ core_.symbols.removeWGPUView(params.id);
1664
1790
  },
1665
1791
  wgpuViewGetNativeHandle: (params: { id: number }): Pointer | null => {
1666
- const view = WGPUView.getById(params.id);
1667
- if (!view?.ptr) {
1668
- console.error(
1669
- `wgpuViewGetNativeHandle: WGPUView not found or has no ptr for id ${params.id}`,
1670
- );
1671
- return null;
1672
- }
1673
-
1674
- const handle = native_.symbols.wgpuViewGetNativeHandle(view.ptr);
1675
- return handle || null;
1792
+ return core_.symbols.getWGPUViewNativeHandle(params.id) || null;
1793
+ },
1794
+ runWGPUViewTest: (params: { id: number }) => {
1795
+ core_.symbols.runWGPUViewTest(params.id);
1676
1796
  },
1677
1797
 
1678
1798
  evaluateJavascriptWithNoCompletion: (params: {
@@ -1684,6 +1804,18 @@ const _ffiImpl = {
1684
1804
  toCString(params.js),
1685
1805
  );
1686
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
+ },
1687
1819
  webviewOpenDevTools: (params: { id: number }) => {
1688
1820
  core_.symbols.webviewOpenDevTools(params.id);
1689
1821
  },
@@ -1699,6 +1831,12 @@ const _ffiImpl = {
1699
1831
  webviewGetPageZoom: (params: { id: number }): number => {
1700
1832
  return core_.symbols.webviewGetPageZoom(params.id);
1701
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
+ },
1702
1840
 
1703
1841
  createTray: (params: {
1704
1842
  title: string;
@@ -2243,7 +2381,7 @@ const getMimeType = new JSCallback(
2243
2381
 
2244
2382
  const getHTMLForWebviewSync = new JSCallback(
2245
2383
  (webviewId) => {
2246
- const webview = BrowserView.getById(webviewId);
2384
+ const webview = BrowserView.ensureWrapped(webviewId);
2247
2385
 
2248
2386
  return toCString(webview?.html || "");
2249
2387
  },
@@ -2296,7 +2434,7 @@ if (native) {
2296
2434
  },
2297
2435
  { args: [], returns: "void", threadsafe: true },
2298
2436
  );
2299
- native_.symbols.setQuitRequestedHandler(quitRequestedCallback);
2437
+ core_.symbols.setQuitRequestedHandler(quitRequestedCallback);
2300
2438
 
2301
2439
  const globalShortcutCallback = new JSCallback(
2302
2440
  (acceleratorPtr) => {
@@ -2592,6 +2730,8 @@ const webviewDecideNavigation = new JSCallback(
2592
2730
  );
2593
2731
 
2594
2732
  const webviewEventHandler = (id: number, eventName: string, detail: string) => {
2733
+ BrowserView.ensureWrapped(id);
2734
+
2595
2735
  core_.symbols.dispatchHostWebviewEvent(
2596
2736
  id,
2597
2737
  toCString(eventName),
@@ -2690,7 +2830,7 @@ const webviewEventJSCallback = new JSCallback(
2690
2830
  },
2691
2831
  );
2692
2832
 
2693
- const bunBridgePostmessageHandler = new JSCallback(
2833
+ const hostBridgePostmessageHandler = new JSCallback(
2694
2834
  (id, msg) => {
2695
2835
  try {
2696
2836
  const msgStr = new CString(msg);
@@ -2704,12 +2844,14 @@ const bunBridgePostmessageHandler = new JSCallback(
2704
2844
  }
2705
2845
  const msgJson = JSON.parse(rawMessage);
2706
2846
 
2707
- const webview = BrowserView.getById(id);
2708
- if (!webview) return;
2847
+ const webview = BrowserView.ensureWrapped(id);
2848
+ if (!webview) {
2849
+ return;
2850
+ }
2709
2851
 
2710
2852
  webview.rpcHandler?.(msgJson);
2711
2853
  } catch (err) {
2712
- console.error("error sending message to bun: ", err);
2854
+ console.error("error sending message to host: ", err);
2713
2855
  }
2714
2856
  },
2715
2857
  {
@@ -3030,14 +3172,14 @@ export const internalRpcHandlers = {
3030
3172
  );
3031
3173
  },
3032
3174
  webviewTagUpdateSrc: (params: { id: number; url: string }) => {
3033
- const webview = BrowserView.getById(params.id);
3175
+ const webview = BrowserView.ensureWrapped(params.id);
3034
3176
  if (webview) {
3035
3177
  webview.url = params.url;
3036
3178
  }
3037
3179
  core_.symbols.loadURLInWebView(params.id, toCString(params.url));
3038
3180
  },
3039
3181
  webviewTagUpdateHtml: (params: { id: number; html: string }) => {
3040
- const webview = BrowserView.getById(params.id);
3182
+ const webview = BrowserView.ensureWrapped(params.id);
3041
3183
  if (!webview) {
3042
3184
  console.error(`webviewTagUpdateHtml: BrowserView not found for id ${params.id}`);
3043
3185
  return;
@@ -3047,7 +3189,7 @@ export const internalRpcHandlers = {
3047
3189
  webview.html = params.html;
3048
3190
  },
3049
3191
  webviewTagUpdatePreload: (params: { id: number; preload: string }) => {
3050
- const webview = BrowserView.getById(params.id);
3192
+ const webview = BrowserView.ensureWrapped(params.id);
3051
3193
  if (webview) {
3052
3194
  webview.preload = params.preload;
3053
3195
  }
@@ -3068,7 +3210,7 @@ export const internalRpcHandlers = {
3068
3210
  core_.symbols.webviewReload(params.id);
3069
3211
  },
3070
3212
  webviewTagRemove: (params: { id: number }) => {
3071
- const webview = BrowserView.getById(params.id);
3213
+ const webview = BrowserView.ensureWrapped(params.id);
3072
3214
  if (!webview) {
3073
3215
  console.error(`webviewTagRemove: BrowserView not found for id ${params.id}`);
3074
3216
  return;
@@ -3157,7 +3299,7 @@ export const internalRpcHandlers = {
3157
3299
  },
3158
3300
  webviewTagSetNavigationRules: (params: { id: number; rules: string[] }) => {
3159
3301
  const rulesJson = JSON.stringify(params.rules);
3160
- const webview = BrowserView.getById(params.id);
3302
+ const webview = BrowserView.ensureWrapped(params.id);
3161
3303
  if (webview) {
3162
3304
  webview.navigationRules = rulesJson;
3163
3305
  }
@@ -291,7 +291,7 @@ const WGPU_LIB_NAMES: Record<string, string[]> = {
291
291
  };
292
292
 
293
293
  function findWgpuLibraryPath(): string | null {
294
- const envPath = process.env['ELECTROBUN_WGPU_PATH'];
294
+ const envPath = process.env["ELECTROBUN_WGPU_PATH"];
295
295
  if (envPath && existsSync(envPath)) return envPath;
296
296
 
297
297
  const names = WGPU_LIB_NAMES[process.platform] ?? ["libwebgpu_dawn." + suffix];
@@ -236,6 +236,26 @@ class ElectrobunWebviewTag extends HTMLElement {
236
236
  if (this._sync)
237
237
  this._sync.stop();
238
238
  }
239
+ getInitialNavigationRules() {
240
+ const rawRules = this.getAttribute("navigation-rules");
241
+ if (rawRules === null) {
242
+ return null;
243
+ }
244
+ const trimmed = rawRules.trim();
245
+ if (!trimmed) {
246
+ return [];
247
+ }
248
+ try {
249
+ const parsed = JSON.parse(trimmed);
250
+ if (!Array.isArray(parsed) || !parsed.every((rule) => typeof rule === "string")) {
251
+ throw new Error("navigation-rules must be a JSON string array");
252
+ }
253
+ return parsed;
254
+ } catch (error) {
255
+ console.error("Invalid navigation-rules attribute:", error);
256
+ return [];
257
+ }
258
+ }
239
259
  async initWebview() {
240
260
  const rect = this.getBoundingClientRect();
241
261
  const initialRect = {
@@ -250,6 +270,7 @@ class ElectrobunWebviewTag extends HTMLElement {
250
270
  const partition = this.getAttribute("partition");
251
271
  const renderer = this.getAttribute("renderer") || "native";
252
272
  const masks = this.getAttribute("masks");
273
+ const navigationRules = this.getInitialNavigationRules();
253
274
  const sandbox = this.hasAttribute("sandbox");
254
275
  this.sandboxed = sandbox;
255
276
  const transparent = this.hasAttribute("transparent");
@@ -264,7 +285,7 @@ class ElectrobunWebviewTag extends HTMLElement {
264
285
  masks.split(",").forEach((s) => this.maskSelectors.add(s.trim()));
265
286
  }
266
287
  try {
267
- const webviewId = await request("webviewTagInit", {
288
+ const webviewInitParams = {
268
289
  hostWebviewId: window.__electrobunWebviewId,
269
290
  windowId: window.__electrobunWindowId,
270
291
  renderer,
@@ -278,11 +299,12 @@ class ElectrobunWebviewTag extends HTMLElement {
278
299
  x: rect.x,
279
300
  y: rect.y
280
301
  },
281
- navigationRules: null,
282
302
  sandbox,
283
303
  transparent,
284
- passthrough
285
- });
304
+ passthrough,
305
+ ...navigationRules === null ? {} : { navigationRules }
306
+ };
307
+ const webviewId = await request("webviewTagInit", webviewInitParams);
286
308
  this.webviewId = webviewId;
287
309
  this.id = `electrobun-webview-${webviewId}`;
288
310
  webviewRegistry[webviewId] = this;
@@ -859,18 +881,24 @@ initEncryption().catch((err) => console.error("Failed to initialize encryption:"
859
881
  var internalMessageHandler = (msg) => {
860
882
  handleResponse(msg);
861
883
  };
884
+ var defaultUserMessageHandler = (msg) => {
885
+ if (!window.__electrobunPendingHostMessages) {
886
+ window.__electrobunPendingHostMessages = [];
887
+ }
888
+ window.__electrobunPendingHostMessages.push(msg);
889
+ };
862
890
  if (!window.__electrobun) {
863
891
  window.__electrobun = {
892
+ receiveInternalMessageFromHost: internalMessageHandler,
893
+ receiveMessageFromHost: defaultUserMessageHandler,
864
894
  receiveInternalMessageFromBun: internalMessageHandler,
865
- receiveMessageFromBun: (msg) => {
866
- console.log("receiveMessageFromBun (no handler):", msg);
867
- }
895
+ receiveMessageFromBun: defaultUserMessageHandler
868
896
  };
869
897
  } else {
898
+ window.__electrobun.receiveInternalMessageFromHost = internalMessageHandler;
899
+ window.__electrobun.receiveMessageFromHost = defaultUserMessageHandler;
870
900
  window.__electrobun.receiveInternalMessageFromBun = internalMessageHandler;
871
- window.__electrobun.receiveMessageFromBun = (msg) => {
872
- console.log("receiveMessageFromBun (no handler):", msg);
873
- };
901
+ window.__electrobun.receiveMessageFromBun = defaultUserMessageHandler;
874
902
  }
875
903
  window.__electrobunSendToHost = (message) => {
876
904
  emitWebviewEvent("host-message", JSON.stringify(message));