electrobun 0.1.7 → 0.1.9
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.
- package/dist/api/bun/core/BrowserView.ts +25 -5
- package/dist/api/bun/events/webviewEvents.ts +9 -1
- package/dist/api/bun/proc/native.ts +67 -15
- package/package.json +1 -1
- package/src/cli/index.ts +17 -17
- package/templates/interactive-playground/package-lock.json +1085 -9
- package/templates/interactive-playground/src/mainview/demos/WebViewDemo.ts +55 -1
- package/test-new-window-events.ts +26 -0
- package/test-new-window.html +75 -0
|
@@ -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
|
-
|
|
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
|
-
|
|
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<{
|
|
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
|
|
@@ -885,7 +893,7 @@ const getHTMLForWebviewSync = new JSCallback((webviewId) => {
|
|
|
885
893
|
|
|
886
894
|
return toCString(webview?.html || '');
|
|
887
895
|
}, {
|
|
888
|
-
args: [FFIType.
|
|
896
|
+
args: [FFIType.u32],
|
|
889
897
|
returns: FFIType.cstring,
|
|
890
898
|
// threadsafe: true
|
|
891
899
|
});
|
|
@@ -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
|
-
|
|
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(
|
|
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
|
-
|
|
951
|
-
|
|
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
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
|