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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (63) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +31 -170
  3. package/bin/electrobun.cjs +230 -153
  4. package/package.json +18 -49
  5. package/bun.lock +0 -119
  6. package/dist/api/browser/builtinrpcSchema.ts +0 -19
  7. package/dist/api/browser/global.d.ts +0 -36
  8. package/dist/api/browser/index.ts +0 -234
  9. package/dist/api/browser/webviewtag.ts +0 -88
  10. package/dist/api/browser/wgputag.ts +0 -48
  11. package/dist/api/bun/ElectrobunConfig.ts +0 -530
  12. package/dist/api/bun/__tests__/ffi-contract.test.ts +0 -105
  13. package/dist/api/bun/core/ApplicationMenu.ts +0 -70
  14. package/dist/api/bun/core/BrowserView.ts +0 -419
  15. package/dist/api/bun/core/BrowserWindow.ts +0 -400
  16. package/dist/api/bun/core/BuildConfig.ts +0 -71
  17. package/dist/api/bun/core/ContextMenu.ts +0 -75
  18. package/dist/api/bun/core/GpuWindow.ts +0 -289
  19. package/dist/api/bun/core/Paths.ts +0 -5
  20. package/dist/api/bun/core/Socket.ts +0 -22
  21. package/dist/api/bun/core/Tray.ts +0 -197
  22. package/dist/api/bun/core/Updater.ts +0 -1162
  23. package/dist/api/bun/core/Utils.ts +0 -487
  24. package/dist/api/bun/core/WGPUView.ts +0 -167
  25. package/dist/api/bun/core/menuRoles.ts +0 -181
  26. package/dist/api/bun/events/ApplicationEvents.ts +0 -22
  27. package/dist/api/bun/events/event.ts +0 -29
  28. package/dist/api/bun/events/eventEmitter.ts +0 -45
  29. package/dist/api/bun/events/trayEvents.ts +0 -11
  30. package/dist/api/bun/events/webviewEvents.ts +0 -39
  31. package/dist/api/bun/events/windowEvents.ts +0 -23
  32. package/dist/api/bun/index.ts +0 -298
  33. package/dist/api/bun/preload/.generated/compiled.ts +0 -8
  34. package/dist/api/bun/preload/build.ts +0 -65
  35. package/dist/api/bun/preload/dragRegions.ts +0 -41
  36. package/dist/api/bun/preload/encryption.ts +0 -86
  37. package/dist/api/bun/preload/events.ts +0 -171
  38. package/dist/api/bun/preload/globals.d.ts +0 -45
  39. package/dist/api/bun/preload/index-sandboxed.ts +0 -28
  40. package/dist/api/bun/preload/index.ts +0 -77
  41. package/dist/api/bun/preload/internalRpc.ts +0 -80
  42. package/dist/api/bun/preload/overlaySync.ts +0 -107
  43. package/dist/api/bun/preload/webviewTag.ts +0 -451
  44. package/dist/api/bun/preload/wgpuTag.ts +0 -246
  45. package/dist/api/bun/proc/linux.md +0 -43
  46. package/dist/api/bun/proc/native.ts +0 -3385
  47. package/dist/api/bun/webGPU.ts +0 -346
  48. package/dist/api/bun/webgpuAdapter.ts +0 -3011
  49. package/dist/api/shared/bun-version.ts +0 -3
  50. package/dist/api/shared/cef-version.ts +0 -5
  51. package/dist/api/shared/electrobun-version.ts +0 -2
  52. package/dist/api/shared/naming.test.ts +0 -327
  53. package/dist/api/shared/naming.ts +0 -188
  54. package/dist/api/shared/platform.ts +0 -48
  55. package/dist/api/shared/rpc.ts +0 -541
  56. package/dist/main.js +0 -168
  57. package/dist/preload-full.js +0 -913
  58. package/dist/preload-sandboxed.js +0 -111
  59. package/dist/zig-sdk/electrobun.zig +0 -1993
  60. package/src/cli/bun.lockb +0 -0
  61. package/src/cli/index.ts +0 -5689
  62. package/src/cli/package-lock.json +0 -81
  63. package/src/cli/package.json +0 -11
