bunite-core 0.0.1 → 0.0.4
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/package.json +3 -2
- package/src/bun/core/App.ts +155 -15
- package/src/bun/core/BrowserView.ts +124 -44
- package/src/bun/core/BrowserWindow.ts +94 -47
- package/src/bun/core/Socket.ts +2 -1
- package/src/bun/core/SurfaceBrowserIPC.ts +65 -0
- package/src/bun/core/SurfaceManager.ts +201 -0
- package/src/bun/core/SurfaceRegistry.ts +60 -0
- package/src/bun/core/Utils.ts +275 -46
- package/src/bun/events/appEvents.ts +2 -1
- package/src/bun/events/webviewEvents.ts +1 -3
- package/src/bun/events/windowEvents.ts +2 -0
- package/src/bun/index.ts +4 -3
- package/src/bun/preload/inline.ts +19 -25
- package/src/bun/proc/native.ts +158 -122
- package/src/native/shared/callbacks.h +6 -6
- package/src/native/shared/ffi_exports.h +123 -119
- package/src/native/shared/log.h +24 -0
- package/src/native/shared/webview_storage.h +5 -5
- package/src/native/win/native_host_appres.cpp +258 -0
- package/src/native/win/native_host_cef.cpp +834 -0
- package/src/native/win/native_host_ffi.cpp +935 -0
- package/src/native/win/native_host_internal.h +285 -0
- package/src/native/win/native_host_runtime.cpp +286 -0
- package/src/native/win/native_host_utils.cpp +314 -0
- package/src/native/win/process_helper_win.cpp +126 -26
- package/src/preload/runtime.built.js +1 -1
- package/src/preload/runtime.ts +65 -42
- package/src/preload/tsconfig.json +2 -1
- package/src/preload/tsconfig.tsbuildinfo +1 -0
- package/src/preload/webviewElement.ts +307 -0
- package/src/shared/cefVersion.ts +2 -0
- package/src/shared/log.ts +40 -0
- package/src/shared/paths.ts +122 -52
- package/src/shared/rpc.ts +7 -1
- package/src/shared/webviewPolyfill.ts +80 -0
- package/src/view/index.ts +8 -5
- package/src/native/shared/cef_response_filter.h +0 -116
- package/src/native/win/native_host.cpp +0 -2453
- package/src/types/config.ts +0 -29
package/package.json
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "bunite-core",
|
|
3
3
|
"description": "Uniting UI and Bun",
|
|
4
|
-
"version": "0.0.
|
|
4
|
+
"version": "0.0.4",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"scripts": {
|
|
7
|
+
"setup:cef": "bun ../tools/bunite-dev/scripts/setup-cef.ts",
|
|
7
8
|
"build:native:win": "cmake -S . -B build/win -DBUNITE_TARGET_ARCH=x64 && cmake --build build/win --config Release",
|
|
8
9
|
"build:preload": "bun build src/preload/runtime.ts --outfile src/preload/runtime.built.js --target browser --minify"
|
|
9
10
|
},
|
|
@@ -11,7 +12,7 @@
|
|
|
11
12
|
".": "./src/bun/index.ts",
|
|
12
13
|
"./bun": "./src/bun/index.ts",
|
|
13
14
|
"./view": "./src/view/index.ts",
|
|
14
|
-
"./
|
|
15
|
+
"./webview-polyfill": "./src/shared/webviewPolyfill.ts",
|
|
15
16
|
"./shared/rpc": "./src/shared/rpc.ts",
|
|
16
17
|
"./package.json": "./package.json"
|
|
17
18
|
},
|
package/src/bun/core/App.ts
CHANGED
|
@@ -1,18 +1,32 @@
|
|
|
1
|
-
import { join } from "node:path";
|
|
1
|
+
import { isAbsolute, join, resolve } from "node:path";
|
|
2
|
+
import { existsSync } from "node:fs";
|
|
3
|
+
import { getBaseDir } from "../../shared/paths";
|
|
4
|
+
import { dlopen, FFIType } from "bun:ffi";
|
|
2
5
|
import { BuniteEvent } from "../events/event";
|
|
3
6
|
import { buniteEventEmitter } from "../events/eventEmitter";
|
|
7
|
+
import { handleMessageBoxResponse } from "./Utils";
|
|
4
8
|
import {
|
|
5
9
|
getNativeLibrary,
|
|
6
10
|
initNativeRuntime,
|
|
7
11
|
getNativeRuntimeState,
|
|
8
12
|
setRouteRequestHandler,
|
|
13
|
+
setNativeLogLevel,
|
|
9
14
|
toCString,
|
|
10
15
|
type NativeBootstrapOptions
|
|
11
16
|
} from "../proc/native";
|
|
12
17
|
import { attachGlobalIPCResolver, ensureRPCServer } from "./Socket";
|
|
18
|
+
import { BrowserWindow } from "./BrowserWindow";
|
|
19
|
+
import { getSurfaceIPCHandlers } from "./SurfaceManager";
|
|
20
|
+
import { getWebviewIPCHandlers } from "./SurfaceBrowserIPC";
|
|
21
|
+
import { log, logLevelToInt } from "../../shared/log";
|
|
22
|
+
|
|
23
|
+
import type { LogLevel } from "../../shared/log";
|
|
13
24
|
|
|
14
25
|
type AppInitOptions = NativeBootstrapOptions & {
|
|
15
26
|
userDataDir?: string;
|
|
27
|
+
cefDir?: string;
|
|
28
|
+
exitOnLastWindowClosed?: boolean;
|
|
29
|
+
logLevel?: LogLevel;
|
|
16
30
|
};
|
|
17
31
|
|
|
18
32
|
export type GlobalIPCHandler = (params: unknown, ctx: { viewId: number }) => unknown | Promise<unknown>;
|
|
@@ -21,14 +35,50 @@ class AppRuntime {
|
|
|
21
35
|
private initPromise: Promise<void> | null = null;
|
|
22
36
|
private stubKeepAliveTimer: ReturnType<typeof setInterval> | null = null;
|
|
23
37
|
private readonly globalIPCHandlers = new Map<string, GlobalIPCHandler>();
|
|
38
|
+
private exitOnLastWindowClosed = true;
|
|
39
|
+
private quitting = false;
|
|
24
40
|
|
|
25
41
|
async init(options: AppInitOptions = {}) {
|
|
26
42
|
if (!this.initPromise) {
|
|
27
43
|
this.initPromise = (async () => {
|
|
44
|
+
if (options.exitOnLastWindowClosed !== undefined) {
|
|
45
|
+
this.exitOnLastWindowClosed = options.exitOnLastWindowClosed;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
if (options.logLevel) {
|
|
49
|
+
log.setLevel(options.logLevel);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
if (options.cefDir) {
|
|
53
|
+
process.env.BUNITE_CEF_DIR = options.cefDir;
|
|
54
|
+
}
|
|
55
|
+
|
|
28
56
|
if (options.userDataDir) {
|
|
29
57
|
process.env.BUNITE_USER_DATA_DIR = options.userDataDir;
|
|
30
58
|
} else if (!process.env.BUNITE_USER_DATA_DIR) {
|
|
31
|
-
|
|
59
|
+
// XDG_DATA_HOME takes priority on any platform, then OS convention
|
|
60
|
+
const appDataDir = process.env.XDG_DATA_HOME
|
|
61
|
+
?? (process.platform === "win32"
|
|
62
|
+
? (process.env.APPDATA ?? join(process.env.USERPROFILE ?? "", "AppData", "Roaming"))
|
|
63
|
+
: process.platform === "darwin"
|
|
64
|
+
? join(process.env.HOME ?? "", "Library", "Application Support")
|
|
65
|
+
: join(process.env.HOME ?? "", ".local", "share"));
|
|
66
|
+
let name = "bunite-app";
|
|
67
|
+
try {
|
|
68
|
+
// Walk up from entry script to find nearest package.json
|
|
69
|
+
let dir = getBaseDir();
|
|
70
|
+
while (dir) {
|
|
71
|
+
const pkgPath = join(dir, "package.json");
|
|
72
|
+
if (existsSync(pkgPath)) {
|
|
73
|
+
name = JSON.parse(require("node:fs").readFileSync(pkgPath, "utf8")).name ?? name;
|
|
74
|
+
break;
|
|
75
|
+
}
|
|
76
|
+
const parent = resolve(dir, "..");
|
|
77
|
+
if (parent === dir) break;
|
|
78
|
+
dir = parent;
|
|
79
|
+
}
|
|
80
|
+
} catch {}
|
|
81
|
+
process.env.BUNITE_USER_DATA_DIR = join(appDataDir, name);
|
|
32
82
|
}
|
|
33
83
|
|
|
34
84
|
const runtime = await initNativeRuntime({
|
|
@@ -38,12 +88,48 @@ class AppRuntime {
|
|
|
38
88
|
chromiumFlags: options.chromiumFlags
|
|
39
89
|
});
|
|
40
90
|
|
|
91
|
+
if (options.logLevel && runtime.nativeLoaded) {
|
|
92
|
+
setNativeLogLevel(logLevelToInt(options.logLevel));
|
|
93
|
+
}
|
|
94
|
+
|
|
41
95
|
attachGlobalIPCResolver((channel) => this.getGlobalIPCHandler(channel));
|
|
96
|
+
|
|
97
|
+
for (const [channel, handler] of getSurfaceIPCHandlers()) {
|
|
98
|
+
this.globalIPCHandlers.set(channel, handler);
|
|
99
|
+
}
|
|
100
|
+
for (const [channel, handler] of getWebviewIPCHandlers()) {
|
|
101
|
+
this.globalIPCHandlers.set(channel, handler);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
this.globalIPCHandlers.set("__bunite:messageBoxResponse", (params) => {
|
|
105
|
+
const { requestId, response } = params as { requestId: number; response: number };
|
|
106
|
+
handleMessageBoxResponse(requestId, response);
|
|
107
|
+
return {};
|
|
108
|
+
});
|
|
109
|
+
|
|
42
110
|
setRouteRequestHandler((requestId, path) => this.handleRouteRequest(requestId, path));
|
|
43
111
|
|
|
44
|
-
// Replay
|
|
45
|
-
for (const path of this.
|
|
46
|
-
getNativeLibrary()?.symbols.
|
|
112
|
+
// Replay appres routes registered before init
|
|
113
|
+
for (const path of this.appresHandlers.keys()) {
|
|
114
|
+
getNativeLibrary()?.symbols.bunite_register_appres_route(toCString(path));
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
if (this.exitOnLastWindowClosed && runtime.nativeLoaded) {
|
|
119
|
+
buniteEventEmitter.on("all-windows-closed", () => {
|
|
120
|
+
if (this.quitting) {
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
queueMicrotask(() => {
|
|
124
|
+
if (this.quitting) {
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
// Recheck: a new window may have been created since the event
|
|
128
|
+
if (BrowserWindow.getAll().length === 0) {
|
|
129
|
+
this.quit();
|
|
130
|
+
}
|
|
131
|
+
});
|
|
132
|
+
});
|
|
47
133
|
}
|
|
48
134
|
|
|
49
135
|
ensureRPCServer();
|
|
@@ -60,6 +146,11 @@ class AppRuntime {
|
|
|
60
146
|
}
|
|
61
147
|
|
|
62
148
|
on(name: string, handler: (payload: unknown) => void) {
|
|
149
|
+
if (name === "before-quit") {
|
|
150
|
+
// before-quit listeners receive the BuniteEvent directly so they can set event.response
|
|
151
|
+
buniteEventEmitter.on(name, handler);
|
|
152
|
+
return () => buniteEventEmitter.off(name, handler);
|
|
153
|
+
}
|
|
63
154
|
const wrapped = (event: { data: unknown }) => handler(event.data);
|
|
64
155
|
buniteEventEmitter.on(name, wrapped);
|
|
65
156
|
return () => buniteEventEmitter.off(name, wrapped);
|
|
@@ -76,18 +167,31 @@ class AppRuntime {
|
|
|
76
167
|
}
|
|
77
168
|
|
|
78
169
|
if (!this.stubKeepAliveTimer) {
|
|
79
|
-
|
|
170
|
+
log.warn("Running without a native event loop. Keeping the process alive in stub mode.");
|
|
80
171
|
this.stubKeepAliveTimer = setInterval(() => {}, 60_000);
|
|
81
172
|
}
|
|
82
173
|
}
|
|
83
174
|
|
|
84
175
|
quit(code = 0) {
|
|
176
|
+
if (this.quitting) {
|
|
177
|
+
return;
|
|
178
|
+
}
|
|
179
|
+
this.quitting = true;
|
|
180
|
+
|
|
181
|
+
const event = buniteEventEmitter.events.app.beforeQuit({});
|
|
182
|
+
buniteEventEmitter.emitEvent(event);
|
|
183
|
+
if (event.responseWasSet && event.response?.allow === false) {
|
|
184
|
+
this.quitting = false;
|
|
185
|
+
return;
|
|
186
|
+
}
|
|
85
187
|
if (this.stubKeepAliveTimer) {
|
|
86
188
|
clearInterval(this.stubKeepAliveTimer);
|
|
87
189
|
this.stubKeepAliveTimer = null;
|
|
88
190
|
}
|
|
191
|
+
// bunite_quit() blocks until native shutdown completes or times out
|
|
89
192
|
getNativeLibrary()?.symbols.bunite_quit();
|
|
90
|
-
|
|
193
|
+
process.exitCode = code;
|
|
194
|
+
process.exit(code);
|
|
91
195
|
}
|
|
92
196
|
|
|
93
197
|
handle(channel: string, handler: GlobalIPCHandler) {
|
|
@@ -110,23 +214,23 @@ class AppRuntime {
|
|
|
110
214
|
return this.globalIPCHandlers.get(channel);
|
|
111
215
|
}
|
|
112
216
|
|
|
113
|
-
private readonly
|
|
217
|
+
private readonly appresHandlers = new Map<string, () => string>();
|
|
114
218
|
|
|
115
|
-
|
|
116
|
-
this.
|
|
117
|
-
getNativeLibrary()?.symbols.
|
|
219
|
+
getAppRes(path: string, handler: () => string) {
|
|
220
|
+
this.appresHandlers.set(path, handler);
|
|
221
|
+
getNativeLibrary()?.symbols.bunite_register_appres_route(toCString(path));
|
|
118
222
|
}
|
|
119
223
|
|
|
120
|
-
|
|
121
|
-
this.
|
|
122
|
-
getNativeLibrary()?.symbols.
|
|
224
|
+
removeAppRes(path: string) {
|
|
225
|
+
this.appresHandlers.delete(path);
|
|
226
|
+
getNativeLibrary()?.symbols.bunite_unregister_appres_route(toCString(path));
|
|
123
227
|
}
|
|
124
228
|
|
|
125
229
|
/** @internal */
|
|
126
230
|
handleRouteRequest(requestId: number, path: string) {
|
|
127
231
|
let html: string;
|
|
128
232
|
try {
|
|
129
|
-
const handler = this.
|
|
233
|
+
const handler = this.appresHandlers.get(path);
|
|
130
234
|
html = handler ? handler() : "<html><body>No handler for: " + path + "</body></html>";
|
|
131
235
|
} catch (error) {
|
|
132
236
|
html = "<html><body>Route handler error: " + (error instanceof Error ? error.message : String(error)) + "</body></html>";
|
|
@@ -134,9 +238,45 @@ class AppRuntime {
|
|
|
134
238
|
getNativeLibrary()?.symbols.bunite_complete_route_request(requestId, toCString(html));
|
|
135
239
|
}
|
|
136
240
|
|
|
241
|
+
/** Resolve a path relative to the entry script (dev) or executable (compiled). */
|
|
242
|
+
resolve(relativePath: string): string {
|
|
243
|
+
if (isAbsolute(relativePath)) return relativePath;
|
|
244
|
+
return resolve(getBaseDir(), relativePath);
|
|
245
|
+
}
|
|
246
|
+
|
|
137
247
|
get runtime() {
|
|
138
248
|
return getNativeRuntimeState();
|
|
139
249
|
}
|
|
250
|
+
|
|
251
|
+
get version(): string {
|
|
252
|
+
try {
|
|
253
|
+
const { createRequire } = require("node:module");
|
|
254
|
+
const req = createRequire(import.meta.url);
|
|
255
|
+
const pkg = req("bunite-core/package.json");
|
|
256
|
+
return pkg.version ?? "unknown";
|
|
257
|
+
} catch {
|
|
258
|
+
return "unknown";
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
private cachedCefVersion: string | null | undefined;
|
|
263
|
+
|
|
264
|
+
get cefVersion(): string | null {
|
|
265
|
+
if (this.cachedCefVersion !== undefined) return this.cachedCefVersion;
|
|
266
|
+
this.cachedCefVersion = null;
|
|
267
|
+
const arts = getNativeRuntimeState()?.artifacts;
|
|
268
|
+
if (!arts?.cefDir) return null;
|
|
269
|
+
const libcefPath = join(arts.cefDir, "libcef.dll");
|
|
270
|
+
if (!existsSync(libcefPath)) return null;
|
|
271
|
+
try {
|
|
272
|
+
const lib = dlopen(libcefPath, {
|
|
273
|
+
cef_version_info: { returns: FFIType.i32, args: [FFIType.i32] },
|
|
274
|
+
});
|
|
275
|
+
const v = (entry: number) => lib.symbols.cef_version_info(entry);
|
|
276
|
+
this.cachedCefVersion = `${v(0)}.${v(1)}.${v(2)}+chromium-${v(4)}.${v(5)}.${v(6)}.${v(7)}`;
|
|
277
|
+
} catch { /* leave as null */ }
|
|
278
|
+
return this.cachedCefVersion;
|
|
279
|
+
}
|
|
140
280
|
}
|
|
141
281
|
|
|
142
282
|
export const app = new AppRuntime();
|
|
@@ -1,11 +1,14 @@
|
|
|
1
|
+
import { ptr } from "bun:ffi";
|
|
1
2
|
import { buildViewPreloadScript } from "../preload/inline";
|
|
2
|
-
import
|
|
3
|
+
import { log } from "../../shared/log";
|
|
3
4
|
import { buniteEventEmitter } from "../events/eventEmitter";
|
|
4
5
|
import { defineBuniteRPC, type BuniteRPCConfig, type BuniteRPCSchema, type RPCWithTransport } from "../../shared/rpc";
|
|
5
|
-
import { ensureNativeRuntime, getNativeLibrary, toCString } from "../proc/native";
|
|
6
|
+
import { ensureNativeRuntime, getNativeLibrary, toCString, waitForViewReady, cancelWaitForViewReady } from "../proc/native";
|
|
6
7
|
import { attachBrowserViewRegistry, getRPCPort, sendMessageToView } from "./Socket";
|
|
7
8
|
import { randomBytes } from "node:crypto";
|
|
8
|
-
import {
|
|
9
|
+
import { resolveDefaultAppResRoot } from "../../shared/paths";
|
|
10
|
+
import { removeSurfacesForHostView } from "./SurfaceRegistry";
|
|
11
|
+
import { cancelPendingMessageBoxesForView } from "./Utils";
|
|
9
12
|
|
|
10
13
|
const BrowserViewMap: Record<number, BrowserView<any>> = {};
|
|
11
14
|
let nextWebviewId = 1;
|
|
@@ -14,9 +17,9 @@ export type BrowserViewOptions<T = undefined> = {
|
|
|
14
17
|
url: string | null;
|
|
15
18
|
html: string | null;
|
|
16
19
|
preload: string | null;
|
|
17
|
-
|
|
20
|
+
appresRoot: string | null;
|
|
21
|
+
preloadOrigins?: string[];
|
|
18
22
|
partition: string | null;
|
|
19
|
-
windowPtr?: Pointer | null;
|
|
20
23
|
frame: {
|
|
21
24
|
x: number;
|
|
22
25
|
y: number;
|
|
@@ -34,9 +37,9 @@ const defaultOptions: BrowserViewOptions = {
|
|
|
34
37
|
url: null,
|
|
35
38
|
html: null,
|
|
36
39
|
preload: null,
|
|
37
|
-
|
|
40
|
+
appresRoot: null,
|
|
41
|
+
preloadOrigins: undefined,
|
|
38
42
|
partition: null,
|
|
39
|
-
windowPtr: null,
|
|
40
43
|
frame: {
|
|
41
44
|
x: 0,
|
|
42
45
|
y: 0,
|
|
@@ -51,12 +54,14 @@ const defaultOptions: BrowserViewOptions = {
|
|
|
51
54
|
|
|
52
55
|
export class BrowserView<T extends RPCWithTransport = RPCWithTransport> {
|
|
53
56
|
id = nextWebviewId++;
|
|
54
|
-
|
|
57
|
+
private nativeAttached = false;
|
|
58
|
+
private _readyPromise: Promise<void>;
|
|
55
59
|
windowId: number;
|
|
56
60
|
url: string | null;
|
|
57
61
|
html: string | null;
|
|
58
62
|
preload: string | null;
|
|
59
|
-
|
|
63
|
+
appresRoot: string | null;
|
|
64
|
+
preloadOrigins?: string[];
|
|
60
65
|
partition: string | null;
|
|
61
66
|
frame: BrowserViewOptions["frame"];
|
|
62
67
|
rpc?: T;
|
|
@@ -73,7 +78,8 @@ export class BrowserView<T extends RPCWithTransport = RPCWithTransport> {
|
|
|
73
78
|
this.url = options.url ?? defaultOptions.url;
|
|
74
79
|
this.html = options.html ?? defaultOptions.html;
|
|
75
80
|
this.preload = options.preload ?? defaultOptions.preload;
|
|
76
|
-
this.
|
|
81
|
+
this.appresRoot = options.appresRoot ?? defaultOptions.appresRoot ?? resolveDefaultAppResRoot();
|
|
82
|
+
this.preloadOrigins = options.preloadOrigins ?? defaultOptions.preloadOrigins;
|
|
77
83
|
this.partition = options.partition ?? defaultOptions.partition;
|
|
78
84
|
this.frame = options.frame ?? defaultOptions.frame;
|
|
79
85
|
this.rpc = options.rpc;
|
|
@@ -86,12 +92,12 @@ export class BrowserView<T extends RPCWithTransport = RPCWithTransport> {
|
|
|
86
92
|
throw new Error("sandboxed BrowserView is not implemented in Bunite Windows Phase 1 yet.");
|
|
87
93
|
}
|
|
88
94
|
if (this.partition) {
|
|
89
|
-
|
|
95
|
+
log.warn("BrowserView.partition is not implemented in Bunite Windows Phase 1 yet.");
|
|
90
96
|
}
|
|
91
97
|
|
|
92
98
|
const preloadScript = buildViewPreloadScript({
|
|
93
99
|
preload: this.preload,
|
|
94
|
-
|
|
100
|
+
appresRoot: this.appresRoot,
|
|
95
101
|
webviewId: this.id,
|
|
96
102
|
rpcSocketPort: getRPCPort(),
|
|
97
103
|
secretKey: this.secretKey
|
|
@@ -99,22 +105,51 @@ export class BrowserView<T extends RPCWithTransport = RPCWithTransport> {
|
|
|
99
105
|
|
|
100
106
|
BrowserViewMap[this.id] = this;
|
|
101
107
|
this.rpc?.setTransport(this.createTransport());
|
|
102
|
-
|
|
108
|
+
// Register ready waiter BEFORE native create — OnAfterCreated can fire
|
|
109
|
+
// on the CEF UI thread before bunite_view_create returns to JS.
|
|
110
|
+
this._readyPromise = waitForViewReady(this.id);
|
|
111
|
+
this.nativeAttached =
|
|
103
112
|
getNativeLibrary()?.symbols.bunite_view_create(
|
|
104
113
|
this.id,
|
|
105
|
-
|
|
114
|
+
this.windowId,
|
|
106
115
|
toCString(this.url ?? ""),
|
|
107
116
|
toCString(this.html ?? ""),
|
|
108
117
|
toCString(preloadScript),
|
|
109
|
-
toCString(this.
|
|
118
|
+
toCString(this.appresRoot ?? ""),
|
|
110
119
|
toCString(this.navigationRules ? JSON.stringify(this.navigationRules) : ""),
|
|
111
120
|
this.frame.x,
|
|
112
121
|
this.frame.y,
|
|
113
122
|
this.frame.width,
|
|
114
123
|
this.frame.height,
|
|
115
124
|
this.autoResize,
|
|
116
|
-
this.sandbox
|
|
117
|
-
|
|
125
|
+
this.sandbox,
|
|
126
|
+
toCString(this.preloadOrigins ? JSON.stringify(this.preloadOrigins) : "")
|
|
127
|
+
) ?? false;
|
|
128
|
+
|
|
129
|
+
if (this.nativeAttached) {
|
|
130
|
+
// Clean up owned surfaces when this view navigates (page refresh/navigation
|
|
131
|
+
// destroys the JS context without firing disconnectedCallback).
|
|
132
|
+
// Uses did-navigate (not will-navigate) because will-navigate fires even
|
|
133
|
+
// when navigation is denied by navigationRules.
|
|
134
|
+
this.on("did-navigate", (event: any) => {
|
|
135
|
+
this.url = event.data?.detail ?? this.url;
|
|
136
|
+
cancelPendingMessageBoxesForView(this.id);
|
|
137
|
+
removeSurfacesForHostView(this.id);
|
|
138
|
+
});
|
|
139
|
+
} else {
|
|
140
|
+
cancelWaitForViewReady(this.id);
|
|
141
|
+
this._readyPromise = Promise.reject(new Error("Native view creation failed"));
|
|
142
|
+
this._readyPromise.catch(() => {}); // prevent unhandled rejection
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
whenReady(timeoutMs = 8000): Promise<void> {
|
|
147
|
+
return Promise.race([
|
|
148
|
+
this._readyPromise,
|
|
149
|
+
new Promise<never>((_, reject) =>
|
|
150
|
+
setTimeout(() => reject(new Error(`Browser creation timed out for view ${this.id}`)), timeoutMs)
|
|
151
|
+
)
|
|
152
|
+
]);
|
|
118
153
|
}
|
|
119
154
|
|
|
120
155
|
static getById(id: number) {
|
|
@@ -155,84 +190,130 @@ export class BrowserView<T extends RPCWithTransport = RPCWithTransport> {
|
|
|
155
190
|
|
|
156
191
|
setAnchor(mode: "none" | "fill" | "top" | "below-top", inset = 0) {
|
|
157
192
|
const modeInt = { none: 0, fill: 1, top: 2, "below-top": 3 }[mode];
|
|
158
|
-
if (this.
|
|
159
|
-
getNativeLibrary()?.symbols.bunite_view_set_anchor(this.
|
|
193
|
+
if (this.nativeAttached) {
|
|
194
|
+
getNativeLibrary()?.symbols.bunite_view_set_anchor(this.id, modeInt, inset);
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
executeJavaScript(script: string) {
|
|
199
|
+
if (this.nativeAttached) {
|
|
200
|
+
getNativeLibrary()?.symbols.bunite_view_execute_javascript(this.id, toCString(script));
|
|
160
201
|
}
|
|
161
202
|
}
|
|
162
203
|
|
|
163
204
|
goBack() {
|
|
164
|
-
if (this.
|
|
165
|
-
getNativeLibrary()?.symbols.bunite_view_go_back(this.
|
|
205
|
+
if (this.nativeAttached) {
|
|
206
|
+
getNativeLibrary()?.symbols.bunite_view_go_back(this.id);
|
|
166
207
|
}
|
|
167
208
|
}
|
|
168
209
|
|
|
169
210
|
reload() {
|
|
170
|
-
if (this.
|
|
171
|
-
getNativeLibrary()?.symbols.bunite_view_reload(this.
|
|
211
|
+
if (this.nativeAttached) {
|
|
212
|
+
getNativeLibrary()?.symbols.bunite_view_reload(this.id);
|
|
172
213
|
}
|
|
173
214
|
}
|
|
174
215
|
|
|
175
216
|
setVisible(visible: boolean) {
|
|
176
|
-
if (this.
|
|
177
|
-
getNativeLibrary()?.symbols.bunite_view_set_visible(this.
|
|
217
|
+
if (this.nativeAttached) {
|
|
218
|
+
getNativeLibrary()?.symbols.bunite_view_set_visible(this.id, visible);
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
setInputPassthrough(passthrough: boolean) {
|
|
223
|
+
if (this.nativeAttached) {
|
|
224
|
+
getNativeLibrary()?.symbols.bunite_view_set_input_passthrough(this.id, passthrough);
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
setMaskRegion(rects: Array<{ x: number; y: number; w: number; h: number }>) {
|
|
229
|
+
if (!this.nativeAttached) return;
|
|
230
|
+
if (rects.length === 0) {
|
|
231
|
+
getNativeLibrary()?.symbols.bunite_view_set_mask_region(this.id, null as any, 0);
|
|
232
|
+
return;
|
|
233
|
+
}
|
|
234
|
+
const buf = new Float64Array(rects.length * 4);
|
|
235
|
+
for (let i = 0; i < rects.length; i++) {
|
|
236
|
+
buf[i * 4] = rects[i].x;
|
|
237
|
+
buf[i * 4 + 1] = rects[i].y;
|
|
238
|
+
buf[i * 4 + 2] = rects[i].w;
|
|
239
|
+
buf[i * 4 + 3] = rects[i].h;
|
|
240
|
+
}
|
|
241
|
+
getNativeLibrary()?.symbols.bunite_view_set_mask_region(
|
|
242
|
+
this.id, ptr(buf.buffer), rects.length
|
|
243
|
+
);
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
bringToFront() {
|
|
247
|
+
if (this.nativeAttached) {
|
|
248
|
+
getNativeLibrary()?.symbols.bunite_view_bring_to_front(this.id);
|
|
178
249
|
}
|
|
179
250
|
}
|
|
180
251
|
|
|
181
252
|
setBounds(x: number, y: number, width: number, height: number) {
|
|
182
253
|
this.frame = { x, y, width, height };
|
|
183
|
-
if (this.
|
|
184
|
-
getNativeLibrary()?.symbols.bunite_view_set_bounds(this.
|
|
254
|
+
if (this.nativeAttached) {
|
|
255
|
+
getNativeLibrary()?.symbols.bunite_view_set_bounds(this.id, x, y, width, height);
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
/** Fire-and-forget setBounds — does not block on the UI thread. */
|
|
260
|
+
setBoundsAsync(x: number, y: number, width: number, height: number) {
|
|
261
|
+
this.frame = { x, y, width, height };
|
|
262
|
+
if (this.nativeAttached) {
|
|
263
|
+
getNativeLibrary()?.symbols.bunite_view_set_bounds_async(this.id, x, y, width, height);
|
|
185
264
|
}
|
|
186
265
|
}
|
|
187
266
|
|
|
188
267
|
loadURL(url: string) {
|
|
189
268
|
this.url = url;
|
|
190
|
-
if (this.
|
|
191
|
-
getNativeLibrary()?.symbols.bunite_view_load_url(this.
|
|
269
|
+
if (this.nativeAttached) {
|
|
270
|
+
getNativeLibrary()?.symbols.bunite_view_load_url(this.id, toCString(url));
|
|
192
271
|
}
|
|
193
272
|
}
|
|
194
273
|
|
|
195
274
|
loadHTML(html: string) {
|
|
196
275
|
this.html = html;
|
|
197
|
-
if (this.
|
|
198
|
-
getNativeLibrary()?.symbols.bunite_view_load_html(this.
|
|
276
|
+
if (this.nativeAttached) {
|
|
277
|
+
getNativeLibrary()?.symbols.bunite_view_load_html(this.id, toCString(html));
|
|
199
278
|
}
|
|
200
279
|
}
|
|
201
280
|
|
|
202
281
|
remove() {
|
|
203
|
-
if (this.
|
|
204
|
-
getNativeLibrary()?.symbols.bunite_view_remove(this.
|
|
282
|
+
if (this.nativeAttached) {
|
|
283
|
+
getNativeLibrary()?.symbols.bunite_view_remove(this.id);
|
|
205
284
|
}
|
|
206
285
|
this.detachFromNative();
|
|
207
286
|
}
|
|
208
287
|
|
|
209
288
|
openDevTools() {
|
|
210
|
-
if (this.
|
|
211
|
-
getNativeLibrary()?.symbols.bunite_view_open_devtools(this.
|
|
289
|
+
if (this.nativeAttached) {
|
|
290
|
+
getNativeLibrary()?.symbols.bunite_view_open_devtools(this.id);
|
|
212
291
|
}
|
|
213
292
|
}
|
|
214
293
|
|
|
215
294
|
closeDevTools() {
|
|
216
|
-
if (this.
|
|
217
|
-
getNativeLibrary()?.symbols.bunite_view_close_devtools(this.
|
|
295
|
+
if (this.nativeAttached) {
|
|
296
|
+
getNativeLibrary()?.symbols.bunite_view_close_devtools(this.id);
|
|
218
297
|
}
|
|
219
298
|
}
|
|
220
299
|
|
|
221
300
|
toggleDevTools() {
|
|
222
|
-
if (this.
|
|
223
|
-
getNativeLibrary()?.symbols.bunite_view_toggle_devtools(this.
|
|
301
|
+
if (this.nativeAttached) {
|
|
302
|
+
getNativeLibrary()?.symbols.bunite_view_toggle_devtools(this.id);
|
|
224
303
|
}
|
|
225
304
|
}
|
|
226
305
|
|
|
227
306
|
detachFromNative() {
|
|
228
|
-
this.
|
|
307
|
+
cancelPendingMessageBoxesForView(this.id);
|
|
308
|
+
removeSurfacesForHostView(this.id);
|
|
309
|
+
cancelWaitForViewReady(this.id);
|
|
310
|
+
this.nativeAttached = false;
|
|
229
311
|
for (const eventName of [
|
|
230
312
|
"will-navigate",
|
|
231
313
|
"did-navigate",
|
|
232
314
|
"dom-ready",
|
|
233
315
|
"new-window-open",
|
|
234
|
-
"permission-requested"
|
|
235
|
-
"message-box-response"
|
|
316
|
+
"permission-requested"
|
|
236
317
|
]) {
|
|
237
318
|
buniteEventEmitter.removeAllListeners(`${eventName}-${this.id}`);
|
|
238
319
|
}
|
|
@@ -245,8 +326,7 @@ export class BrowserView<T extends RPCWithTransport = RPCWithTransport> {
|
|
|
245
326
|
| "did-navigate"
|
|
246
327
|
| "dom-ready"
|
|
247
328
|
| "new-window-open"
|
|
248
|
-
| "permission-requested"
|
|
249
|
-
| "message-box-response",
|
|
329
|
+
| "permission-requested",
|
|
250
330
|
handler: (event: unknown) => void
|
|
251
331
|
) {
|
|
252
332
|
const specificName = `${name}-${this.id}`;
|