bunite-core 0.12.1 → 0.16.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 +19 -2
- package/src/host/core/BrowserView.ts +515 -38
- package/src/host/core/SurfaceBrowserIPC.ts +53 -3
- package/src/host/core/SurfaceManager.ts +603 -30
- package/src/host/core/SurfaceRegistry.ts +9 -1
- package/src/host/core/inputDispatch.ts +147 -0
- package/src/host/events/webviewEvents.ts +25 -1
- package/src/host/log.ts +6 -1
- package/src/host/native.ts +263 -1
- package/src/host/preloadBundle.ts +7 -2
- package/src/native/linux/bunite_linux_ffi.cpp +427 -6
- package/src/native/linux/bunite_linux_internal.h +18 -0
- package/src/native/linux/bunite_linux_runtime.cpp +6 -1
- package/src/native/linux/bunite_linux_utils.cpp +2 -2
- package/src/native/linux/bunite_linux_view.cpp +296 -5
- package/src/native/mac/bunite_mac_ffi.mm +630 -8
- package/src/native/mac/bunite_mac_internal.h +19 -0
- package/src/native/mac/bunite_mac_utils.mm +2 -2
- package/src/native/mac/bunite_mac_view.mm +371 -9
- package/src/native/shared/ffi_exports.h +200 -2
- package/src/native/win/native_host_cef.cpp +186 -11
- package/src/native/win/native_host_ffi.cpp +1194 -1
- package/src/native/win/native_host_internal.h +35 -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 +1023 -12
- package/src/native/win-webview2/webview2_internal.h +25 -0
- package/src/native/win-webview2/webview2_runtime.cpp +403 -34
- 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 +340 -8
- package/src/rpc/index.ts +32 -0
- package/src/webview/native.ts +253 -51
- package/src/webview/polyfill.ts +283 -22
|
@@ -21,6 +21,13 @@ export function trackSurface(surfaceId: number, record: SurfaceRecord) {
|
|
|
21
21
|
ids.add(surfaceId);
|
|
22
22
|
}
|
|
23
23
|
|
|
24
|
+
type DisposeHook = (surfaceId: number) => void;
|
|
25
|
+
const disposeHooks: DisposeHook[] = [];
|
|
26
|
+
|
|
27
|
+
export function onSurfaceDispose(cb: DisposeHook) {
|
|
28
|
+
disposeHooks.push(cb);
|
|
29
|
+
}
|
|
30
|
+
|
|
24
31
|
export function untrackSurface(surfaceId: number) {
|
|
25
32
|
const record = surfaces.get(surfaceId);
|
|
26
33
|
if (!record) return;
|
|
@@ -30,6 +37,7 @@ export function untrackSurface(surfaceId: number) {
|
|
|
30
37
|
ids.delete(surfaceId);
|
|
31
38
|
if (ids.size === 0) hostSurfaceIds.delete(record.hostViewId);
|
|
32
39
|
}
|
|
40
|
+
for (const cb of disposeHooks) cb(surfaceId);
|
|
33
41
|
}
|
|
34
42
|
|
|
35
43
|
export function getOwnedSurface(surfaceId: number, ctx: { viewId: number }): SurfaceRecord | null {
|
|
@@ -54,7 +62,7 @@ export function removeSurfacesForHostView(hostViewId: number) {
|
|
|
54
62
|
for (const surfaceId of Array.from(ids)) {
|
|
55
63
|
const record = surfaces.get(surfaceId);
|
|
56
64
|
if (!record) continue;
|
|
57
|
-
untrackSurface(surfaceId);
|
|
65
|
+
untrackSurface(surfaceId); // fires disposeHooks
|
|
58
66
|
record.view.remove();
|
|
59
67
|
}
|
|
60
68
|
}
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
// Input dispatch helpers — modifier encoding + DOM `key` → Win32 VK + macOS
|
|
2
|
+
// Quartz key code + DOM `code` + char. Backends translate the FFI bitmask
|
|
3
|
+
// (Alt=1, Ctrl=2, Meta=4, Shift=8) to native form. Stage B keymap covers
|
|
4
|
+
// ASCII + the named keys Playwright-style automation relies on.
|
|
5
|
+
|
|
6
|
+
import type { Modifier } from "../../rpc/framework";
|
|
7
|
+
|
|
8
|
+
export function encodeModifiers(mods: Modifier[] | undefined): number {
|
|
9
|
+
if (!mods) return 0;
|
|
10
|
+
let bits = 0;
|
|
11
|
+
for (const m of mods) {
|
|
12
|
+
if (m === "alt") bits |= 1;
|
|
13
|
+
else if (m === "ctrl") bits |= 2;
|
|
14
|
+
else if (m === "meta") bits |= 4;
|
|
15
|
+
else if (m === "shift") bits |= 8;
|
|
16
|
+
}
|
|
17
|
+
return bits;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export interface ResolvedKey {
|
|
21
|
+
windowsVkCode: number;
|
|
22
|
+
macKeyCode: number;
|
|
23
|
+
/** DOM `KeyboardEvent.key` — pass-through; native dispatchers forward to engines. */
|
|
24
|
+
key: string;
|
|
25
|
+
/** DOM `KeyboardEvent.code` — derived from US keyboard mapping. */
|
|
26
|
+
code: string;
|
|
27
|
+
/** Text payload for the CHAR / insertText event; empty = skip char. */
|
|
28
|
+
character: string;
|
|
29
|
+
/** Win scancode 0xE0 prefix: nav cluster (Arrow/Insert/Delete/Home/End/
|
|
30
|
+
* PageUp/PageDown/Meta/ContextMenu) AND Numpad-Enter. Distinct from
|
|
31
|
+
* `location` — most extended keys are NOT numpad (location 0). */
|
|
32
|
+
extended: boolean;
|
|
33
|
+
/** DOM `KeyboardEvent.location`: 0=standard, 1=left mod, 2=right mod,
|
|
34
|
+
* 3=numpad. WV2 CDP uses this; CEF derives from scancode 0xE0 prefix. */
|
|
35
|
+
location: 0 | 1 | 2 | 3;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/** Maps a DOM `KeyboardEvent.key` value to backend-neutral identifiers. */
|
|
39
|
+
export function resolveKey(domKey: string): ResolvedKey {
|
|
40
|
+
if (domKey.length === 0) {
|
|
41
|
+
return { windowsVkCode: 0, macKeyCode: 0, key: "", code: "", character: "", extended: false, location: 0 };
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// Named key (Enter, Tab, ArrowLeft …).
|
|
45
|
+
const named = NAMED_KEYS[domKey];
|
|
46
|
+
if (named) {
|
|
47
|
+
return {
|
|
48
|
+
windowsVkCode: named.win,
|
|
49
|
+
macKeyCode: named.mac,
|
|
50
|
+
key: domKey,
|
|
51
|
+
code: named.code,
|
|
52
|
+
// Space/Tab/Enter generate text in CDP automatically; we pass an explicit
|
|
53
|
+
// character so DOM `keypress` fires consistently across engines.
|
|
54
|
+
character: named.character ?? "",
|
|
55
|
+
extended: named.ext === true,
|
|
56
|
+
location: named.loc ?? 0,
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// Single Unicode codepoint — letter / digit / printable / extended.
|
|
61
|
+
if ([...domKey].length === 1) {
|
|
62
|
+
const cp = domKey.codePointAt(0)!;
|
|
63
|
+
// ASCII A-Z / a-z → matching Win VK + mac keyCode + DOM code "KeyX".
|
|
64
|
+
if ((cp >= 0x41 && cp <= 0x5A) || (cp >= 0x61 && cp <= 0x7A)) {
|
|
65
|
+
const upper = cp & ~0x20; // strip lowercase bit
|
|
66
|
+
return {
|
|
67
|
+
windowsVkCode: upper,
|
|
68
|
+
macKeyCode: MAC_KEY_LETTER[upper - 0x41],
|
|
69
|
+
key: domKey,
|
|
70
|
+
code: `Key${String.fromCharCode(upper)}`,
|
|
71
|
+
character: domKey,
|
|
72
|
+
extended: false,
|
|
73
|
+
location: 0,
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
// ASCII 0-9.
|
|
77
|
+
if (cp >= 0x30 && cp <= 0x39) {
|
|
78
|
+
return {
|
|
79
|
+
windowsVkCode: cp,
|
|
80
|
+
macKeyCode: MAC_KEY_DIGIT[cp - 0x30],
|
|
81
|
+
key: domKey,
|
|
82
|
+
code: `Digit${domKey}`,
|
|
83
|
+
character: domKey,
|
|
84
|
+
extended: false,
|
|
85
|
+
location: 0,
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
// Other printable codepoint — char event only, no virtual key.
|
|
89
|
+
return { windowsVkCode: 0, macKeyCode: 0, key: domKey, code: "", character: domKey, extended: false, location: 0 };
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// Multi-codepoint string we don't recognise as a named key — pass-through.
|
|
93
|
+
return { windowsVkCode: 0, macKeyCode: 0, key: domKey, code: "", character: "", extended: false, location: 0 };
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// Win32 VK_* + Quartz Event Services kVK_* + DOM code + literal character +
|
|
97
|
+
// LPARAM extended-key flag. `ext: true` for nav-cluster keys (separate from
|
|
98
|
+
// numpad equivalents) and Numpad-Enter — Chromium derives `KeyboardEvent.code`
|
|
99
|
+
// from LPARAM scancode + extended bit. Sources:
|
|
100
|
+
// learn.microsoft.com/windows/win32/inputdev/virtual-key-codes
|
|
101
|
+
// chromium/ui/events/keycodes/dom/keycode_converter_data.inc
|
|
102
|
+
type NamedKey = { win: number; mac: number; code: string; character?: string; ext?: true; loc?: 1 | 2 | 3 };
|
|
103
|
+
const NAMED_KEYS: Record<string, NamedKey> = {
|
|
104
|
+
Backspace: { win: 0x08, mac: 0x33, code: "Backspace" },
|
|
105
|
+
Tab: { win: 0x09, mac: 0x30, code: "Tab", character: "\t" },
|
|
106
|
+
Enter: { win: 0x0D, mac: 0x24, code: "Enter", character: "\r" },
|
|
107
|
+
NumpadEnter: { win: 0x0D, mac: 0x4C, code: "NumpadEnter", character: "\r", ext: true, loc: 3 },
|
|
108
|
+
Escape: { win: 0x1B, mac: 0x35, code: "Escape" },
|
|
109
|
+
" ": { win: 0x20, mac: 0x31, code: "Space", character: " " },
|
|
110
|
+
Space: { win: 0x20, mac: 0x31, code: "Space", character: " " },
|
|
111
|
+
PageUp: { win: 0x21, mac: 0x74, code: "PageUp", ext: true },
|
|
112
|
+
PageDown: { win: 0x22, mac: 0x79, code: "PageDown", ext: true },
|
|
113
|
+
End: { win: 0x23, mac: 0x77, code: "End", ext: true },
|
|
114
|
+
Home: { win: 0x24, mac: 0x73, code: "Home", ext: true },
|
|
115
|
+
ArrowLeft: { win: 0x25, mac: 0x7B, code: "ArrowLeft", ext: true },
|
|
116
|
+
ArrowUp: { win: 0x26, mac: 0x7E, code: "ArrowUp", ext: true },
|
|
117
|
+
ArrowRight: { win: 0x27, mac: 0x7C, code: "ArrowRight", ext: true },
|
|
118
|
+
ArrowDown: { win: 0x28, mac: 0x7D, code: "ArrowDown", ext: true },
|
|
119
|
+
Insert: { win: 0x2D, mac: 0x72, code: "Insert", ext: true },
|
|
120
|
+
Delete: { win: 0x2E, mac: 0x75, code: "Delete", ext: true },
|
|
121
|
+
Meta: { win: 0x5B, mac: 0x37, code: "MetaLeft", ext: true },
|
|
122
|
+
ContextMenu: { win: 0x5D, mac: 0x6E, code: "ContextMenu", ext: true },
|
|
123
|
+
F1: { win: 0x70, mac: 0x7A, code: "F1" },
|
|
124
|
+
F2: { win: 0x71, mac: 0x78, code: "F2" },
|
|
125
|
+
F3: { win: 0x72, mac: 0x63, code: "F3" },
|
|
126
|
+
F4: { win: 0x73, mac: 0x76, code: "F4" },
|
|
127
|
+
F5: { win: 0x74, mac: 0x60, code: "F5" },
|
|
128
|
+
F6: { win: 0x75, mac: 0x61, code: "F6" },
|
|
129
|
+
F7: { win: 0x76, mac: 0x62, code: "F7" },
|
|
130
|
+
F8: { win: 0x77, mac: 0x64, code: "F8" },
|
|
131
|
+
F9: { win: 0x78, mac: 0x65, code: "F9" },
|
|
132
|
+
F10: { win: 0x79, mac: 0x6D, code: "F10" },
|
|
133
|
+
F11: { win: 0x7A, mac: 0x67, code: "F11" },
|
|
134
|
+
F12: { win: 0x7B, mac: 0x6F, code: "F12" },
|
|
135
|
+
};
|
|
136
|
+
|
|
137
|
+
// US keyboard layout — Quartz hardware key code per ASCII letter (A → 0x00, …, Z → 0x06).
|
|
138
|
+
const MAC_KEY_LETTER = [
|
|
139
|
+
// A B C D E F G H I J K L M
|
|
140
|
+
0x00, 0x0B, 0x08, 0x02, 0x0E, 0x03, 0x05, 0x04, 0x22, 0x26, 0x28, 0x25, 0x2E,
|
|
141
|
+
// N O P Q R S T U V W X Y Z
|
|
142
|
+
0x2D, 0x1F, 0x23, 0x0C, 0x0F, 0x01, 0x11, 0x20, 0x09, 0x0D, 0x07, 0x10, 0x06,
|
|
143
|
+
];
|
|
144
|
+
// US keyboard layout — Quartz hardware key code per digit (0 → 0x1D, 1 → 0x12 …).
|
|
145
|
+
const MAC_KEY_DIGIT = [
|
|
146
|
+
0x1D, 0x12, 0x13, 0x14, 0x15, 0x17, 0x16, 0x1A, 0x1C, 0x19,
|
|
147
|
+
];
|
|
@@ -8,5 +8,29 @@ export default {
|
|
|
8
8
|
new BuniteEvent("new-window-open", data),
|
|
9
9
|
permissionRequested: (data: { requestId: number; kind: number; url?: string }) =>
|
|
10
10
|
new BuniteEvent("permission-requested", data),
|
|
11
|
-
titleChanged: (data: { detail: string }) => new BuniteEvent("title-changed", data)
|
|
11
|
+
titleChanged: (data: { detail: string }) => new BuniteEvent("title-changed", data),
|
|
12
|
+
loadStart: (data: { detail: string }) => new BuniteEvent("load-start", data),
|
|
13
|
+
loadFinish: (data: { detail: string }) => new BuniteEvent("load-finish", data),
|
|
14
|
+
loadFail: (data: { url: string; reason?: string }) => new BuniteEvent("load-fail", data),
|
|
15
|
+
dialog: (data: { requestId: number; kind: "alert" | "confirm" | "prompt" | "beforeunload"; message: string; defaultPrompt?: string }) =>
|
|
16
|
+
new BuniteEvent("dialog", data),
|
|
17
|
+
consoleMessage: (data: { level: "log" | "warn" | "error" | "info" | "debug"; args: string[]; ts: number }) =>
|
|
18
|
+
new BuniteEvent("console-message", data),
|
|
19
|
+
downloadEvent: (data: {
|
|
20
|
+
kind: "started" | "progress" | "completed" | "failed" | "blocked";
|
|
21
|
+
id: string;
|
|
22
|
+
url?: string;
|
|
23
|
+
suggestedFilename?: string;
|
|
24
|
+
mimeType?: string;
|
|
25
|
+
sizeBytes?: number;
|
|
26
|
+
receivedBytes?: number;
|
|
27
|
+
totalBytes?: number;
|
|
28
|
+
localPath?: string;
|
|
29
|
+
reason?: string;
|
|
30
|
+
}) => new BuniteEvent("download-event", data),
|
|
31
|
+
popupRequested: (data: {
|
|
32
|
+
newSurfaceId: number;
|
|
33
|
+
url: string;
|
|
34
|
+
disposition: "tab" | "window" | "popup";
|
|
35
|
+
}) => new BuniteEvent("popup-requested", data),
|
|
12
36
|
};
|
package/src/host/log.ts
CHANGED
|
@@ -8,7 +8,12 @@ const levels: Record<LogLevel, number> = {
|
|
|
8
8
|
silent: 4
|
|
9
9
|
};
|
|
10
10
|
|
|
11
|
-
|
|
11
|
+
function initialLevel(): LogLevel {
|
|
12
|
+
const v = (typeof process !== "undefined" ? process.env?.BUNITE_LOG_LEVEL : undefined);
|
|
13
|
+
return v === "debug" || v === "info" || v === "warn" || v === "error" || v === "silent" ? v : "warn";
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
let currentLevel: LogLevel = initialLevel();
|
|
12
17
|
|
|
13
18
|
function shouldLog(level: LogLevel): boolean {
|
|
14
19
|
return levels[level] >= levels[currentLevel];
|
package/src/host/native.ts
CHANGED
|
@@ -102,6 +102,51 @@ type NativeSymbols = {
|
|
|
102
102
|
bunite_view_reload: (viewId: number) => void;
|
|
103
103
|
bunite_view_execute_javascript: (viewId: number, script: CStringPointer) => void;
|
|
104
104
|
bunite_view_evaluate: (viewId: number, requestId: number, script: CStringPointer) => void;
|
|
105
|
+
bunite_view_click: (
|
|
106
|
+
viewId: number, x: number, y: number,
|
|
107
|
+
button: number, clickCount: number, modifiers: number
|
|
108
|
+
) => void;
|
|
109
|
+
bunite_view_type: (viewId: number, text: CStringPointer) => void;
|
|
110
|
+
bunite_view_press: (
|
|
111
|
+
viewId: number, windowsVkCode: number, macKeyCode: number,
|
|
112
|
+
key: CStringPointer, code: CStringPointer, character: CStringPointer,
|
|
113
|
+
modifiers: number, action: number, extended: boolean, location: number
|
|
114
|
+
) => void;
|
|
115
|
+
bunite_view_scroll: (
|
|
116
|
+
viewId: number, dx: number, dy: number,
|
|
117
|
+
x: number, y: number, modifiers: number
|
|
118
|
+
) => void;
|
|
119
|
+
bunite_view_mouse: (
|
|
120
|
+
viewId: number, action: number, x: number, y: number,
|
|
121
|
+
button: number, modifiers: number
|
|
122
|
+
) => void;
|
|
123
|
+
bunite_view_respond_dialog: (
|
|
124
|
+
viewId: number, requestId: number, accept: boolean, text: CStringPointer
|
|
125
|
+
) => void;
|
|
126
|
+
bunite_view_screenshot: (
|
|
127
|
+
viewId: number, requestId: number, format: CStringPointer, quality: number
|
|
128
|
+
) => void;
|
|
129
|
+
bunite_view_accessibility_snapshot: (
|
|
130
|
+
viewId: number, requestId: number, interestingOnly: number
|
|
131
|
+
) => void;
|
|
132
|
+
bunite_view_list_frames: (viewId: number, requestId: number) => void;
|
|
133
|
+
bunite_view_evaluate_in_frame: (
|
|
134
|
+
viewId: number, requestId: number, script: CStringPointer, frameId: CStringPointer
|
|
135
|
+
) => void;
|
|
136
|
+
bunite_view_resolve_and_click: (
|
|
137
|
+
viewId: number, requestId: number,
|
|
138
|
+
selector: CStringPointer, frameId: CStringPointer,
|
|
139
|
+
button: number, clickCount: number, modifiers: number
|
|
140
|
+
) => void;
|
|
141
|
+
bunite_view_set_download_policy: (
|
|
142
|
+
viewId: number, policy: number, downloadDir: CStringPointer
|
|
143
|
+
) => void;
|
|
144
|
+
bunite_view_popup_accept: (
|
|
145
|
+
newViewId: number, hostWindowId: number,
|
|
146
|
+
x: number, y: number, w: number, h: number,
|
|
147
|
+
) => void;
|
|
148
|
+
bunite_view_popup_dismiss: (newViewId: number) => void;
|
|
149
|
+
bunite_view_capabilities: (viewId: number) => number;
|
|
105
150
|
bunite_view_load_url: (viewId: number, url: CStringPointer) => void;
|
|
106
151
|
bunite_view_load_html: (viewId: number, html: CStringPointer) => void;
|
|
107
152
|
bunite_view_remove: (viewId: number) => void;
|
|
@@ -289,6 +334,66 @@ const nativeSymbolDefinitions = {
|
|
|
289
334
|
args: [FFIType.u32, FFIType.u32, FFIType.cstring],
|
|
290
335
|
returns: FFIType.void
|
|
291
336
|
},
|
|
337
|
+
bunite_view_click: {
|
|
338
|
+
args: [FFIType.u32, FFIType.f64, FFIType.f64, FFIType.i32, FFIType.i32, FFIType.u32],
|
|
339
|
+
returns: FFIType.void
|
|
340
|
+
},
|
|
341
|
+
bunite_view_type: {
|
|
342
|
+
args: [FFIType.u32, FFIType.cstring],
|
|
343
|
+
returns: FFIType.void
|
|
344
|
+
},
|
|
345
|
+
bunite_view_press: {
|
|
346
|
+
args: [FFIType.u32, FFIType.i32, FFIType.i32, FFIType.cstring, FFIType.cstring, FFIType.cstring, FFIType.u32, FFIType.i32, FFIType.bool, FFIType.i32],
|
|
347
|
+
returns: FFIType.void
|
|
348
|
+
},
|
|
349
|
+
bunite_view_scroll: {
|
|
350
|
+
args: [FFIType.u32, FFIType.f64, FFIType.f64, FFIType.f64, FFIType.f64, FFIType.u32],
|
|
351
|
+
returns: FFIType.void
|
|
352
|
+
},
|
|
353
|
+
bunite_view_mouse: {
|
|
354
|
+
args: [FFIType.u32, FFIType.i32, FFIType.f64, FFIType.f64, FFIType.i32, FFIType.u32],
|
|
355
|
+
returns: FFIType.void
|
|
356
|
+
},
|
|
357
|
+
bunite_view_respond_dialog: {
|
|
358
|
+
args: [FFIType.u32, FFIType.u32, FFIType.bool, FFIType.cstring],
|
|
359
|
+
returns: FFIType.void
|
|
360
|
+
},
|
|
361
|
+
bunite_view_screenshot: {
|
|
362
|
+
args: [FFIType.u32, FFIType.u32, FFIType.cstring, FFIType.i32],
|
|
363
|
+
returns: FFIType.void
|
|
364
|
+
},
|
|
365
|
+
bunite_view_accessibility_snapshot: {
|
|
366
|
+
args: [FFIType.u32, FFIType.u32, FFIType.i32],
|
|
367
|
+
returns: FFIType.void
|
|
368
|
+
},
|
|
369
|
+
bunite_view_list_frames: {
|
|
370
|
+
args: [FFIType.u32, FFIType.u32],
|
|
371
|
+
returns: FFIType.void
|
|
372
|
+
},
|
|
373
|
+
bunite_view_evaluate_in_frame: {
|
|
374
|
+
args: [FFIType.u32, FFIType.u32, FFIType.cstring, FFIType.cstring],
|
|
375
|
+
returns: FFIType.void
|
|
376
|
+
},
|
|
377
|
+
bunite_view_resolve_and_click: {
|
|
378
|
+
args: [FFIType.u32, FFIType.u32, FFIType.cstring, FFIType.cstring, FFIType.i32, FFIType.i32, FFIType.u32],
|
|
379
|
+
returns: FFIType.void
|
|
380
|
+
},
|
|
381
|
+
bunite_view_set_download_policy: {
|
|
382
|
+
args: [FFIType.u32, FFIType.i32, FFIType.cstring],
|
|
383
|
+
returns: FFIType.void
|
|
384
|
+
},
|
|
385
|
+
bunite_view_popup_accept: {
|
|
386
|
+
args: [FFIType.u32, FFIType.u32, FFIType.f64, FFIType.f64, FFIType.f64, FFIType.f64],
|
|
387
|
+
returns: FFIType.void
|
|
388
|
+
},
|
|
389
|
+
bunite_view_popup_dismiss: {
|
|
390
|
+
args: [FFIType.u32],
|
|
391
|
+
returns: FFIType.void
|
|
392
|
+
},
|
|
393
|
+
bunite_view_capabilities: {
|
|
394
|
+
args: [FFIType.u32],
|
|
395
|
+
returns: FFIType.u32
|
|
396
|
+
},
|
|
292
397
|
bunite_view_load_url: {
|
|
293
398
|
args: [FFIType.u32, FFIType.cstring],
|
|
294
399
|
returns: FFIType.void
|
|
@@ -350,6 +455,75 @@ export function setEvaluateResultHandler(handler: (viewId: number, result: Nativ
|
|
|
350
455
|
evaluateResultHandler = handler;
|
|
351
456
|
}
|
|
352
457
|
|
|
458
|
+
export type NativeScreenshotResult = {
|
|
459
|
+
requestId: number;
|
|
460
|
+
ok: boolean;
|
|
461
|
+
format?: "png" | "jpeg";
|
|
462
|
+
mime?: string;
|
|
463
|
+
dataBase64?: string;
|
|
464
|
+
code?: string;
|
|
465
|
+
message?: string;
|
|
466
|
+
};
|
|
467
|
+
let screenshotResultHandler: ((viewId: number, result: NativeScreenshotResult) => void) | null = null;
|
|
468
|
+
export function setScreenshotResultHandler(handler: (viewId: number, result: NativeScreenshotResult) => void) {
|
|
469
|
+
screenshotResultHandler = handler;
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
export type NativeAccessibilityResult = {
|
|
473
|
+
requestId: number;
|
|
474
|
+
ok: boolean;
|
|
475
|
+
tree?: unknown; // CDP Accessibility.AXNode tree as JSON value
|
|
476
|
+
code?: string;
|
|
477
|
+
message?: string;
|
|
478
|
+
};
|
|
479
|
+
let accessibilityResultHandler: ((viewId: number, result: NativeAccessibilityResult) => void) | null = null;
|
|
480
|
+
export function setAccessibilityResultHandler(handler: (viewId: number, result: NativeAccessibilityResult) => void) {
|
|
481
|
+
accessibilityResultHandler = handler;
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
export type NativeListFramesResult = {
|
|
485
|
+
requestId: number;
|
|
486
|
+
ok: boolean;
|
|
487
|
+
/** Raw CDP `Page.getFrameTree` result (`{frameTree: {frame, childFrames}}`). TS flattens. */
|
|
488
|
+
raw?: unknown;
|
|
489
|
+
code?: string;
|
|
490
|
+
message?: string;
|
|
491
|
+
};
|
|
492
|
+
let listFramesResultHandler: ((viewId: number, result: NativeListFramesResult) => void) | null = null;
|
|
493
|
+
export function setListFramesResultHandler(handler: (viewId: number, result: NativeListFramesResult) => void) {
|
|
494
|
+
listFramesResultHandler = handler;
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
export type NativeResolveAndClickResult = {
|
|
498
|
+
requestId: number;
|
|
499
|
+
ok: boolean;
|
|
500
|
+
rect?: { x: number; y: number; width: number; height: number };
|
|
501
|
+
isTrustedEvent?: boolean;
|
|
502
|
+
code?: string;
|
|
503
|
+
message?: string;
|
|
504
|
+
};
|
|
505
|
+
let resolveAndClickResultHandler: ((viewId: number, result: NativeResolveAndClickResult) => void) | null = null;
|
|
506
|
+
export function setResolveAndClickResultHandler(handler: (viewId: number, result: NativeResolveAndClickResult) => void) {
|
|
507
|
+
resolveAndClickResultHandler = handler;
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
export type NativeDownloadEvent = {
|
|
511
|
+
kind: "started" | "progress" | "completed" | "failed" | "blocked";
|
|
512
|
+
id: string;
|
|
513
|
+
url?: string;
|
|
514
|
+
suggestedFilename?: string;
|
|
515
|
+
mimeType?: string;
|
|
516
|
+
sizeBytes?: number;
|
|
517
|
+
receivedBytes?: number;
|
|
518
|
+
totalBytes?: number;
|
|
519
|
+
localPath?: string;
|
|
520
|
+
reason?: string;
|
|
521
|
+
};
|
|
522
|
+
let downloadEventHandler: ((viewId: number, event: NativeDownloadEvent) => void) | null = null;
|
|
523
|
+
export function setDownloadEventHandler(handler: (viewId: number, event: NativeDownloadEvent) => void) {
|
|
524
|
+
downloadEventHandler = handler;
|
|
525
|
+
}
|
|
526
|
+
|
|
353
527
|
// Per-view deferred resolvers for "view-ready" (OnAfterCreated).
|
|
354
528
|
const viewReadyResolvers = new Map<number, () => void>();
|
|
355
529
|
|
|
@@ -499,6 +673,47 @@ function registerNativeCallbacks(library: LoadedNativeLibrary) {
|
|
|
499
673
|
evaluateResultHandler?.(viewId, parsed);
|
|
500
674
|
break;
|
|
501
675
|
}
|
|
676
|
+
case "screenshot-result": {
|
|
677
|
+
const parsed = maybeParsePayload(payload) as NativeScreenshotResult;
|
|
678
|
+
screenshotResultHandler?.(viewId, parsed);
|
|
679
|
+
break;
|
|
680
|
+
}
|
|
681
|
+
case "accessibility-result": {
|
|
682
|
+
const parsed = maybeParsePayload(payload) as NativeAccessibilityResult;
|
|
683
|
+
accessibilityResultHandler?.(viewId, parsed);
|
|
684
|
+
break;
|
|
685
|
+
}
|
|
686
|
+
case "list-frames-result": {
|
|
687
|
+
const parsed = maybeParsePayload(payload) as NativeListFramesResult;
|
|
688
|
+
listFramesResultHandler?.(viewId, parsed);
|
|
689
|
+
break;
|
|
690
|
+
}
|
|
691
|
+
case "resolve-and-click-result": {
|
|
692
|
+
const parsed = maybeParsePayload(payload) as NativeResolveAndClickResult;
|
|
693
|
+
resolveAndClickResultHandler?.(viewId, parsed);
|
|
694
|
+
break;
|
|
695
|
+
}
|
|
696
|
+
case "download-event": {
|
|
697
|
+
const parsed = maybeParsePayload(payload) as NativeDownloadEvent;
|
|
698
|
+
downloadEventHandler?.(viewId, parsed);
|
|
699
|
+
buniteEventEmitter.emitEvent(
|
|
700
|
+
buniteEventEmitter.events.webview.downloadEvent(parsed),
|
|
701
|
+
viewId
|
|
702
|
+
);
|
|
703
|
+
break;
|
|
704
|
+
}
|
|
705
|
+
case "popup-requested": {
|
|
706
|
+
const parsed = maybeParsePayload(payload) as {
|
|
707
|
+
newSurfaceId: number;
|
|
708
|
+
url: string;
|
|
709
|
+
disposition: "tab" | "window" | "popup";
|
|
710
|
+
};
|
|
711
|
+
buniteEventEmitter.emitEvent(
|
|
712
|
+
buniteEventEmitter.events.webview.popupRequested(parsed),
|
|
713
|
+
viewId
|
|
714
|
+
);
|
|
715
|
+
break;
|
|
716
|
+
}
|
|
502
717
|
case "title-changed": {
|
|
503
718
|
const parsed = maybeParsePayload(payload) as { title: string };
|
|
504
719
|
buniteEventEmitter.emitEvent(
|
|
@@ -507,6 +722,53 @@ function registerNativeCallbacks(library: LoadedNativeLibrary) {
|
|
|
507
722
|
);
|
|
508
723
|
break;
|
|
509
724
|
}
|
|
725
|
+
case "load-start":
|
|
726
|
+
buniteEventEmitter.emitEvent(
|
|
727
|
+
buniteEventEmitter.events.webview.loadStart({ detail: payload }),
|
|
728
|
+
viewId
|
|
729
|
+
);
|
|
730
|
+
break;
|
|
731
|
+
case "load-finish":
|
|
732
|
+
buniteEventEmitter.emitEvent(
|
|
733
|
+
buniteEventEmitter.events.webview.loadFinish({ detail: payload }),
|
|
734
|
+
viewId
|
|
735
|
+
);
|
|
736
|
+
break;
|
|
737
|
+
case "load-fail": {
|
|
738
|
+
const parsed = maybeParsePayload(payload) as { url?: string; reason?: string };
|
|
739
|
+
buniteEventEmitter.emitEvent(
|
|
740
|
+
buniteEventEmitter.events.webview.loadFail({
|
|
741
|
+
url: parsed.url ?? "", reason: parsed.reason,
|
|
742
|
+
}),
|
|
743
|
+
viewId
|
|
744
|
+
);
|
|
745
|
+
break;
|
|
746
|
+
}
|
|
747
|
+
case "dialog": {
|
|
748
|
+
const parsed = maybeParsePayload(payload) as {
|
|
749
|
+
requestId: number;
|
|
750
|
+
kind: "alert" | "confirm" | "prompt" | "beforeunload";
|
|
751
|
+
message: string;
|
|
752
|
+
defaultPrompt?: string;
|
|
753
|
+
};
|
|
754
|
+
buniteEventEmitter.emitEvent(
|
|
755
|
+
buniteEventEmitter.events.webview.dialog(parsed),
|
|
756
|
+
viewId
|
|
757
|
+
);
|
|
758
|
+
break;
|
|
759
|
+
}
|
|
760
|
+
case "console-message": {
|
|
761
|
+
const parsed = maybeParsePayload(payload) as {
|
|
762
|
+
level: "log" | "warn" | "error" | "info" | "debug";
|
|
763
|
+
args: string[];
|
|
764
|
+
ts: number;
|
|
765
|
+
};
|
|
766
|
+
buniteEventEmitter.emitEvent(
|
|
767
|
+
buniteEventEmitter.events.webview.consoleMessage(parsed),
|
|
768
|
+
viewId
|
|
769
|
+
);
|
|
770
|
+
break;
|
|
771
|
+
}
|
|
510
772
|
}
|
|
511
773
|
},
|
|
512
774
|
{
|
|
@@ -656,7 +918,7 @@ export async function initNativeRuntime(
|
|
|
656
918
|
throw new Error(`bunite: failed to load native library at ${artifacts.nativeLibPath}.`);
|
|
657
919
|
}
|
|
658
920
|
|
|
659
|
-
const EXPECTED_ABI =
|
|
921
|
+
const EXPECTED_ABI = 11;
|
|
660
922
|
const nativeAbi = nativeLibrary.symbols.bunite_abi_version();
|
|
661
923
|
if (nativeAbi !== EXPECTED_ABI) {
|
|
662
924
|
throw new Error(
|
|
@@ -70,11 +70,16 @@ export function buildViewPreloadScript(options: {
|
|
|
70
70
|
}) {
|
|
71
71
|
const secretKeyBase64 = Buffer.from(options.secretKey).toString("base64");
|
|
72
72
|
|
|
73
|
-
// Per-view config — these globals are consumed by the pre-built runtime
|
|
73
|
+
// Per-view config — these globals are consumed by the pre-built runtime.
|
|
74
74
|
const config = `var __buniteWebviewId=${options.webviewId},__buniteRpcSocketPort=${options.rpcSocketPort},__buniteSecretKeyBase64=${JSON.stringify(secretKeyBase64)};`;
|
|
75
75
|
|
|
76
76
|
const runtime = getPreloadRuntime();
|
|
77
77
|
const customPreload = readCustomPreload(options.preload, options.appresRoot).trim();
|
|
78
78
|
|
|
79
|
-
|
|
79
|
+
// `;\n` between segments guards against ASI / token-boundary issues.
|
|
80
|
+
const inner = [config, runtime, customPreload].filter(Boolean).join(";\n");
|
|
81
|
+
// Catch top-level errors so a broken segment doesn't abort the surrounding
|
|
82
|
+
// native IIFE wrapper. Stashes the error on globalThis for programmatic
|
|
83
|
+
// inspection in addition to console.error.
|
|
84
|
+
return `try{${inner}\n}catch(e){try{globalThis.__bunitePreloadError=e}catch(_){}try{console.error("[bunite preload] failed:",e&&e.stack||e)}catch(_){}}`;
|
|
80
85
|
}
|