@@ -1,400 +0,0 @@
1
- import { ffi } from "../proc/native";
2
- import electrobunEventEmitter from "../events/eventEmitter";
3
- import { BrowserView } from "./BrowserView";
4
- import { type Pointer } from "bun:ffi";
5
- import { BuildConfig } from "./BuildConfig";
6
- import { type RPCWithTransport } from "../../shared/rpc.js";
7
- import { WGPUView } from "./WGPUView";
8
-
9
- const buildConfig = BuildConfig.getSync();
10
- ffi.request.setExitOnLastWindowClosed({
11
- enabled: buildConfig.runtime?.exitOnLastWindowClosed ?? true,
12
- });
13
-
14
- export type WindowOptionsType<T = undefined> = {
15
- trafficLightOffset?: {
16
- x: number;
17
- y: number;
18
- };
19
- activate?: boolean;
20
- title: string;
21
- frame: {
22
- x: number;
23
- y: number;
24
- width: number;
25
- height: number;
26
- };
27
- url: string | null;
28
- html: string | null;
29
- preload: string | null;
30
- viewsRoot: string | null;
31
- renderer: "native" | "cef";
32
- rpc?: T;
33
- styleMask?: {};
34
- // titleBarStyle options:
35
- // - 'default': normal titlebar with native window controls
36
- // - 'hidden': no titlebar, no native window controls (for fully custom chrome)
37
- // - 'hiddenInset': transparent titlebar with inset native controls
38
- titleBarStyle: "hidden" | "hiddenInset" | "default";
39
- // transparent: when true, window background is transparent (see-through)
40
- transparent: boolean;
41
- // passthrough: when true, mouse events pass through transparent regions
42
- passthrough: boolean;
43
- hidden?: boolean;
44
- navigationRules: string | null;
45
- // Sandbox mode: when true, disables RPC and only allows event emission
46
- // Use for untrusted content (remote URLs) to prevent malicious sites from
47
- // accessing internal APIs, creating OOPIFs, or communicating with Bun
48
- sandbox: boolean;
49
- };
50
-
51
- const defaultOptions: WindowOptionsType = {
52
- title: "Electrobun",
53
- frame: {
54
- x: 0,
55
- y: 0,
56
- width: 800,
57
- height: 600,
58
- },
59
- url: "https://electrobun.dev",
60
- html: null,
61
- preload: null,
62
- viewsRoot: null,
63
- renderer: buildConfig.defaultRenderer,
64
- titleBarStyle: "default",
65
- transparent: false,
66
- passthrough: false,
67
- hidden: false,
68
- navigationRules: null,
69
- sandbox: false,
70
- };
71
-
72
- export const BrowserWindowMap: {
73
- [id: number]: BrowserWindow<RPCWithTransport>;
74
- } = {};
75
-
76
- // Clean up JS wrapper state when a window closes. Native child cleanup is core-owned.
77
- electrobunEventEmitter.on("close", (event: { data: { id: number } }) => {
78
- const windowId = event.data.id;
79
- delete BrowserWindowMap[windowId];
80
-
81
- // Clean up all webviews associated with this window
82
- for (const view of BrowserView.getAll()) {
83
- if (view.windowId === windowId) {
84
- view.remove();
85
- }
86
- }
87
-
88
- // Clean up all WGPU views associated with this window
89
- const wgpuViews = WGPUView.getAll().filter(v => v.windowId === windowId);
90
- for (const view of wgpuViews) {
91
- try {
92
- view.remove();
93
- } catch (e) {
94
- console.error(`Error cleaning up WGPU view ${view.id}:`, e);
95
- }
96
- }
97
-
98
- });
99
-
100
- export class BrowserWindow<T extends RPCWithTransport = RPCWithTransport> {
101
- id = 0;
102
- title: string = "Electrobun";
103
- state: "creating" | "created" = "creating";
104
- url: string | null = null;
105
- html: string | null = null;
106
- preload: string | null = null;
107
- viewsRoot: string | null = null;
108
- renderer: "native" | "cef" = "native";
109
- transparent: boolean = false;
110
- passthrough: boolean = false;
111
- hidden: boolean = false;
112
- trafficLightOffset: { x: number; y: number } = { x: 0, y: 0 };
113
- navigationRules: string | null = null;
114
- // Sandbox mode disables RPC and only allows event emission (for untrusted content)
115
- sandbox: boolean = false;
116
- frame: {
117
- x: number;
118
- y: number;
119
- width: number;
120
- height: number;
121
- } = {
122
- x: 0,
123
- y: 0,
124
- width: 800,
125
- height: 600,
126
- };
127
- // todo (yoav): make this an array of ids or something
128
- webviewId!: number;
129
-
130
- get ptr(): Pointer | null {
131
- return ffi.request.getWindowPointer({ winId: this.id }) as Pointer | null;
132
- }
133
-
134
- constructor(options: Partial<WindowOptionsType<T>> = defaultOptions) {
135
- this.title = options.title || "New Window";
136
- this.frame = options.frame
137
- ? { ...defaultOptions.frame, ...options.frame }
138
- : { ...defaultOptions.frame };
139
- this.url = options.url || null;
140
- this.html = options.html || null;
141
- this.preload = options.preload || null;
142
- this.viewsRoot = options.viewsRoot || null;
143
- this.renderer = options.renderer || defaultOptions.renderer;
144
- this.transparent = options.transparent ?? false;
145
- this.passthrough = options.passthrough ?? false;
146
- this.hidden = options.hidden ?? false;
147
- this.trafficLightOffset = {
148
- x: options.trafficLightOffset?.x ?? 0,
149
- y: options.trafficLightOffset?.y ?? 0,
150
- };
151
- this.navigationRules = options.navigationRules || null;
152
- this.sandbox = options.sandbox ?? false;
153
-
154
- this.init(options);
155
- }
156
-
157
- init({
158
- rpc,
159
- styleMask,
160
- titleBarStyle,
161
- transparent,
162
- hidden,
163
- activate,
164
- }: Partial<WindowOptionsType<T>>) {
165
- const windowId = ffi.request.createWindow({
166
- title: this.title,
167
- url: this.url || "",
168
- frame: {
169
- width: this.frame.width,
170
- height: this.frame.height,
171
- x: this.frame.x,
172
- y: this.frame.y,
173
- },
174
- styleMask: {
175
- Borderless: false,
176
- Titled: true,
177
- Closable: true,
178
- Miniaturizable: true,
179
- Resizable: true,
180
- UnifiedTitleAndToolbar: false,
181
- FullScreen: false,
182
- FullSizeContentView: false,
183
- UtilityWindow: false,
184
- DocModalWindow: false,
185
- NonactivatingPanel: false,
186
- HUDWindow: false,
187
- ...(styleMask || {}),
188
- // hiddenInset: transparent titlebar with inset native controls
189
- ...(titleBarStyle === "hiddenInset"
190
- ? {
191
- Titled: true,
192
- FullSizeContentView: true,
193
- }
194
- : {}),
195
- // hidden: no titlebar, no native controls (for fully custom chrome)
196
- ...(titleBarStyle === "hidden"
197
- ? {
198
- Titled: false,
199
- FullSizeContentView: true,
200
- }
201
- : {}),
202
- },
203
- titleBarStyle: titleBarStyle || "default",
204
- transparent: transparent ?? false,
205
- hidden: hidden ?? false,
206
- activate: activate ?? true,
207
- trafficLightOffset: this.trafficLightOffset,
208
- });
209
-
210
- if (!windowId) {
211
- throw "Failed to create window";
212
- }
213
-
214
- this.id = windowId as number;
215
-
216
- BrowserWindowMap[this.id] = this;
217
-
218
- // todo (yoav): user should be able to override this and pass in their
219
- // own webview instance, or instances for attaching to the window.
220
- const webview = new BrowserView({
221
- // TODO: decide whether we want to keep sending url/html
222
- // here, if we're manually calling loadURL/loadHTML below
223
- // then we can remove it from the api here
224
- url: this.url,
225
- html: this.html,
226
- preload: this.preload,
227
- viewsRoot: this.viewsRoot,
228
- // frame: this.frame,
229
- renderer: this.renderer,
230
- frame: {
231
- x: 0,
232
- y: 0,
233
- width: this.frame.width,
234
- height: this.frame.height,
235
- },
236
- rpc,
237
- // todo: we need to send the window here and attach it in one go
238
- // then the view creation code in objc can toggle between offscreen
239
- // or on screen views depending on if windowId is null
240
- // does this mean browserView needs to track the windowId or handle it ephemerally?
241
- windowId: this.id,
242
- navigationRules: this.navigationRules,
243
- sandbox: this.sandbox,
244
- startPassthrough: this.passthrough,
245
- });
246
-
247
- this.webviewId = webview.id;
248
- }
249
-
250
- get webview() {
251
- // todo (yoav): we don't want this to be undefined, so maybe we should just
252
- // link directly to the browserview object instead of a getter
253
- return BrowserView.getById(this.webviewId) as BrowserView<T>;
254
- }
255
-
256
- static getById(id: number) {
257
- return BrowserWindowMap[id];
258
- }
259
-
260
- setTitle(title: string) {
261
- this.title = title;
262
- return ffi.request.setTitle({ winId: this.id, title });
263
- }
264
-
265
- close() {
266
- return ffi.request.closeWindow({ winId: this.id });
267
- }
268
-
269
- activate() {
270
- return ffi.request.activateWindow({ winId: this.id });
271
- }
272
-
273
- focus() {
274
- console.log(
275
- "[electrobun] BrowserWindow.focus() is deprecated. Use window.activate() instead.",
276
- );
277
- return this.activate();
278
- }
279
-
280
- show() {
281
- return ffi.request.showWindow({ winId: this.id, activate: true });
282
- }
283
-
284
- showInactive() {
285
- return ffi.request.showWindow({ winId: this.id, activate: false });
286
- }
287
-
288
- hide() {
289
- return ffi.request.hideWindow({ winId: this.id });
290
- }
291
-
292
- minimize() {
293
- return ffi.request.minimizeWindow({ winId: this.id });
294
- }
295
-
296
- unminimize() {
297
- return ffi.request.restoreWindow({ winId: this.id });
298
- }
299
-
300
- isMinimized(): boolean {
301
- return ffi.request.isWindowMinimized({ winId: this.id });
302
- }
303
-
304
- maximize() {
305
- return ffi.request.maximizeWindow({ winId: this.id });
306
- }
307
-
308
- unmaximize() {
309
- return ffi.request.unmaximizeWindow({ winId: this.id });
310
- }
311
-
312
- isMaximized(): boolean {
313
- return ffi.request.isWindowMaximized({ winId: this.id });
314
- }
315
-
316
- setFullScreen(fullScreen: boolean) {
317
- return ffi.request.setWindowFullScreen({ winId: this.id, fullScreen });
318
- }
319
-
320
- isFullScreen(): boolean {
321
- return ffi.request.isWindowFullScreen({ winId: this.id });
322
- }
323
-
324
- setAlwaysOnTop(alwaysOnTop: boolean) {
325
- return ffi.request.setWindowAlwaysOnTop({ winId: this.id, alwaysOnTop });
326
- }
327
-
328
- isAlwaysOnTop(): boolean {
329
- return ffi.request.isWindowAlwaysOnTop({ winId: this.id });
330
- }
331
-
332
- setVisibleOnAllWorkspaces(visibleOnAllWorkspaces: boolean) {
333
- return ffi.request.setWindowVisibleOnAllWorkspaces({ winId: this.id, visibleOnAllWorkspaces });
334
- }
335
-
336
- isVisibleOnAllWorkspaces(): boolean {
337
- return ffi.request.isWindowVisibleOnAllWorkspaces({ winId: this.id });
338
- }
339
-
340
- setPosition(x: number, y: number) {
341
- this.frame.x = x;
342
- this.frame.y = y;
343
- return ffi.request.setWindowPosition({ winId: this.id, x, y });
344
- }
345
-
346
- setWindowButtonPosition(x: number, y: number) {
347
- return ffi.request.setWindowButtonPosition({ winId: this.id, x, y });
348
- }
349
-
350
- setSize(width: number, height: number) {
351
- this.frame.width = width;
352
- this.frame.height = height;
353
- return ffi.request.setWindowSize({ winId: this.id, width, height });
354
- }
355
-
356
- setFrame(x: number, y: number, width: number, height: number) {
357
- this.frame = { x, y, width, height };
358
- return ffi.request.setWindowFrame({ winId: this.id, x, y, width, height });
359
- }
360
-
361
- getFrame(): { x: number; y: number; width: number; height: number } {
362
- const frame = ffi.request.getWindowFrame({ winId: this.id });
363
- // Update internal state
364
- this.frame = frame;
365
- return frame;
366
- }
367
-
368
- getPosition(): { x: number; y: number } {
369
- const frame = this.getFrame();
370
- return { x: frame.x, y: frame.y };
371
- }
372
-
373
- getSize(): { width: number; height: number } {
374
- const frame = this.getFrame();
375
- return { width: frame.width, height: frame.height };
376
- }
377
-
378
- /**
379
- * Set the page zoom level for the window's webview (WebKit only).
380
- * @param zoomLevel - The zoom level (1.0 = 100%, 1.5 = 150%, etc.)
381
- */
382
- setPageZoom(zoomLevel: number) {
383
- this.webview?.setPageZoom(zoomLevel);
384
- }
385
-
386
- /**
387
- * Get the current page zoom level for the window's webview.
388
- * @returns The current zoom level (1.0 = 100%)
389
- */
390
- getPageZoom(): number {
391
- return this.webview?.getPageZoom() ?? 1.0;
392
- }
393
-
394
- // todo (yoav): move this to a class that also has off, append, prepend, etc.
395
- // name should only allow browserWindow events
396
- on(name: string, handler: (event: unknown) => void) {
397
- const specificName = `${name}-${this.id}`;
398
- electrobunEventEmitter.on(specificName, handler);
399
- }
400
- }
@@ -1,71 +0,0 @@
1
- import { readFileSync } from "fs";
2
-
3
- export type BuildConfigType = {
4
- defaultRenderer: "native" | "cef";
5
- availableRenderers: ("native" | "cef")[];
6
- cefVersion?: string;
7
- bunVersion?: string;
8
- runtime?: {
9
- exitOnLastWindowClosed?: boolean;
10
- [key: string]: unknown;
11
- };
12
- };
13
-
14
- let buildConfig: BuildConfigType | null = null;
15
-
16
- function fallbackBuildConfig(): BuildConfigType {
17
- return {
18
- defaultRenderer: "native",
19
- availableRenderers: ["native"],
20
- };
21
- }
22
-
23
- const BuildConfig = {
24
- /**
25
- * Get the build configuration. Loads from build.json on first call, then returns cached value.
26
- */
27
- get: async (): Promise<BuildConfigType> => {
28
- if (buildConfig) {
29
- return buildConfig;
30
- }
31
-
32
- try {
33
- const resourcesDir = "Resources";
34
- buildConfig = await Bun.file(`../${resourcesDir}/build.json`).json();
35
- return buildConfig!;
36
- } catch (error) {
37
- // Fallback for dev mode or missing file
38
- buildConfig = fallbackBuildConfig();
39
- return buildConfig;
40
- }
41
- },
42
-
43
- /**
44
- * Get the build configuration synchronously.
45
- * Useful for modules that cannot use top-level await.
46
- */
47
- getSync: (): BuildConfigType => {
48
- if (buildConfig) {
49
- return buildConfig;
50
- }
51
-
52
- try {
53
- const resourcesDir = "Resources";
54
- buildConfig = JSON.parse(
55
- readFileSync(`../${resourcesDir}/build.json`, "utf8"),
56
- ) as BuildConfigType;
57
- return buildConfig;
58
- } catch (error) {
59
- buildConfig = fallbackBuildConfig();
60
- return buildConfig;
61
- }
62
- },
63
-
64
- /**
65
- * Get the cached build configuration synchronously.
66
- * Returns null if config hasn't been loaded yet.
67
- */
68
- getCached: (): BuildConfigType | null => buildConfig,
69
- };
70
-
71
- export { BuildConfig };
@@ -1,75 +0,0 @@
1
- // Context menus intentionally share ApplicationMenuItemConfig for now:
2
- // - macOS supports role-based context menu items.
3
- // - Windows has partial role behavior.
4
- // - Linux showContextMenu is currently unsupported.
5
- // Keep role typed here to avoid removing working macOS capability.
6
- import { ffi, type ApplicationMenuItemConfig } from "../proc/native";
7
- import electrobunEventEmitter from "../events/eventEmitter";
8
- import { roleLabelMap } from "./menuRoles";
9
-
10
- type NonDividerMenuItem = {
11
- type?: "normal";
12
- label?: string;
13
- tooltip?: string;
14
- action?: string;
15
- role?: string;
16
- data?: unknown;
17
- submenu?: Array<ApplicationMenuItemConfig>;
18
- enabled?: boolean;
19
- checked?: boolean;
20
- hidden?: boolean;
21
- accelerator?: string;
22
- };
23
-
24
- export const showContextMenu = (menu: Array<ApplicationMenuItemConfig>) => {
25
- const menuWithDefaults = menuConfigWithDefaults(menu);
26
- ffi.request.showContextMenu({
27
- menuConfig: JSON.stringify(menuWithDefaults),
28
- });
29
- };
30
-
31
- export const on = (
32
- name: "context-menu-clicked",
33
- handler: (event: unknown) => void,
34
- ) => {
35
- const specificName = `${name}`;
36
- electrobunEventEmitter.on(specificName, handler);
37
- };
38
-
39
- const menuConfigWithDefaults = (
40
- menu: Array<ApplicationMenuItemConfig>,
41
- ): Array<ApplicationMenuItemConfig> => {
42
- return menu.map((item) => {
43
- if (item.type === "divider" || item.type === "separator") {
44
- return { type: "divider" } as const;
45
- } else {
46
- const menuItem = item as NonDividerMenuItem;
47
- // Use shared serialization method
48
- const actionWithDataId = ffi.internal.serializeMenuAction(
49
- menuItem.action || "",
50
- menuItem.data,
51
- );
52
-
53
- return {
54
- label:
55
- menuItem.label ||
56
- roleLabelMap[menuItem.role as keyof typeof roleLabelMap] ||
57
- "",
58
- type: menuItem.type || "normal",
59
- // application menus can either have an action or a role. not both.
60
- ...(menuItem.role
61
- ? { role: menuItem.role }
62
- : { action: actionWithDataId }),
63
- // default enabled to true unless explicitly set to false
64
- enabled: menuItem.enabled === false ? false : true,
65
- checked: Boolean(menuItem.checked),
66
- hidden: Boolean(menuItem.hidden),
67
- tooltip: menuItem.tooltip || undefined,
68
- ...(menuItem.accelerator ? { accelerator: menuItem.accelerator } : {}),
69
- ...(menuItem.submenu
70
- ? { submenu: menuConfigWithDefaults(menuItem.submenu) }
71
- : {}),
72
- };
73
- }
74
- });
75
- };