bunite-core 0.12.0 → 0.14.0
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 +4 -4
- package/src/host/core/App.ts +17 -1
- package/src/host/core/BrowserView.ts +197 -28
- package/src/host/core/SurfaceBrowserIPC.ts +44 -3
- package/src/host/core/SurfaceManager.ts +260 -28
- package/src/host/core/SurfaceRegistry.ts +9 -1
- package/src/host/core/inputDispatch.ts +147 -0
- package/src/host/events/webviewEvents.ts +8 -1
- package/src/host/native.ts +124 -1
- package/src/native/linux/bunite_linux_ffi.cpp +223 -6
- package/src/native/linux/bunite_linux_internal.h +6 -0
- package/src/native/linux/bunite_linux_runtime.cpp +1 -1
- package/src/native/linux/bunite_linux_utils.cpp +2 -2
- package/src/native/linux/bunite_linux_view.cpp +85 -0
- package/src/native/mac/bunite_mac_ffi.mm +356 -8
- package/src/native/mac/bunite_mac_internal.h +6 -0
- package/src/native/mac/bunite_mac_utils.mm +2 -2
- package/src/native/mac/bunite_mac_view.mm +144 -2
- package/src/native/shared/ffi_exports.h +135 -0
- package/src/native/win/native_host_cef.cpp +86 -3
- package/src/native/win/native_host_ffi.cpp +378 -1
- package/src/native/win/native_host_internal.h +13 -0
- package/src/native/win/native_host_utils.cpp +2 -1
- package/src/native/win/process_helper_win.cpp +54 -27
- package/src/native/win-webview2/bunite_webview2_ffi.cpp +303 -9
- package/src/native/win-webview2/webview2_internal.h +11 -0
- package/src/native/win-webview2/webview2_runtime.cpp +128 -12
- package/src/native/win-webview2/webview2_utils.cpp +30 -12
- package/src/preload/runtime.built.js +1 -1
- package/src/preload/runtime.ts +97 -0
- package/src/rpc/framework.ts +173 -4
- package/src/rpc/index.ts +21 -0
- package/src/webview/native.ts +126 -25
- package/src/webview/polyfill.ts +196 -12
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "bunite-core",
|
|
3
3
|
"description": "Uniting UI and Bun",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.14.0",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"scripts": {
|
|
7
7
|
"setup:cef": "bun ../tools/bunite-dev/scripts/setup-cef.ts",
|
|
@@ -24,8 +24,8 @@
|
|
|
24
24
|
"msgpackr": "^1.11.9"
|
|
25
25
|
},
|
|
26
26
|
"optionalDependencies": {
|
|
27
|
-
"bunite-native-win-x64": "0.0.
|
|
28
|
-
"bunite-native-mac-arm64": "0.0.
|
|
29
|
-
"bunite-native-linux-x64": "0.0.
|
|
27
|
+
"bunite-native-win-x64": "0.0.12",
|
|
28
|
+
"bunite-native-mac-arm64": "0.0.3",
|
|
29
|
+
"bunite-native-linux-x64": "0.0.3"
|
|
30
30
|
}
|
|
31
31
|
}
|
package/src/host/core/App.ts
CHANGED
|
@@ -18,7 +18,7 @@ import { BrowserWindow } from "./BrowserWindow";
|
|
|
18
18
|
import { createSurfaceCapImpl } from "./SurfaceManager";
|
|
19
19
|
import "./SurfaceBrowserIPC";
|
|
20
20
|
import { log, logLevelToInt } from "../log";
|
|
21
|
-
import { RuntimeCap, SurfaceCap, IpcError, type ImplOf } from "../../rpc/index";
|
|
21
|
+
import { RuntimeCap, SurfaceCap, PageReportingCap, IpcError, type ImplOf } from "../../rpc/index";
|
|
22
22
|
|
|
23
23
|
import type { LogLevel } from "../log";
|
|
24
24
|
|
|
@@ -189,6 +189,22 @@ export class AppRuntime {
|
|
|
189
189
|
themeWatch: () => notImpl("themeWatch"),
|
|
190
190
|
surface: (_: void, ctx: Parameters<ImplOf<typeof RuntimeCap>["surface"]>[1]) =>
|
|
191
191
|
ctx.exportCap(SurfaceCap, createSurfaceCapImpl(viewId)),
|
|
192
|
+
reporting: (_: void, ctx: Parameters<ImplOf<typeof RuntimeCap>["reporting"]>[1]) =>
|
|
193
|
+
ctx.exportCap(PageReportingCap, {
|
|
194
|
+
reportConsoleBatch: ({ entries }) => {
|
|
195
|
+
// One queueMicrotask per batch (not per entry) — a 1000-log
|
|
196
|
+
// spam still translates to 1 microtask + N synchronous emits,
|
|
197
|
+
// not N microtasks competing for the queue.
|
|
198
|
+
queueMicrotask(() => {
|
|
199
|
+
for (const entry of entries) {
|
|
200
|
+
buniteEventEmitter.emitEvent(
|
|
201
|
+
buniteEventEmitter.events.webview.consoleMessage(entry),
|
|
202
|
+
viewId
|
|
203
|
+
);
|
|
204
|
+
}
|
|
205
|
+
});
|
|
206
|
+
},
|
|
207
|
+
}),
|
|
192
208
|
} satisfies ImplOf<typeof RuntimeCap>;
|
|
193
209
|
return impl;
|
|
194
210
|
}
|
|
@@ -8,13 +8,14 @@ import {
|
|
|
8
8
|
type Connection,
|
|
9
9
|
type BytesPipe,
|
|
10
10
|
} from "../../rpc/index";
|
|
11
|
-
import type { EvaluateResult, SurfaceCapabilities } from "../../rpc/framework";
|
|
11
|
+
import type { EvaluateResult, SurfaceCapabilities, ScreenshotResult, Modifier } from "../../rpc/framework";
|
|
12
|
+
import { encodeModifiers, resolveKey } from "./inputDispatch";
|
|
12
13
|
import { createEncryptedPipe } from "../encryptedPipe";
|
|
13
14
|
import {
|
|
14
15
|
ensureNativeRuntime, getNativeLibrary, toCString, waitForViewReady, cancelWaitForViewReady,
|
|
15
|
-
setEvaluateResultHandler, type NativeEvaluateResult
|
|
16
|
+
setEvaluateResultHandler, type NativeEvaluateResult,
|
|
17
|
+
setScreenshotResultHandler, type NativeScreenshotResult,
|
|
16
18
|
} from "../native";
|
|
17
|
-
import { getNativeEngineName } from "../native";
|
|
18
19
|
import { attachBrowserViewRegistry, getRpcPort } from "./Socket";
|
|
19
20
|
import { getAppRuntimeOrThrow } from "./App";
|
|
20
21
|
import { randomBytes } from "node:crypto";
|
|
@@ -25,29 +26,89 @@ const BrowserViewMap: Record<number, BrowserView> = {};
|
|
|
25
26
|
let nextWebviewId = 1;
|
|
26
27
|
|
|
27
28
|
// Evaluate request plumbing — native fires `evaluate-result` events keyed by
|
|
28
|
-
// requestId.
|
|
29
|
-
//
|
|
29
|
+
// requestId. Each pending entry records its viewId so detachFromNative can
|
|
30
|
+
// reject any in-flight Promises for that view (otherwise the resolver leaks
|
|
31
|
+
// when the view is destroyed mid-evaluate).
|
|
32
|
+
type EvaluatePending = { viewId: number; resolve: (result: EvaluateResult) => void };
|
|
30
33
|
let nextEvaluateRequestId = 1;
|
|
31
|
-
const evaluateResolvers = new Map<number,
|
|
34
|
+
const evaluateResolvers = new Map<number, EvaluatePending>();
|
|
32
35
|
|
|
33
|
-
function registerEvaluateRequest(resolve: (result: EvaluateResult) => void): number {
|
|
36
|
+
function registerEvaluateRequest(viewId: number, resolve: (result: EvaluateResult) => void): number {
|
|
34
37
|
const id = nextEvaluateRequestId++;
|
|
35
|
-
evaluateResolvers.set(id, resolve);
|
|
38
|
+
evaluateResolvers.set(id, { viewId, resolve });
|
|
36
39
|
return id;
|
|
37
40
|
}
|
|
38
41
|
|
|
39
|
-
|
|
40
|
-
const
|
|
41
|
-
|
|
42
|
+
function rejectEvaluatesForView(viewId: number) {
|
|
43
|
+
for (const [reqId, entry] of evaluateResolvers) {
|
|
44
|
+
if (entry.viewId === viewId) {
|
|
45
|
+
evaluateResolvers.delete(reqId);
|
|
46
|
+
entry.resolve({ ok: false, code: "not_supported", message: "view destroyed" });
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// Screenshot resolvers — parallel to evaluate. Native fires `screenshot-result`
|
|
52
|
+
// keyed by requestId; payload carries base64 data which TS decodes to Uint8Array.
|
|
53
|
+
type ScreenshotPending = { viewId: number; resolve: (result: ScreenshotResult) => void };
|
|
54
|
+
let nextScreenshotRequestId = 1;
|
|
55
|
+
const screenshotResolvers = new Map<number, ScreenshotPending>();
|
|
56
|
+
|
|
57
|
+
function registerScreenshotRequest(viewId: number, resolve: (result: ScreenshotResult) => void): number {
|
|
58
|
+
const id = nextScreenshotRequestId++;
|
|
59
|
+
screenshotResolvers.set(id, { viewId, resolve });
|
|
60
|
+
return id;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function rejectScreenshotsForView(viewId: number) {
|
|
64
|
+
for (const [reqId, entry] of screenshotResolvers) {
|
|
65
|
+
if (entry.viewId === viewId) {
|
|
66
|
+
screenshotResolvers.delete(reqId);
|
|
67
|
+
entry.resolve({ ok: false, code: "not_supported", message: "view destroyed" });
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function decodeBase64(b64: string): Uint8Array {
|
|
73
|
+
const bin = atob(b64);
|
|
74
|
+
const out = new Uint8Array(bin.length);
|
|
75
|
+
for (let i = 0; i < bin.length; i++) out[i] = bin.charCodeAt(i);
|
|
76
|
+
return out;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
setScreenshotResultHandler((viewId, raw: NativeScreenshotResult) => {
|
|
80
|
+
const entry = screenshotResolvers.get(raw.requestId);
|
|
81
|
+
if (!entry) return;
|
|
82
|
+
if (entry.viewId !== viewId) return;
|
|
83
|
+
screenshotResolvers.delete(raw.requestId);
|
|
84
|
+
if (raw.ok && raw.dataBase64 && raw.format && raw.mime) {
|
|
85
|
+
try {
|
|
86
|
+
entry.resolve({ ok: true, data: decodeBase64(raw.dataBase64), mime: raw.mime, format: raw.format });
|
|
87
|
+
} catch (e) {
|
|
88
|
+
entry.resolve({ ok: false, code: "runtime_error", message: `base64 decode failed: ${(e as Error).message}` });
|
|
89
|
+
}
|
|
90
|
+
} else {
|
|
91
|
+
entry.resolve({
|
|
92
|
+
ok: false,
|
|
93
|
+
code: (raw.code as "not_supported" | "runtime_error" | "timeout") ?? "runtime_error",
|
|
94
|
+
message: raw.message ?? "screenshot failed",
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
setEvaluateResultHandler((viewId, raw: NativeEvaluateResult) => {
|
|
100
|
+
const entry = evaluateResolvers.get(raw.requestId);
|
|
101
|
+
if (!entry) return;
|
|
102
|
+
if (entry.viewId !== viewId) return; // foreign event — ignore
|
|
42
103
|
evaluateResolvers.delete(raw.requestId);
|
|
43
104
|
if (raw.ok && raw.value !== undefined) {
|
|
44
105
|
try {
|
|
45
|
-
resolve({ ok: true, value: JSON.parse(raw.value) });
|
|
106
|
+
entry.resolve({ ok: true, value: JSON.parse(raw.value) });
|
|
46
107
|
} catch (e) {
|
|
47
|
-
resolve({ ok: false, code: "runtime_error", message: `result JSON parse failed: ${(e as Error).message}` });
|
|
108
|
+
entry.resolve({ ok: false, code: "runtime_error", message: `result JSON parse failed: ${(e as Error).message}` });
|
|
48
109
|
}
|
|
49
110
|
} else {
|
|
50
|
-
resolve({
|
|
111
|
+
entry.resolve({
|
|
51
112
|
ok: false,
|
|
52
113
|
code: (raw.code as EvaluateResult extends { code: infer C } ? C : never) ?? "runtime_error",
|
|
53
114
|
message: raw.message ?? "evaluate failed",
|
|
@@ -55,18 +116,40 @@ setEvaluateResultHandler((_viewId, raw: NativeEvaluateResult) => {
|
|
|
55
116
|
}
|
|
56
117
|
});
|
|
57
118
|
|
|
58
|
-
|
|
59
|
-
|
|
119
|
+
// Bit positions match the native enum in `ffi_exports.h` (BuniteCapBit).
|
|
120
|
+
const CAP_EVALUATE = 1 << 0;
|
|
121
|
+
const CAP_CROSS_ORIGIN_EVAL = 1 << 1;
|
|
122
|
+
const CAP_SURFACE_EVENTS = 1 << 2;
|
|
123
|
+
const CAP_NATIVE_INPUT_TRUSTED = 1 << 3;
|
|
124
|
+
const CAP_CLICK = 1 << 4;
|
|
125
|
+
const CAP_TYPE = 1 << 5;
|
|
126
|
+
const CAP_PRESS = 1 << 6;
|
|
127
|
+
const CAP_SCROLL = 1 << 7;
|
|
128
|
+
const CAP_SCREENSHOT = 1 << 8;
|
|
129
|
+
const CAP_FORMAT_PNG = 1 << 9;
|
|
130
|
+
const CAP_FORMAT_JPEG = 1 << 10;
|
|
131
|
+
const CAP_MOUSE = 1 << 11;
|
|
132
|
+
const CAP_DIALOGS = 1 << 12;
|
|
133
|
+
const CAP_CONSOLE = 1 << 13;
|
|
134
|
+
|
|
135
|
+
function decodeCapabilityBits(bits: number): SurfaceCapabilities {
|
|
136
|
+
const formats: ("png" | "jpeg")[] = [];
|
|
137
|
+
if (bits & CAP_FORMAT_PNG) formats.push("png");
|
|
138
|
+
if (bits & CAP_FORMAT_JPEG) formats.push("jpeg");
|
|
60
139
|
return {
|
|
61
|
-
evaluate:
|
|
62
|
-
crossOriginEval:
|
|
63
|
-
|
|
64
|
-
nativeInputTrusted:
|
|
65
|
-
click:
|
|
66
|
-
type:
|
|
67
|
-
press:
|
|
68
|
-
scroll:
|
|
69
|
-
|
|
140
|
+
evaluate: !!(bits & CAP_EVALUATE),
|
|
141
|
+
crossOriginEval: !!(bits & CAP_CROSS_ORIGIN_EVAL),
|
|
142
|
+
surfaceEvents: !!(bits & CAP_SURFACE_EVENTS),
|
|
143
|
+
nativeInputTrusted: !!(bits & CAP_NATIVE_INPUT_TRUSTED),
|
|
144
|
+
click: !!(bits & CAP_CLICK),
|
|
145
|
+
type: !!(bits & CAP_TYPE),
|
|
146
|
+
press: !!(bits & CAP_PRESS),
|
|
147
|
+
scroll: !!(bits & CAP_SCROLL),
|
|
148
|
+
mouse: !!(bits & CAP_MOUSE),
|
|
149
|
+
dialogs: !!(bits & CAP_DIALOGS),
|
|
150
|
+
console: !!(bits & CAP_CONSOLE),
|
|
151
|
+
screenshot: !!(bits & CAP_SCREENSHOT),
|
|
152
|
+
...(formats.length > 0 ? { formats } : {}),
|
|
70
153
|
};
|
|
71
154
|
}
|
|
72
155
|
|
|
@@ -264,13 +347,97 @@ export class BrowserView {
|
|
|
264
347
|
return Promise.resolve({ ok: false, code: "not_supported", message: "native runtime unavailable" });
|
|
265
348
|
}
|
|
266
349
|
return new Promise<EvaluateResult>((resolve) => {
|
|
267
|
-
const requestId = registerEvaluateRequest(resolve);
|
|
350
|
+
const requestId = registerEvaluateRequest(this.id, resolve);
|
|
268
351
|
getNativeLibrary()?.symbols.bunite_view_evaluate(this.id, requestId, toCString(script));
|
|
269
352
|
});
|
|
270
353
|
}
|
|
271
354
|
|
|
272
355
|
capabilities(): SurfaceCapabilities {
|
|
273
|
-
return
|
|
356
|
+
if (!this.nativeAttached) return decodeCapabilityBits(0);
|
|
357
|
+
const bits = getNativeLibrary()?.symbols.bunite_view_capabilities(this.id) ?? 0;
|
|
358
|
+
return decodeCapabilityBits(bits);
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
// High-level automation API — same shape as `SurfaceCap` RPC + element
|
|
362
|
+
// `send*` methods. Modifier translation + key resolution happen inside;
|
|
363
|
+
// callers never touch the FFI int contract.
|
|
364
|
+
click(args: {
|
|
365
|
+
x: number; y: number;
|
|
366
|
+
button?: "left" | "middle" | "right";
|
|
367
|
+
clickCount?: number;
|
|
368
|
+
modifiers?: Modifier[];
|
|
369
|
+
}) {
|
|
370
|
+
if (!this.nativeAttached) return;
|
|
371
|
+
const button = args.button === "right" ? 2 : args.button === "middle" ? 1 : 0;
|
|
372
|
+
getNativeLibrary()?.symbols.bunite_view_click(
|
|
373
|
+
this.id, args.x, args.y, button, args.clickCount ?? 1, encodeModifiers(args.modifiers)
|
|
374
|
+
);
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
type(text: string) {
|
|
378
|
+
if (!this.nativeAttached) return;
|
|
379
|
+
getNativeLibrary()?.symbols.bunite_view_type(this.id, toCString(text));
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
press(key: string, modifiers?: Modifier[], action?: "down" | "up" | "both") {
|
|
383
|
+
if (!this.nativeAttached) return;
|
|
384
|
+
const r = resolveKey(key);
|
|
385
|
+
const a = action === "down" ? 0 : action === "up" ? 1 : 2;
|
|
386
|
+
getNativeLibrary()?.symbols.bunite_view_press(
|
|
387
|
+
this.id, r.windowsVkCode, r.macKeyCode,
|
|
388
|
+
toCString(r.key), toCString(r.code), toCString(r.character),
|
|
389
|
+
encodeModifiers(modifiers), a, r.extended, r.location
|
|
390
|
+
);
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
scroll(args: {
|
|
394
|
+
dx: number; dy: number; x?: number; y?: number;
|
|
395
|
+
modifiers?: Modifier[];
|
|
396
|
+
}) {
|
|
397
|
+
if (!this.nativeAttached) return;
|
|
398
|
+
getNativeLibrary()?.symbols.bunite_view_scroll(
|
|
399
|
+
this.id, args.dx, args.dy, args.x ?? 0, args.y ?? 0, encodeModifiers(args.modifiers)
|
|
400
|
+
);
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
mouse(args: {
|
|
404
|
+
action: "move" | "down" | "up";
|
|
405
|
+
x: number; y: number;
|
|
406
|
+
button?: "left" | "middle" | "right";
|
|
407
|
+
modifiers?: Modifier[];
|
|
408
|
+
}) {
|
|
409
|
+
if (!this.nativeAttached) return;
|
|
410
|
+
const action = args.action === "move" ? 0 : args.action === "down" ? 1 : 2;
|
|
411
|
+
const button = args.button === "right" ? 2 : args.button === "middle" ? 1 : 0;
|
|
412
|
+
getNativeLibrary()?.symbols.bunite_view_mouse(
|
|
413
|
+
this.id, action, args.x, args.y, button, encodeModifiers(args.modifiers)
|
|
414
|
+
);
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
respondToDialog(requestId: number, accept: boolean, text?: string) {
|
|
418
|
+
if (!this.nativeAttached) return;
|
|
419
|
+
getNativeLibrary()?.symbols.bunite_view_respond_dialog(
|
|
420
|
+
this.id, requestId, accept, toCString(text ?? "")
|
|
421
|
+
);
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
screenshot(format: "png" | "jpeg", quality: number): Promise<ScreenshotResult> {
|
|
425
|
+
if (!this.nativeAttached) {
|
|
426
|
+
return Promise.resolve({ ok: false, code: "not_supported", message: "native runtime unavailable" });
|
|
427
|
+
}
|
|
428
|
+
return new Promise<ScreenshotResult>((resolve) => {
|
|
429
|
+
const requestId = registerScreenshotRequest(this.id, resolve);
|
|
430
|
+
// Timeout — guards against silent hangs (e.g. CEF compositor never delivers).
|
|
431
|
+
const timer = setTimeout(() => {
|
|
432
|
+
if (screenshotResolvers.delete(requestId)) {
|
|
433
|
+
resolve({ ok: false, code: "timeout", message: "screenshot timed out after 30s" });
|
|
434
|
+
}
|
|
435
|
+
}, 30_000);
|
|
436
|
+
const wrappedResolve = (r: ScreenshotResult) => { clearTimeout(timer); resolve(r); };
|
|
437
|
+
// Replace the registered resolver so the timeout-clearing wrapper runs on success.
|
|
438
|
+
screenshotResolvers.set(requestId, { viewId: this.id, resolve: wrappedResolve });
|
|
439
|
+
getNativeLibrary()?.symbols.bunite_view_screenshot(this.id, requestId, toCString(format), quality);
|
|
440
|
+
});
|
|
274
441
|
}
|
|
275
442
|
|
|
276
443
|
goBack() {
|
|
@@ -377,6 +544,8 @@ export class BrowserView {
|
|
|
377
544
|
detachFromNative() {
|
|
378
545
|
removeSurfacesForHostView(this.id);
|
|
379
546
|
cancelWaitForViewReady(this.id);
|
|
547
|
+
rejectEvaluatesForView(this.id);
|
|
548
|
+
rejectScreenshotsForView(this.id);
|
|
380
549
|
this.nativeAttached = false;
|
|
381
550
|
for (const eventName of [
|
|
382
551
|
"will-navigate", "did-navigate", "dom-ready", "new-window-open", "permission-requested", "title-changed"
|
|
@@ -387,7 +556,7 @@ export class BrowserView {
|
|
|
387
556
|
}
|
|
388
557
|
|
|
389
558
|
on(
|
|
390
|
-
name: "will-navigate" | "did-navigate" | "dom-ready" | "new-window-open" | "permission-requested" | "title-changed",
|
|
559
|
+
name: "will-navigate" | "did-navigate" | "dom-ready" | "new-window-open" | "permission-requested" | "title-changed" | "load-start" | "load-finish" | "load-fail" | "dialog" | "console-message",
|
|
391
560
|
handler: (event: unknown) => void
|
|
392
561
|
) {
|
|
393
562
|
const specificName = `${name}-${this.id}`;
|
|
@@ -1,10 +1,51 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
onSurfaceInit, emitSurfaceEvent, emitConsole,
|
|
3
|
+
registerDialogRequest, disposeSurfaceState, clearConsoleBuffer,
|
|
4
|
+
} from "./SurfaceManager";
|
|
2
5
|
|
|
3
6
|
onSurfaceInit((surfaceId, hostViewId, view) => {
|
|
4
7
|
view.on("did-navigate", (event: any) => {
|
|
5
|
-
|
|
8
|
+
emitSurfaceEvent(hostViewId, surfaceId, { type: "navigate", url: event.data.detail });
|
|
6
9
|
});
|
|
7
10
|
view.on("title-changed", (event: any) => {
|
|
8
|
-
|
|
11
|
+
emitSurfaceEvent(hostViewId, surfaceId, { type: "title-change", title: event.data.detail });
|
|
12
|
+
});
|
|
13
|
+
view.on("load-start", (event: any) => {
|
|
14
|
+
// Reload / fresh nav — clear retained host buffer so consumers don't see
|
|
15
|
+
// stale entries from the prior document.
|
|
16
|
+
clearConsoleBuffer(surfaceId);
|
|
17
|
+
emitSurfaceEvent(hostViewId, surfaceId, { type: "load-start", url: event.data.detail });
|
|
18
|
+
});
|
|
19
|
+
view.on("load-finish", (event: any) => {
|
|
20
|
+
emitSurfaceEvent(hostViewId, surfaceId, { type: "load-finish", url: event.data.detail });
|
|
21
|
+
});
|
|
22
|
+
view.on("load-fail", (event: any) => {
|
|
23
|
+
const d = event.data;
|
|
24
|
+
emitSurfaceEvent(hostViewId, surfaceId, {
|
|
25
|
+
type: "load-fail", url: d.url ?? "", reason: d.reason,
|
|
26
|
+
});
|
|
27
|
+
});
|
|
28
|
+
view.on("dialog", (event: any) => {
|
|
29
|
+
const d = event.data as {
|
|
30
|
+
requestId: number;
|
|
31
|
+
kind: "alert" | "confirm" | "prompt" | "beforeunload";
|
|
32
|
+
message: string;
|
|
33
|
+
defaultPrompt?: string;
|
|
34
|
+
};
|
|
35
|
+
registerDialogRequest(hostViewId, surfaceId, d);
|
|
36
|
+
});
|
|
37
|
+
view.on("console-message", (event: any) => {
|
|
38
|
+
// PageReportingCap impl already wraps the whole batch in a single
|
|
39
|
+
// microtask — no extra deferral needed at the listener level.
|
|
40
|
+
emitConsole(hostViewId, surfaceId, event.data);
|
|
9
41
|
});
|
|
10
42
|
});
|
|
43
|
+
|
|
44
|
+
// Surface registry's untrackSurface doesn't fire a teardown event — wire it
|
|
45
|
+
// at the call site if needed. For now state TTL aligns with the view lifetime
|
|
46
|
+
// since surfaceId is reused only after view destruction; the buffer is GC'd
|
|
47
|
+
// once the surface is removed via `remove()` which eventually triggers
|
|
48
|
+
// `disposeSurfaceState` here.
|
|
49
|
+
export function disposeSurface(surfaceId: number) {
|
|
50
|
+
disposeSurfaceState(surfaceId);
|
|
51
|
+
}
|