electrobun 0.1.8 → 0.1.10

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.
@@ -122,6 +122,18 @@ export class BrowserView<T> {
122
122
 
123
123
  BrowserViewMap[this.id] = this;
124
124
  this.ptr = this.init();
125
+
126
+ // If HTML content was provided, load it after webview creation
127
+ if (this.html) {
128
+ console.log(`DEBUG: BrowserView constructor triggering loadHTML for webview ${this.id}`);
129
+ // Small delay to ensure webview is ready
130
+ setTimeout(() => {
131
+ console.log(`DEBUG: BrowserView delayed loadHTML for webview ${this.id}`);
132
+ this.loadHTML(this.html!);
133
+ }, 100); // Back to 100ms since we fixed the race condition
134
+ } else {
135
+ console.log(`DEBUG: BrowserView constructor - no HTML provided for webview ${this.id}`);
136
+ }
125
137
  }
126
138
 
127
139
  init() {
@@ -138,7 +150,8 @@ export class BrowserView<T> {
138
150
  hostWebviewId: this.hostWebviewId || null,
139
151
  pipePrefix: this.pipePrefix,
140
152
  partition: this.partition,
141
- url: this.url,
153
+ // Only pass URL if no HTML content is provided to avoid conflicts
154
+ url: this.html ? null : this.url,
142
155
  html: this.html,
143
156
  preload: this.preload,
144
157
  frame: {
@@ -194,16 +207,23 @@ export class BrowserView<T> {
194
207
  }
195
208
 
196
209
  loadURL(url: string) {
210
+ console.log(`DEBUG: loadURL called for webview ${this.id}: ${url}`);
197
211
  this.url = url;
198
212
  native.symbols.loadURLInWebView(this.ptr, toCString(this.url))
199
213
  }
200
214
 
201
215
  loadHTML(html: string) {
202
- // Note: CEF doesn't natively support "setting html" so we just return
203
- // this BrowserView's html when a special views:// url is hit.
204
- // So we can update that content and ask the webview to load that url
205
216
  this.html = html;
206
- this.loadURL('views://internal/index.html');
217
+ console.log(`DEBUG: Setting HTML content for webview ${this.id}:`, html.substring(0, 50) + '...');
218
+
219
+ if (this.renderer === 'cef') {
220
+ // For CEF, store HTML content in native map and use scheme handler
221
+ native.symbols.setWebviewHTMLContent(this.id, toCString(html));
222
+ this.loadURL('views://internal/index.html');
223
+ } else {
224
+ // For WKWebView, load HTML content directly
225
+ native.symbols.loadHTMLInWebView(this.ptr, toCString(html));
226
+ }
207
227
  }
208
228
 
209
229
 
@@ -12,5 +12,13 @@ export default {
12
12
  domReady: (data) =>
13
13
  new ElectrobunEvent<{ detail: string }, {}>("dom-ready", data),
14
14
  newWindowOpen: (data) =>
15
- new ElectrobunEvent<{ detail: string }, {}>("new-window-open", data),
15
+ new ElectrobunEvent<{
16
+ detail: string | {
17
+ url: string;
18
+ isCmdClick: boolean;
19
+ modifierFlags?: number;
20
+ targetDisposition?: number;
21
+ userGesture?: boolean;
22
+ }
23
+ }, {}>("new-window-open", data),
16
24
  };
@@ -110,6 +110,10 @@ export const native = (() => {
110
110
  args: [FFIType.ptr, FFIType.cstring],
111
111
  returns: FFIType.void
112
112
  },
113
+ loadHTMLInWebView: {
114
+ args: [FFIType.ptr, FFIType.cstring],
115
+ returns: FFIType.void
116
+ },
113
117
 
114
118
  updatePreloadScriptToWebView: {
115
119
  args: [
@@ -136,6 +140,10 @@ export const native = (() => {
136
140
  args: [FFIType.ptr],
137
141
  returns: FFIType.void
138
142
  },
143
+ setWebviewHTMLContent: {
144
+ args: [FFIType.u32, FFIType.cstring],
145
+ returns: FFIType.void
146
+ },
139
147
  startWindowMove: {
140
148
  args: [FFIType.ptr],
141
149
  returns: FFIType.void
@@ -907,14 +915,32 @@ const webviewDecideNavigation = new JSCallback((webviewId, url) => {
907
915
  });
908
916
 
909
917
 
910
- const webviewEventHandler = (id, eventName, detail) => {
918
+ const webviewEventHandler = (id, eventName, detail) => {
911
919
  const webview = BrowserView.getById(id);
920
+ if (!webview) {
921
+ console.error('[webviewEventHandler] No webview found for id:', id);
922
+ return;
923
+ }
924
+
912
925
  if (webview.hostWebviewId) {
926
+ const hostWebview = BrowserView.getById(webview.hostWebviewId);
927
+
928
+ if (!hostWebview) {
929
+ console.error('[webviewEventHandler] No webview found for id:', id);
930
+ return;
931
+ }
932
+
913
933
  // This is a webviewtag so we should send the event into the parent as well
914
934
  // TODO XX: escape event name and detail to remove `
915
- const js = `document.querySelector('#electrobun-webview-${id}').emit(\`${eventName}\`, \`${detail}\`);`
935
+ // NOTE: for new-window-open the detail is a json string that needs to be parsed
936
+ let js;
937
+ if (eventName === 'new-window-open') {
938
+ js = `document.querySelector('#electrobun-webview-${id}').emit(\`${eventName}\`, ${detail});`
939
+ } else {
940
+ js = `document.querySelector('#electrobun-webview-${id}').emit(\`${eventName}\`, \`${detail}\`);`
941
+ }
916
942
 
917
- native.symbols.evaluateJavaScriptWithNoCompletion(webview.ptr, toCString(js))
943
+ native.symbols.evaluateJavaScriptWithNoCompletion(hostWebview.ptr, toCString(js))
918
944
  }
919
945
 
920
946
  const eventMap = {
@@ -931,28 +957,50 @@ const webviewEventHandler = (id, eventName, detail) => {
931
957
  electrobunEventEmitter.events.webview[eventMap[eventName]];
932
958
 
933
959
  if (!handler) {
934
-
960
+ console.error('[webviewEventHandler] No handler found for event:', eventName, '(mapped to:', eventMap[eventName], ')');
935
961
  return { success: false };
936
962
  }
937
963
 
964
+ // Parse JSON data for new-window-open events
965
+ let parsedDetail = detail;
966
+ if (eventName === "new-window-open") {
967
+ try {
968
+ parsedDetail = JSON.parse(detail);
969
+ } catch (e) {
970
+ console.error('[webviewEventHandler] Failed to parse JSON:', e);
971
+ // Fallback to string if parsing fails (backward compatibility)
972
+ parsedDetail = detail;
973
+ }
974
+ }
975
+
938
976
  const event = handler({
939
977
  id,
940
- detail,
941
- });
978
+ detail: parsedDetail,
979
+ });
942
980
 
943
981
  let result;
944
- // global event
945
- result = electrobunEventEmitter.emitEvent(event);
946
- result = electrobunEventEmitter.emitEvent(event, id);
982
+ // global event
983
+ result = electrobunEventEmitter.emitEvent(event);
984
+ result = electrobunEventEmitter.emitEvent(event, id);
947
985
  }
948
986
 
949
- const webviewEventJSCallback = new JSCallback((id, _eventName, _detail) => {
950
- const eventName = new CString(_eventName);
951
- const detail = new CString(_detail);
952
-
987
+ const webviewEventJSCallback = new JSCallback((id, _eventName, _detail) => {
988
+ let eventName = "";
989
+ let detail = "";
990
+
991
+ try {
992
+ // Convert cstring pointers to actual strings
993
+ eventName = new CString(_eventName).toString();
994
+ detail = new CString(_detail).toString();
995
+ } catch (err) {
996
+ console.error('[webviewEventJSCallback] Error converting strings:', err);
997
+ console.error('[webviewEventJSCallback] Raw values:', { _eventName, _detail });
998
+ return;
999
+ }
1000
+
953
1001
  webviewEventHandler(id, eventName, detail);
954
1002
  }, {
955
- args: [FFIType.u32, FFIType.cstring],
1003
+ args: [FFIType.u32, FFIType.cstring, FFIType.cstring],
956
1004
  returns: FFIType.void,
957
1005
  threadsafe: true
958
1006
  });
@@ -1200,6 +1248,10 @@ export const internalRpcHandlers = {
1200
1248
  console.error(`webviewTagUpdateHtml: BrowserView not found or has no ptr for id ${params.id}`);
1201
1249
  return;
1202
1250
  }
1251
+
1252
+ // Store HTML content in native map for scheme handlers
1253
+ native.symbols.setWebviewHTMLContent(webview.id, toCString(params.html));
1254
+
1203
1255
  webview.loadHTML(params.html);
1204
1256
  webview.html = params.html;
1205
1257
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "electrobun",
3
- "version": "0.1.8",
3
+ "version": "0.1.10",
4
4
  "description": "Build ultra fast, tiny, and cross-platform desktop apps with Typescript.",
5
5
  "license": "MIT",
6
6
  "author": "Blackboard Technologies Inc.",
package/src/cli/index.ts CHANGED
@@ -261,7 +261,7 @@ async function ensureCoreDependencies(targetOS?: 'macos' | 'win' | 'linux', targ
261
261
  if (existsSync(extractedMainJs) && !existsSync(sharedMainJs)) {
262
262
  console.log('Development fallback: copying main.js from platform-specific download to shared dist/');
263
263
  mkdirSync(sharedDistPath, { recursive: true });
264
- cpSync(extractedMainJs, sharedMainJs);
264
+ cpSync(extractedMainJs, sharedMainJs, { dereference: true });
265
265
  }
266
266
 
267
267
  console.log(`Core dependencies for ${platformOS}-${platformArch} downloaded and cached successfully`);
@@ -1122,7 +1122,7 @@ if (commandArg === "init") {
1122
1122
  dereference: true,
1123
1123
  });
1124
1124
 
1125
- cpSync(targetPaths.MAIN_JS, join(appBundleFolderResourcesPath, 'main.js'));
1125
+ cpSync(targetPaths.MAIN_JS, join(appBundleFolderResourcesPath, 'main.js'), { dereference: true });
1126
1126
 
1127
1127
  // Bun runtime binary
1128
1128
  // todo (yoav): this only works for the current architecture
@@ -1163,7 +1163,7 @@ if (commandArg === "init") {
1163
1163
  "WebView2Loader.dll"
1164
1164
  ); ;
1165
1165
  // copy webview2 system webview library
1166
- cpSync(webview2LibSource, webview2LibDestination);
1166
+ cpSync(webview2LibSource, webview2LibDestination, { dereference: true });
1167
1167
 
1168
1168
  } else if (targetOS === 'linux') {
1169
1169
  // Choose the appropriate native wrapper based on bundleCEF setting
@@ -1250,7 +1250,7 @@ if (commandArg === "init") {
1250
1250
  const sourcePath = join(cefSourcePath, dllFile);
1251
1251
  const destPath = join(appBundleMacOSPath, dllFile);
1252
1252
  if (existsSync(sourcePath)) {
1253
- cpSync(sourcePath, destPath);
1253
+ cpSync(sourcePath, destPath, { dereference: true });
1254
1254
  }
1255
1255
  });
1256
1256
 
@@ -1258,7 +1258,7 @@ if (commandArg === "init") {
1258
1258
  const icuDataSource = join(cefSourcePath, 'icudtl.dat');
1259
1259
  const icuDataDest = join(appBundleMacOSPath, 'icudtl.dat');
1260
1260
  if (existsSync(icuDataSource)) {
1261
- cpSync(icuDataSource, icuDataDest);
1261
+ cpSync(icuDataSource, icuDataDest, { dereference: true });
1262
1262
  }
1263
1263
 
1264
1264
  // Copy essential CEF pak files to MacOS root (same folder as libcef.dll) - required for CEF resources
@@ -1268,7 +1268,7 @@ if (commandArg === "init") {
1268
1268
  const destPath = join(appBundleMacOSPath, pakFile);
1269
1269
 
1270
1270
  if (existsSync(sourcePath)) {
1271
- cpSync(sourcePath, destPath);
1271
+ cpSync(sourcePath, destPath, { dereference: true });
1272
1272
  } else {
1273
1273
  console.log(`WARNING: Missing CEF file: ${sourcePath}`);
1274
1274
  }
@@ -1298,7 +1298,7 @@ if (commandArg === "init") {
1298
1298
  if (existsSync(helperSourcePath)) {
1299
1299
  cefHelperNames.forEach((helperName) => {
1300
1300
  const destinationPath = join(appBundleMacOSPath, `${helperName}.exe`);
1301
- cpSync(helperSourcePath, destinationPath);
1301
+ cpSync(helperSourcePath, destinationPath, { dereference: true });
1302
1302
 
1303
1303
  });
1304
1304
  } else {
@@ -1331,7 +1331,7 @@ if (commandArg === "init") {
1331
1331
  const icuDataSource = join(cefSourcePath, 'icudtl.dat');
1332
1332
  const icuDataDest = join(appBundleMacOSPath, 'icudtl.dat');
1333
1333
  if (existsSync(icuDataSource)) {
1334
- cpSync(icuDataSource, icuDataDest);
1334
+ cpSync(icuDataSource, icuDataDest, { dereference: true });
1335
1335
  }
1336
1336
 
1337
1337
  // Copy .pak files and other CEF resources to the main executable directory
@@ -1350,7 +1350,7 @@ if (commandArg === "init") {
1350
1350
  const sourcePath = join(cefSourcePath, pakFile);
1351
1351
  const destPath = join(appBundleMacOSPath, pakFile);
1352
1352
  if (existsSync(sourcePath)) {
1353
- cpSync(sourcePath, destPath, { recursive: true });
1353
+ cpSync(sourcePath, destPath, { recursive: true, dereference: true });
1354
1354
  }
1355
1355
  });
1356
1356
 
@@ -1365,7 +1365,7 @@ if (commandArg === "init") {
1365
1365
  const sourcePath = join(cefSourcePath, soFile);
1366
1366
  const destPath = join(cefResourcesDestination, soFile);
1367
1367
  if (existsSync(sourcePath)) {
1368
- cpSync(sourcePath, destPath);
1368
+ cpSync(sourcePath, destPath, { dereference: true });
1369
1369
  console.log(`Copied CEF library to cef subdirectory: ${soFile}`);
1370
1370
  } else {
1371
1371
  console.log(`WARNING: Missing CEF library: ${sourcePath}`);
@@ -1378,7 +1378,7 @@ if (commandArg === "init") {
1378
1378
  const sourcePath = join(cefSourcePath, cefFile);
1379
1379
  const destPath = join(cefResourcesDestination, cefFile);
1380
1380
  if (existsSync(sourcePath)) {
1381
- cpSync(sourcePath, destPath);
1381
+ cpSync(sourcePath, destPath, { dereference: true });
1382
1382
  console.log(`Copied CEF essential file to cef subdirectory: ${cefFile}`);
1383
1383
  } else {
1384
1384
  console.log(`WARNING: Missing CEF essential file: ${sourcePath}`);
@@ -1403,7 +1403,7 @@ if (commandArg === "init") {
1403
1403
  } catch (error) {
1404
1404
  console.log(`WARNING: Failed to create symlink for ${soFile}: ${error}`);
1405
1405
  // Fallback to copying the file
1406
- cpSync(cefFilePath, mainDirPath);
1406
+ cpSync(cefFilePath, mainDirPath, { dereference: true });
1407
1407
  console.log(`Fallback: Copied CEF library to main directory: ${soFile}`);
1408
1408
  }
1409
1409
  }
@@ -1422,7 +1422,7 @@ if (commandArg === "init") {
1422
1422
  if (existsSync(helperSourcePath)) {
1423
1423
  cefHelperNames.forEach((helperName) => {
1424
1424
  const destinationPath = join(appBundleMacOSPath, helperName);
1425
- cpSync(helperSourcePath, destinationPath);
1425
+ cpSync(helperSourcePath, destinationPath, { dereference: true });
1426
1426
  // console.log(`Copied CEF helper: ${helperName}`);
1427
1427
  });
1428
1428
  } else {
@@ -1706,7 +1706,7 @@ if (commandArg === "init") {
1706
1706
  );
1707
1707
 
1708
1708
  // copy the zstd tarball to the self-extracting app bundle
1709
- cpSync(compressedTarPath, compressedTarballInExtractingBundlePath);
1709
+ cpSync(compressedTarPath, compressedTarballInExtractingBundlePath, { dereference: true });
1710
1710
 
1711
1711
  const selfExtractorBinSourcePath = targetPaths.EXTRACTOR;
1712
1712
  const selfExtractorBinDestinationPath = join(
@@ -1973,7 +1973,7 @@ exec "\$LAUNCHER_BINARY" "\$@"
1973
1973
 
1974
1974
  artifactsToUpload.forEach((filePath) => {
1975
1975
  const filename = basename(filePath);
1976
- cpSync(filePath, join(artifactFolder, filename));
1976
+ cpSync(filePath, join(artifactFolder, filename), { dereference: true });
1977
1977
  });
1978
1978
 
1979
1979
  // todo: now just upload the artifacts to your bucket replacing the ones that exist
@@ -2409,7 +2409,7 @@ async function createAppImage(buildFolder: string, appBundlePath: string, appFil
2409
2409
 
2410
2410
  // Copy app bundle contents to AppDir
2411
2411
  const appDirAppPath = join(appDirPath, "app");
2412
- cpSync(appBundlePath, appDirAppPath, { recursive: true });
2412
+ cpSync(appBundlePath, appDirAppPath, { recursive: true, dereference: true });
2413
2413
 
2414
2414
  // Create AppRun script (main executable for AppImage)
2415
2415
  const appRunContent = `#!/bin/bash
@@ -2443,7 +2443,7 @@ Categories=Application;
2443
2443
  const iconPath = config.build.linux?.appImageIcon;
2444
2444
  if (iconPath && existsSync(iconPath)) {
2445
2445
  const iconDestPath = join(appDirPath, `${appFileName}.png`);
2446
- cpSync(iconPath, iconDestPath);
2446
+ cpSync(iconPath, iconDestPath, { dereference: true });
2447
2447
  }
2448
2448
 
2449
2449
  // Try to create AppImage using available tools