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
|
@@ -15,8 +15,7 @@
|
|
|
15
15
|
extern "C" {
|
|
16
16
|
#endif
|
|
17
17
|
|
|
18
|
-
/** ABI version. Bump on any breaking change to symbol set / signatures.
|
|
19
|
-
* v5 (2026-05): adds `bunite_view_evaluate` + `evaluate-result` webview event. */
|
|
18
|
+
/** ABI version. Bump on any breaking change to symbol set / signatures. */
|
|
20
19
|
BUNITE_EXPORT int32_t bunite_abi_version(void);
|
|
21
20
|
BUNITE_EXPORT void bunite_set_log_level(int32_t level);
|
|
22
21
|
BUNITE_EXPORT bool bunite_init(
|
|
@@ -124,6 +123,205 @@ BUNITE_EXPORT void bunite_view_set_anchor(uint32_t view_id, int mode, double ins
|
|
|
124
123
|
BUNITE_EXPORT void bunite_view_go_back(uint32_t view_id);
|
|
125
124
|
BUNITE_EXPORT void bunite_view_reload(uint32_t view_id);
|
|
126
125
|
BUNITE_EXPORT void bunite_view_remove(uint32_t view_id);
|
|
126
|
+
|
|
127
|
+
/* Input dispatch (ABI v6). Coordinates are CSS px, viewport-relative (TS
|
|
128
|
+
* normalizes devicePixelRatio + container offset). Modifier bitmask is
|
|
129
|
+
* Alt=1, Ctrl=2, Meta=4, Shift=8. Backends translate to their native form.
|
|
130
|
+
* No result envelope — `nativeInputTrusted` capability indicates DOM trust. */
|
|
131
|
+
BUNITE_EXPORT void bunite_view_click(
|
|
132
|
+
uint32_t view_id,
|
|
133
|
+
double x,
|
|
134
|
+
double y,
|
|
135
|
+
int32_t button, /* 0=left, 1=middle, 2=right */
|
|
136
|
+
int32_t click_count, /* >=1 */
|
|
137
|
+
uint32_t modifiers
|
|
138
|
+
);
|
|
139
|
+
/** Type UTF-8 text. Each codepoint becomes a CHAR / insertText event — no IME composition. */
|
|
140
|
+
BUNITE_EXPORT void bunite_view_type(uint32_t view_id, const char* text);
|
|
141
|
+
/** Press a key: down + (optional) char + up.
|
|
142
|
+
* `windows_vk_code` is Win32 VK_* for CEF / WebView2 CDP path.
|
|
143
|
+
* `mac_key_code` is the Quartz Event Services hardware key code (kVK_*) — separate
|
|
144
|
+
* from Win VK because DOM `KeyboardEvent.code` is derived from this on WebKit.
|
|
145
|
+
* `key` / `code` are DOM `KeyboardEvent.key` / `.code` strings, passed to CDP so
|
|
146
|
+
* the page sees the correct values. `character` is UTF-8 for the CHAR event;
|
|
147
|
+
* empty = skip char. 0 vk codes = skip the virtual-key down/up.
|
|
148
|
+
* `action`: 0=down only, 1=up only, 2=both (default). For Playwright-style
|
|
149
|
+
* modifier-held wrap (keydown → click → keyup). CHAR follows Playwright rule:
|
|
150
|
+
* emitted with down only when character is non-empty.
|
|
151
|
+
* `extended`: Win scancode 0xE0 prefix — Numpad-Enter AND nav-cluster
|
|
152
|
+
* (Arrow/Home/End/Insert/Delete/PageUp/PageDown/Meta/ContextMenu). Drives CEF
|
|
153
|
+
* `native_key_code`. Other backends ignore.
|
|
154
|
+
* `location`: DOM `KeyboardEvent.location` (0=standard, 1=left mod, 2=right
|
|
155
|
+
* mod, 3=numpad). WV2 CDP forwards as `location`. Most extended keys are
|
|
156
|
+
* location 0 — only NumpadEnter is location 3 here. */
|
|
157
|
+
BUNITE_EXPORT void bunite_view_press(
|
|
158
|
+
uint32_t view_id,
|
|
159
|
+
int32_t windows_vk_code,
|
|
160
|
+
int32_t mac_key_code,
|
|
161
|
+
const char* key,
|
|
162
|
+
const char* code,
|
|
163
|
+
const char* character,
|
|
164
|
+
uint32_t modifiers,
|
|
165
|
+
int32_t action,
|
|
166
|
+
bool extended,
|
|
167
|
+
int32_t location
|
|
168
|
+
);
|
|
169
|
+
/** Scroll at (x, y). dx/dy in CSS px; positive = right/down. */
|
|
170
|
+
BUNITE_EXPORT void bunite_view_scroll(
|
|
171
|
+
uint32_t view_id,
|
|
172
|
+
double dx,
|
|
173
|
+
double dy,
|
|
174
|
+
double x,
|
|
175
|
+
double y,
|
|
176
|
+
uint32_t modifiers
|
|
177
|
+
);
|
|
178
|
+
|
|
179
|
+
/** Raw mouse primitive. `action` 0=move, 1=down, 2=up. `button` 0=left, 1=middle,
|
|
180
|
+
* 2=right (ignored for move). Coordinates are CSS px viewport-relative. Drag is
|
|
181
|
+
* composed = down → move(s) → up. Backends without input support (linux GTK)
|
|
182
|
+
* treat as no-op. `modifiers` per-call atomic — no sticky state. */
|
|
183
|
+
BUNITE_EXPORT void bunite_view_mouse(
|
|
184
|
+
uint32_t view_id,
|
|
185
|
+
int32_t action,
|
|
186
|
+
double x,
|
|
187
|
+
double y,
|
|
188
|
+
int32_t button,
|
|
189
|
+
uint32_t modifiers
|
|
190
|
+
);
|
|
191
|
+
|
|
192
|
+
/** Respond to a page-initiated modal dialog previously announced via the
|
|
193
|
+
* webview event channel as `dialog` (`{requestId, kind, message, defaultPrompt?}`).
|
|
194
|
+
* `accept` decides confirm/beforeunload outcome and gates whether `text` is
|
|
195
|
+
* applied to `prompt`. Backends release the held page execution. */
|
|
196
|
+
BUNITE_EXPORT void bunite_view_respond_dialog(
|
|
197
|
+
uint32_t view_id,
|
|
198
|
+
uint32_t request_id,
|
|
199
|
+
bool accept,
|
|
200
|
+
const char* text
|
|
201
|
+
);
|
|
202
|
+
|
|
203
|
+
/** Per-view automation capability bitset. Two categories: method gate
|
|
204
|
+
* (bit=false → method must not be called) and property advertise (bit=false
|
|
205
|
+
* → method runs, property degrades). See `.agents/architecture.md`. */
|
|
206
|
+
enum BuniteCapBit {
|
|
207
|
+
/* method gate */
|
|
208
|
+
BUNITE_CAP_EVALUATE = 1u << 0,
|
|
209
|
+
BUNITE_CAP_SURFACE_EVENTS = 1u << 2,
|
|
210
|
+
BUNITE_CAP_CLICK = 1u << 4,
|
|
211
|
+
BUNITE_CAP_TYPE = 1u << 5,
|
|
212
|
+
BUNITE_CAP_PRESS = 1u << 6,
|
|
213
|
+
BUNITE_CAP_SCROLL = 1u << 7,
|
|
214
|
+
BUNITE_CAP_SCREENSHOT = 1u << 8,
|
|
215
|
+
BUNITE_CAP_MOUSE = 1u << 11,
|
|
216
|
+
BUNITE_CAP_DIALOGS = 1u << 12,
|
|
217
|
+
BUNITE_CAP_CONSOLE = 1u << 13,
|
|
218
|
+
BUNITE_CAP_AX = 1u << 15,
|
|
219
|
+
BUNITE_CAP_BOUNDING_RECT = 1u << 16,
|
|
220
|
+
BUNITE_CAP_FRAMES = 1u << 17,
|
|
221
|
+
BUNITE_CAP_DOWNLOADS = 1u << 18,
|
|
222
|
+
BUNITE_CAP_POPUPS = 1u << 19,
|
|
223
|
+
BUNITE_CAP_RESOLVE_AND_CLICK = 1u << 20,
|
|
224
|
+
|
|
225
|
+
/* property advertise */
|
|
226
|
+
BUNITE_CAP_CROSS_ORIGIN_EVAL = 1u << 1,
|
|
227
|
+
BUNITE_CAP_NATIVE_INPUT_TRUSTED = 1u << 3, /* click/type/press/mouse isTrusted */
|
|
228
|
+
BUNITE_CAP_FORMAT_PNG = 1u << 9,
|
|
229
|
+
BUNITE_CAP_FORMAT_JPEG = 1u << 10,
|
|
230
|
+
};
|
|
231
|
+
BUNITE_EXPORT uint32_t bunite_view_capabilities(uint32_t view_id);
|
|
232
|
+
|
|
233
|
+
/** Capture the visible viewport as a PNG/JPEG image. Async — result reported
|
|
234
|
+
* via the webview event handler as `screenshot-result` with payload
|
|
235
|
+
* { requestId, ok: true, format, mime, dataBase64 }
|
|
236
|
+
* { requestId, ok: false, code, message }
|
|
237
|
+
* Error codes: `not_supported`, `runtime_error`, `timeout`, `black_frame` (CEF
|
|
238
|
+
* compositor surface unreachable). `quality` is 0–100 for JPEG only — ignored
|
|
239
|
+
* for PNG, and ignored entirely on WebView2 (`CapturePreview` has no quality
|
|
240
|
+
* parameter; output uses Edge's default ~80). `dataBase64` is base64-encoded
|
|
241
|
+
* image bytes; TS layer decodes. */
|
|
242
|
+
BUNITE_EXPORT void bunite_view_screenshot(
|
|
243
|
+
uint32_t view_id,
|
|
244
|
+
uint32_t request_id,
|
|
245
|
+
const char* format,
|
|
246
|
+
int32_t quality
|
|
247
|
+
);
|
|
248
|
+
|
|
249
|
+
/** Snapshot the accessibility tree (CDP `Accessibility.getFullAXTree`). Async —
|
|
250
|
+
* result reported via webview event handler as `accessibility-result` payload
|
|
251
|
+
* { requestId, ok: true, tree: {nodes: [<CDP AXNode flat list>]} }
|
|
252
|
+
* { requestId, ok: false, code, message }
|
|
253
|
+
* TS builds the nested tree from `childIds`. mac/linux always emit
|
|
254
|
+
* `not_supported` (no public ax tree API). `interesting_only` is reserved and
|
|
255
|
+
* currently unused on the native side (filter is TS-side). */
|
|
256
|
+
BUNITE_EXPORT void bunite_view_accessibility_snapshot(
|
|
257
|
+
uint32_t view_id,
|
|
258
|
+
uint32_t request_id,
|
|
259
|
+
int32_t interesting_only
|
|
260
|
+
);
|
|
261
|
+
|
|
262
|
+
/** Enumerate frames in the view. Async — result reported via webview event
|
|
263
|
+
* `list-frames-result` payload
|
|
264
|
+
* { requestId, ok: true, frames: [{frameId, parentFrameId, origin, url, name?}] }
|
|
265
|
+
* { requestId, ok: false, code, message }
|
|
266
|
+
* Codes: `not_supported`, `runtime_error`. mac/linux emit `not_supported`. */
|
|
267
|
+
BUNITE_EXPORT void bunite_view_list_frames(uint32_t view_id, uint32_t request_id);
|
|
268
|
+
|
|
269
|
+
/** Evaluate `script` in the target frame's isolated world (CDP
|
|
270
|
+
* `Page.createIsolatedWorld` + `Runtime.evaluate`). Page main-world JS
|
|
271
|
+
* variables are NOT visible; DOM access works. Result reused via the
|
|
272
|
+
* existing `evaluate-result` event. `frame_id` empty/null delegates to
|
|
273
|
+
* `bunite_view_evaluate` (main frame, main world). */
|
|
274
|
+
BUNITE_EXPORT void bunite_view_evaluate_in_frame(
|
|
275
|
+
uint32_t view_id,
|
|
276
|
+
uint32_t request_id,
|
|
277
|
+
const char* script,
|
|
278
|
+
const char* frame_id
|
|
279
|
+
);
|
|
280
|
+
|
|
281
|
+
/** Atomic selector resolve + native click. Async via `resolve-and-click-result`:
|
|
282
|
+
* { requestId, ok: true, rect, isTrustedEvent }
|
|
283
|
+
* { requestId, ok: false, code, message }
|
|
284
|
+
* Codes: not_found / not_visible / runtime_error / cross_origin / not_supported.
|
|
285
|
+
* `frame_id` non-empty selects a same-origin iframe (rect viewport-normalized);
|
|
286
|
+
* cross-origin → `cross_origin`, mac/linux → `not_supported`. scrollIntoView is
|
|
287
|
+
* automatic. `isTrustedEvent` is empirical per backend; CEF/WV2 CDP path and
|
|
288
|
+
* mac NSEvent direct dispatch all produce trusted events (all `true`). */
|
|
289
|
+
BUNITE_EXPORT void bunite_view_resolve_and_click(
|
|
290
|
+
uint32_t view_id,
|
|
291
|
+
uint32_t request_id,
|
|
292
|
+
const char* selector,
|
|
293
|
+
const char* frame_id,
|
|
294
|
+
int32_t button,
|
|
295
|
+
int32_t click_count,
|
|
296
|
+
uint32_t modifiers
|
|
297
|
+
);
|
|
298
|
+
|
|
299
|
+
/** Set per-view download policy. `policy`: 0=auto (allow + emit lifecycle),
|
|
300
|
+
* 1=ask (not implemented for v10, treated as block), 2=block (default).
|
|
301
|
+
* `download_dir` (utf-8) optionally overrides backend default save dir.
|
|
302
|
+
* Lifecycle events emit as `download-event` payloads
|
|
303
|
+
* { kind: "started"|"progress"|"completed"|"failed"|"blocked", id, ...fields }
|
|
304
|
+
* See `DownloadEvent` (TS) for the per-kind field set. */
|
|
305
|
+
BUNITE_EXPORT void bunite_view_set_download_policy(
|
|
306
|
+
uint32_t view_id,
|
|
307
|
+
int32_t policy,
|
|
308
|
+
const char* download_dir
|
|
309
|
+
);
|
|
310
|
+
|
|
311
|
+
/** Adopt a popup-minted view. Native must have previously emitted a
|
|
312
|
+
* `popup-requested` event (carrying `newSurfaceId`); host calls this to attach
|
|
313
|
+
* the pre-minted view to the target window + bounds. `host_window_id` is the
|
|
314
|
+
* destination `WindowHost.id`. */
|
|
315
|
+
BUNITE_EXPORT void bunite_view_popup_accept(
|
|
316
|
+
uint32_t new_view_id,
|
|
317
|
+
uint32_t host_window_id,
|
|
318
|
+
double x, double y, double w, double h
|
|
319
|
+
);
|
|
320
|
+
|
|
321
|
+
/** Discard a popup-minted view that wasn't adopted (or that host wants to
|
|
322
|
+
* reject). Native destroys the controller/browser. Idempotent. */
|
|
323
|
+
BUNITE_EXPORT void bunite_view_popup_dismiss(uint32_t new_view_id);
|
|
324
|
+
|
|
127
325
|
BUNITE_EXPORT void bunite_view_open_devtools(uint32_t view_id);
|
|
128
326
|
BUNITE_EXPORT void bunite_view_close_devtools(uint32_t view_id);
|
|
129
327
|
BUNITE_EXPORT void bunite_view_toggle_devtools(uint32_t view_id);
|
|
@@ -85,8 +85,12 @@ class BuniteCefClient
|
|
|
85
85
|
public CefRequestHandler,
|
|
86
86
|
public CefResourceRequestHandler,
|
|
87
87
|
public CefPermissionHandler,
|
|
88
|
-
public CefDisplayHandler
|
|
88
|
+
public CefDisplayHandler,
|
|
89
|
+
public CefJSDialogHandler,
|
|
90
|
+
public CefDownloadHandler {
|
|
89
91
|
public:
|
|
92
|
+
// BuniteCefClient is constructed 1:1 with a `ViewHost*`; `last_title_` is
|
|
93
|
+
// therefore per-view. OnTitleChange runs on the CEF UI thread (single).
|
|
90
94
|
explicit BuniteCefClient(ViewHost* view)
|
|
91
95
|
: view_(view) {}
|
|
92
96
|
|
|
@@ -95,6 +99,113 @@ public:
|
|
|
95
99
|
CefRefPtr<CefRequestHandler> GetRequestHandler() override { return this; }
|
|
96
100
|
CefRefPtr<CefPermissionHandler> GetPermissionHandler() override { return this; }
|
|
97
101
|
CefRefPtr<CefDisplayHandler> GetDisplayHandler() override { return this; }
|
|
102
|
+
CefRefPtr<CefJSDialogHandler> GetJSDialogHandler() override { return this; }
|
|
103
|
+
CefRefPtr<CefDownloadHandler> GetDownloadHandler() override { return this; }
|
|
104
|
+
|
|
105
|
+
bool OnBeforeDownload(CefRefPtr<CefBrowser>, CefRefPtr<CefDownloadItem> item,
|
|
106
|
+
const CefString& suggested_name,
|
|
107
|
+
CefRefPtr<CefBeforeDownloadCallback> callback) override {
|
|
108
|
+
CEF_REQUIRE_UI_THREAD();
|
|
109
|
+
int32_t policy = view_->download_policy.load();
|
|
110
|
+
std::string id = "cef-" + std::to_string(item->GetId());
|
|
111
|
+
std::string url = item->GetURL().ToString();
|
|
112
|
+
std::string mime = item->GetMimeType().ToString();
|
|
113
|
+
int64_t total = item->GetTotalBytes();
|
|
114
|
+
std::string suggested = suggested_name.ToString();
|
|
115
|
+
// Only policy=0 (auto) allows. `ask` (1) falls back to block — distinguished
|
|
116
|
+
// via blocked.reason so callers can detect the unsupported policy path.
|
|
117
|
+
if (policy != 0) {
|
|
118
|
+
const char* reason = (policy == 1) ? "ask-not-implemented" : "host-policy";
|
|
119
|
+
std::string payload = "{\"kind\":\"blocked\",\"id\":\"" + id +
|
|
120
|
+
"\",\"url\":\"" + bunite_win::escapeJsonString(url) +
|
|
121
|
+
"\",\"reason\":\"" + reason + "\"}";
|
|
122
|
+
bunite_win::emitWebviewEvent(view_->id, "download-event", payload);
|
|
123
|
+
return true; // not calling callback->Continue → cancels.
|
|
124
|
+
}
|
|
125
|
+
// auto: build target path. If host set download_dir, use it; else CEF defaults to user Downloads.
|
|
126
|
+
std::string target = view_->download_dir;
|
|
127
|
+
if (!target.empty()) {
|
|
128
|
+
if (target.back() != '\\' && target.back() != '/') target.push_back('\\');
|
|
129
|
+
target += suggested;
|
|
130
|
+
}
|
|
131
|
+
callback->Continue(target, false); // false = no Save-As dialog.
|
|
132
|
+
std::string payload = "{\"kind\":\"started\",\"id\":\"" + id +
|
|
133
|
+
"\",\"url\":\"" + bunite_win::escapeJsonString(url) +
|
|
134
|
+
"\",\"suggestedFilename\":\"" + bunite_win::escapeJsonString(suggested) +
|
|
135
|
+
"\",\"mimeType\":\"" + bunite_win::escapeJsonString(mime) + "\"";
|
|
136
|
+
if (total > 0) payload += ",\"sizeBytes\":" + std::to_string(total);
|
|
137
|
+
payload += "}";
|
|
138
|
+
bunite_win::emitWebviewEvent(view_->id, "download-event", payload);
|
|
139
|
+
return true;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
void OnDownloadUpdated(CefRefPtr<CefBrowser>, CefRefPtr<CefDownloadItem> item,
|
|
143
|
+
CefRefPtr<CefDownloadItemCallback> /*callback*/) override {
|
|
144
|
+
CEF_REQUIRE_UI_THREAD();
|
|
145
|
+
std::string id = "cef-" + std::to_string(item->GetId());
|
|
146
|
+
if (item->IsComplete()) {
|
|
147
|
+
std::string path = item->GetFullPath().ToString();
|
|
148
|
+
std::string payload = "{\"kind\":\"completed\",\"id\":\"" + id +
|
|
149
|
+
"\",\"localPath\":\"" + bunite_win::escapeJsonString(path) + "\"}";
|
|
150
|
+
bunite_win::emitWebviewEvent(view_->id, "download-event", payload);
|
|
151
|
+
} else if (item->IsCanceled()) {
|
|
152
|
+
std::string payload = "{\"kind\":\"failed\",\"id\":\"" + id +
|
|
153
|
+
"\",\"reason\":\"canceled\"}";
|
|
154
|
+
bunite_win::emitWebviewEvent(view_->id, "download-event", payload);
|
|
155
|
+
} else if (item->IsInProgress()) {
|
|
156
|
+
int64_t rec = item->GetReceivedBytes();
|
|
157
|
+
int64_t tot = item->GetTotalBytes();
|
|
158
|
+
std::string payload = "{\"kind\":\"progress\",\"id\":\"" + id +
|
|
159
|
+
"\",\"receivedBytes\":" + std::to_string(rec);
|
|
160
|
+
if (tot > 0) payload += ",\"totalBytes\":" + std::to_string(tot);
|
|
161
|
+
payload += "}";
|
|
162
|
+
bunite_win::emitWebviewEvent(view_->id, "download-event", payload);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
bool OnJSDialog(CefRefPtr<CefBrowser>, const CefString& /*origin_url*/,
|
|
167
|
+
JSDialogType dialog_type, const CefString& message_text,
|
|
168
|
+
const CefString& default_prompt_text,
|
|
169
|
+
CefRefPtr<CefJSDialogCallback> callback,
|
|
170
|
+
bool& suppress_message) override {
|
|
171
|
+
CEF_REQUIRE_UI_THREAD();
|
|
172
|
+
const char* kind = (dialog_type == JSDIALOGTYPE_ALERT) ? "alert"
|
|
173
|
+
: (dialog_type == JSDIALOGTYPE_CONFIRM) ? "confirm" : "prompt";
|
|
174
|
+
const uint32_t rid = view_->next_dialog_request_id++;
|
|
175
|
+
view_->pending_dialogs[rid] = callback;
|
|
176
|
+
BUNITE_INFO("cef/dialog: OnJSDialog view=%u kind=%s rid=%u", view_->id, kind, rid);
|
|
177
|
+
// CEF asserts !suppress_message when return=true (custom dialog path).
|
|
178
|
+
suppress_message = false;
|
|
179
|
+
std::string payload = "{\"requestId\":" + std::to_string(rid) +
|
|
180
|
+
",\"kind\":\"" + kind +
|
|
181
|
+
"\",\"message\":\"" + bunite_win::escapeJsonString(message_text.ToString()) + "\"";
|
|
182
|
+
if (dialog_type == JSDIALOGTYPE_PROMPT) {
|
|
183
|
+
payload += ",\"defaultPrompt\":\"" + bunite_win::escapeJsonString(default_prompt_text.ToString()) + "\"";
|
|
184
|
+
}
|
|
185
|
+
payload += "}";
|
|
186
|
+
bunite_win::emitWebviewEvent(view_->id, "dialog", payload);
|
|
187
|
+
return true; // handled — CEF will not show its own UI.
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
bool OnBeforeUnloadDialog(CefRefPtr<CefBrowser>, const CefString& message_text,
|
|
191
|
+
bool /*is_reload*/,
|
|
192
|
+
CefRefPtr<CefJSDialogCallback> callback) override {
|
|
193
|
+
CEF_REQUIRE_UI_THREAD();
|
|
194
|
+
const uint32_t rid = view_->next_dialog_request_id++;
|
|
195
|
+
view_->pending_dialogs[rid] = callback;
|
|
196
|
+
std::string payload = "{\"requestId\":" + std::to_string(rid) +
|
|
197
|
+
",\"kind\":\"beforeunload\",\"message\":\"" +
|
|
198
|
+
bunite_win::escapeJsonString(message_text.ToString()) + "\"}";
|
|
199
|
+
bunite_win::emitWebviewEvent(view_->id, "dialog", payload);
|
|
200
|
+
return true;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
void OnResetDialogState(CefRefPtr<CefBrowser>) override {
|
|
204
|
+
CEF_REQUIRE_UI_THREAD();
|
|
205
|
+
// Tab navigation / reload clears any stuck dialogs. Drop the callbacks —
|
|
206
|
+
// CEF won't deliver them anymore, and the page is already moving on.
|
|
207
|
+
view_->pending_dialogs.clear();
|
|
208
|
+
}
|
|
98
209
|
|
|
99
210
|
bool OnProcessMessageReceived(
|
|
100
211
|
CefRefPtr<CefBrowser> /*browser*/,
|
|
@@ -122,10 +233,25 @@ public:
|
|
|
122
233
|
|
|
123
234
|
void OnTitleChange(CefRefPtr<CefBrowser>, const CefString& title) override {
|
|
124
235
|
CEF_REQUIRE_UI_THREAD();
|
|
125
|
-
std::string
|
|
236
|
+
std::string s = title.ToString();
|
|
237
|
+
// Filter the transients CEF emits during initial / mid-load (blank doc
|
|
238
|
+
// placeholder, momentary URL-as-title) and dedup on the last emitted value.
|
|
239
|
+
if (s.empty()) return;
|
|
240
|
+
if (s == last_title_) return;
|
|
241
|
+
last_title_ = s;
|
|
242
|
+
std::string payload = "{\"title\":\"" + bunite_win::escapeJsonString(s) + "\"}";
|
|
126
243
|
bunite_win::emitWebviewEvent(view_->id, "title-changed", payload);
|
|
127
244
|
}
|
|
128
245
|
|
|
246
|
+
void OnAddressChange(CefRefPtr<CefBrowser>, CefRefPtr<CefFrame> frame,
|
|
247
|
+
const CefString& url) override {
|
|
248
|
+
CEF_REQUIRE_UI_THREAD();
|
|
249
|
+
if (!frame->IsMain()) return;
|
|
250
|
+
// URL commit point — parity with WV2 SourceChanged / mac didCommitNavigation.
|
|
251
|
+
// Distinct from load-finish (which is OnLoadEnd).
|
|
252
|
+
bunite_win::emitWebviewEvent(view_->id, "did-navigate", url.ToString());
|
|
253
|
+
}
|
|
254
|
+
|
|
129
255
|
void OnBeforeDevToolsPopup(
|
|
130
256
|
CefRefPtr<CefBrowser>,
|
|
131
257
|
CefWindowInfo&,
|
|
@@ -153,6 +279,7 @@ public:
|
|
|
153
279
|
void OnAfterCreated(CefRefPtr<CefBrowser> browser) override {
|
|
154
280
|
CEF_REQUIRE_UI_THREAD();
|
|
155
281
|
view_->browser = browser;
|
|
282
|
+
bunite_win::registerCdpObserverForView(view_);
|
|
156
283
|
{
|
|
157
284
|
std::lock_guard<std::mutex> lock(g_runtime.object_mutex);
|
|
158
285
|
g_runtime.browser_to_view_id[browser->GetIdentifier()] = view_->id;
|
|
@@ -192,6 +319,19 @@ public:
|
|
|
192
319
|
}
|
|
193
320
|
|
|
194
321
|
bunite_win::emitWebviewEvent(view_->id, "view-ready");
|
|
322
|
+
|
|
323
|
+
if (view_->is_popup_pending) {
|
|
324
|
+
if (view_->popup_dismiss_requested) {
|
|
325
|
+
view_->closing.store(true);
|
|
326
|
+
browser->GetHost()->CloseBrowser(true);
|
|
327
|
+
return;
|
|
328
|
+
}
|
|
329
|
+
if (view_->pending_popup_accept) {
|
|
330
|
+
auto p = *view_->pending_popup_accept;
|
|
331
|
+
view_->pending_popup_accept.reset();
|
|
332
|
+
bunite_win::applyPopupAccept(view_, p.host_window_id, p.x, p.y, p.w, p.h);
|
|
333
|
+
}
|
|
334
|
+
}
|
|
195
335
|
}
|
|
196
336
|
|
|
197
337
|
bool DoClose(CefRefPtr<CefBrowser>) override {
|
|
@@ -255,19 +395,39 @@ public:
|
|
|
255
395
|
CefLifeSpanHandler::WindowOpenDisposition,
|
|
256
396
|
bool,
|
|
257
397
|
const CefPopupFeatures&,
|
|
258
|
-
CefWindowInfo
|
|
259
|
-
CefRefPtr<CefClient
|
|
398
|
+
CefWindowInfo& window_info,
|
|
399
|
+
CefRefPtr<CefClient>& client,
|
|
260
400
|
CefBrowserSettings&,
|
|
261
401
|
CefRefPtr<CefDictionaryValue>&,
|
|
262
402
|
bool*
|
|
263
403
|
) override {
|
|
264
404
|
CEF_REQUIRE_UI_THREAD();
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
405
|
+
// Popup IDs live in the upper u32 half; TS allocator stays below.
|
|
406
|
+
static std::atomic<uint32_t> g_popup_seq{0x80000000u};
|
|
407
|
+
uint32_t new_view_id = g_popup_seq.fetch_add(1);
|
|
408
|
+
auto* popup = new ViewHost();
|
|
409
|
+
popup->id = new_view_id;
|
|
410
|
+
popup->window = nullptr;
|
|
411
|
+
popup->is_popup_pending = true;
|
|
412
|
+
{
|
|
413
|
+
std::lock_guard<std::mutex> lock(g_runtime.object_mutex);
|
|
414
|
+
g_runtime.views_by_id[new_view_id] = popup;
|
|
415
|
+
}
|
|
416
|
+
// Initial parent = runtime message window; host adopts and reparents later.
|
|
417
|
+
window_info.SetAsChild(g_runtime.message_window, CefRect{0, 0, 0, 0});
|
|
418
|
+
window_info.style = WS_CHILD;
|
|
419
|
+
client = new BuniteCefClient(popup);
|
|
420
|
+
std::string payload = "{\"newSurfaceId\":" + std::to_string(new_view_id) +
|
|
421
|
+
",\"url\":\"" + bunite_win::escapeJsonString(target_url.ToString()) +
|
|
422
|
+
"\",\"disposition\":\"popup\"}";
|
|
423
|
+
bunite_win::emitWebviewEvent(view_->id, "popup-requested", payload);
|
|
424
|
+
return false; // allow CEF to create the popup browser.
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
void OnLoadStart(CefRefPtr<CefBrowser>, CefRefPtr<CefFrame> frame, TransitionType) override {
|
|
428
|
+
CEF_REQUIRE_UI_THREAD();
|
|
429
|
+
if (!frame->IsMain()) return;
|
|
430
|
+
bunite_win::emitWebviewEvent(view_->id, "load-start", frame->GetURL().ToString());
|
|
271
431
|
}
|
|
272
432
|
|
|
273
433
|
void OnLoadEnd(CefRefPtr<CefBrowser>, CefRefPtr<CefFrame> frame, int) override {
|
|
@@ -277,10 +437,24 @@ public:
|
|
|
277
437
|
}
|
|
278
438
|
|
|
279
439
|
const std::string url = frame->GetURL().ToString();
|
|
280
|
-
bunite_win::emitWebviewEvent(view_->id, "
|
|
440
|
+
bunite_win::emitWebviewEvent(view_->id, "load-finish", url);
|
|
281
441
|
bunite_win::emitWebviewEvent(view_->id, "dom-ready", url);
|
|
282
442
|
}
|
|
283
443
|
|
|
444
|
+
void OnLoadError(CefRefPtr<CefBrowser>, CefRefPtr<CefFrame> frame,
|
|
445
|
+
ErrorCode errorCode, const CefString& errorText, const CefString& failedUrl) override {
|
|
446
|
+
CEF_REQUIRE_UI_THREAD();
|
|
447
|
+
if (!frame->IsMain()) return;
|
|
448
|
+
// ERR_ABORTED fires on user-initiated navigation cancellation — not a
|
|
449
|
+
// failure from the consumer's perspective. Filter to align with WV2.
|
|
450
|
+
if (errorCode == ERR_ABORTED) return;
|
|
451
|
+
std::string reason = errorText.ToString();
|
|
452
|
+
if (reason.empty()) reason = "ERR_" + std::to_string(static_cast<int>(errorCode));
|
|
453
|
+
std::string payload = "{\"url\":\"" + bunite_win::escapeJsonString(failedUrl.ToString()) +
|
|
454
|
+
"\",\"reason\":\"" + bunite_win::escapeJsonString(reason) + "\"}";
|
|
455
|
+
bunite_win::emitWebviewEvent(view_->id, "load-fail", payload);
|
|
456
|
+
}
|
|
457
|
+
|
|
284
458
|
bool OnShowPermissionPrompt(
|
|
285
459
|
CefRefPtr<CefBrowser>,
|
|
286
460
|
uint64_t,
|
|
@@ -341,6 +515,7 @@ public:
|
|
|
341
515
|
|
|
342
516
|
private:
|
|
343
517
|
ViewHost* view_;
|
|
518
|
+
std::string last_title_;
|
|
344
519
|
|
|
345
520
|
IMPLEMENT_REFCOUNTING(BuniteCefClient);
|
|
346
521
|
};
|