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.
Files changed (34) hide show
  1. package/package.json +4 -4
  2. package/src/host/core/App.ts +17 -1
  3. package/src/host/core/BrowserView.ts +197 -28
  4. package/src/host/core/SurfaceBrowserIPC.ts +44 -3
  5. package/src/host/core/SurfaceManager.ts +260 -28
  6. package/src/host/core/SurfaceRegistry.ts +9 -1
  7. package/src/host/core/inputDispatch.ts +147 -0
  8. package/src/host/events/webviewEvents.ts +8 -1
  9. package/src/host/native.ts +124 -1
  10. package/src/native/linux/bunite_linux_ffi.cpp +223 -6
  11. package/src/native/linux/bunite_linux_internal.h +6 -0
  12. package/src/native/linux/bunite_linux_runtime.cpp +1 -1
  13. package/src/native/linux/bunite_linux_utils.cpp +2 -2
  14. package/src/native/linux/bunite_linux_view.cpp +85 -0
  15. package/src/native/mac/bunite_mac_ffi.mm +356 -8
  16. package/src/native/mac/bunite_mac_internal.h +6 -0
  17. package/src/native/mac/bunite_mac_utils.mm +2 -2
  18. package/src/native/mac/bunite_mac_view.mm +144 -2
  19. package/src/native/shared/ffi_exports.h +135 -0
  20. package/src/native/win/native_host_cef.cpp +86 -3
  21. package/src/native/win/native_host_ffi.cpp +378 -1
  22. package/src/native/win/native_host_internal.h +13 -0
  23. package/src/native/win/native_host_utils.cpp +2 -1
  24. package/src/native/win/process_helper_win.cpp +54 -27
  25. package/src/native/win-webview2/bunite_webview2_ffi.cpp +303 -9
  26. package/src/native/win-webview2/webview2_internal.h +11 -0
  27. package/src/native/win-webview2/webview2_runtime.cpp +128 -12
  28. package/src/native/win-webview2/webview2_utils.cpp +30 -12
  29. package/src/preload/runtime.built.js +1 -1
  30. package/src/preload/runtime.ts +97 -0
  31. package/src/rpc/framework.ts +173 -4
  32. package/src/rpc/index.ts +21 -0
  33. package/src/webview/native.ts +126 -25
  34. package/src/webview/polyfill.ts +196 -12
@@ -16,6 +16,27 @@ extern "C" {
16
16
  #endif
17
17
 
18
18
  /** ABI version. Bump on any breaking change to symbol set / signatures.
19
+ * v9 (2026-05): adds `bunite_view_mouse` (move/down/up primitives for drag &
20
+ * hover) + `bunite_view_respond_dialog`. Webview event names
21
+ * expand to include `dialog` (alert/confirm/prompt/beforeunload)
22
+ * and `console-message` (the latter is RPC-pushed by preload,
23
+ * not emitted by native — listed here for the host-side event
24
+ * channel completeness). Capability bits add `MOUSE` (1<<11),
25
+ * `DIALOGS` (1<<12), `CONSOLE` (1<<13). `NATIVE_INPUT_TRUSTED`
26
+ * meaning stretches to include `mouse` (click/type/press/mouse
27
+ * all produce isTrusted=true on supporting backends; scroll and
28
+ * screenshot remain outside that guarantee).
29
+ * v8 (2026-05): `bunite_view_press` gains `action` (down/up/both), `extended`
30
+ * (Win 0xE0 scancode prefix), `location` (DOM KeyboardEvent
31
+ * location, 0/1/2/3) params. Webview event names expand to
32
+ * include `load-start` / `load-finish` / `load-fail`. Capability
33
+ * bit 2 renamed `TITLE_CHANGED` → `SURFACE_EVENTS` — value
34
+ * unchanged but semantic is now "unified surfaceEvents stream
35
+ * supported". A surface fires `load-finish` OR `load-fail` per
36
+ * navigation, never both.
37
+ * v7 (2026-05): adds `bunite_view_screenshot` + `bunite_view_capabilities`
38
+ * + capability bitset (`BuniteCapBit`).
39
+ * v6 (2026-05): adds input dispatch — `bunite_view_click/type/press/scroll`.
19
40
  * v5 (2026-05): adds `bunite_view_evaluate` + `evaluate-result` webview event. */
20
41
  BUNITE_EXPORT int32_t bunite_abi_version(void);
21
42
  BUNITE_EXPORT void bunite_set_log_level(int32_t level);
@@ -124,6 +145,120 @@ BUNITE_EXPORT void bunite_view_set_anchor(uint32_t view_id, int mode, double ins
124
145
  BUNITE_EXPORT void bunite_view_go_back(uint32_t view_id);
125
146
  BUNITE_EXPORT void bunite_view_reload(uint32_t view_id);
126
147
  BUNITE_EXPORT void bunite_view_remove(uint32_t view_id);
148
+
149
+ /* Input dispatch (ABI v6). Coordinates are CSS px, viewport-relative (TS
150
+ * normalizes devicePixelRatio + container offset). Modifier bitmask is
151
+ * Alt=1, Ctrl=2, Meta=4, Shift=8. Backends translate to their native form.
152
+ * No result envelope — `nativeInputTrusted` capability indicates DOM trust. */
153
+ BUNITE_EXPORT void bunite_view_click(
154
+ uint32_t view_id,
155
+ double x,
156
+ double y,
157
+ int32_t button, /* 0=left, 1=middle, 2=right */
158
+ int32_t click_count, /* >=1 */
159
+ uint32_t modifiers
160
+ );
161
+ /** Type UTF-8 text. Each codepoint becomes a CHAR / insertText event — no IME composition. */
162
+ BUNITE_EXPORT void bunite_view_type(uint32_t view_id, const char* text);
163
+ /** Press a key: down + (optional) char + up.
164
+ * `windows_vk_code` is Win32 VK_* for CEF / WebView2 CDP path.
165
+ * `mac_key_code` is the Quartz Event Services hardware key code (kVK_*) — separate
166
+ * from Win VK because DOM `KeyboardEvent.code` is derived from this on WebKit.
167
+ * `key` / `code` are DOM `KeyboardEvent.key` / `.code` strings, passed to CDP so
168
+ * the page sees the correct values. `character` is UTF-8 for the CHAR event;
169
+ * empty = skip char. 0 vk codes = skip the virtual-key down/up.
170
+ * `action`: 0=down only, 1=up only, 2=both (default). For Playwright-style
171
+ * modifier-held wrap (keydown → click → keyup). CHAR follows Playwright rule:
172
+ * emitted with down only when character is non-empty.
173
+ * `extended`: Win scancode 0xE0 prefix — Numpad-Enter AND nav-cluster
174
+ * (Arrow/Home/End/Insert/Delete/PageUp/PageDown/Meta/ContextMenu). Drives CEF
175
+ * `native_key_code`. Other backends ignore.
176
+ * `location`: DOM `KeyboardEvent.location` (0=standard, 1=left mod, 2=right
177
+ * mod, 3=numpad). WV2 CDP forwards as `location`. Most extended keys are
178
+ * location 0 — only NumpadEnter is location 3 here. */
179
+ BUNITE_EXPORT void bunite_view_press(
180
+ uint32_t view_id,
181
+ int32_t windows_vk_code,
182
+ int32_t mac_key_code,
183
+ const char* key,
184
+ const char* code,
185
+ const char* character,
186
+ uint32_t modifiers,
187
+ int32_t action,
188
+ bool extended,
189
+ int32_t location
190
+ );
191
+ /** Scroll at (x, y). dx/dy in CSS px; positive = right/down. */
192
+ BUNITE_EXPORT void bunite_view_scroll(
193
+ uint32_t view_id,
194
+ double dx,
195
+ double dy,
196
+ double x,
197
+ double y,
198
+ uint32_t modifiers
199
+ );
200
+
201
+ /** Raw mouse primitive. `action` 0=move, 1=down, 2=up. `button` 0=left, 1=middle,
202
+ * 2=right (ignored for move). Coordinates are CSS px viewport-relative. Drag is
203
+ * composed = down → move(s) → up. Backends without input support (linux GTK)
204
+ * treat as no-op. `modifiers` per-call atomic — no sticky state. */
205
+ BUNITE_EXPORT void bunite_view_mouse(
206
+ uint32_t view_id,
207
+ int32_t action,
208
+ double x,
209
+ double y,
210
+ int32_t button,
211
+ uint32_t modifiers
212
+ );
213
+
214
+ /** Respond to a page-initiated modal dialog previously announced via the
215
+ * webview event channel as `dialog` (`{requestId, kind, message, defaultPrompt?}`).
216
+ * `accept` decides confirm/beforeunload outcome and gates whether `text` is
217
+ * applied to `prompt`. Backends release the held page execution. */
218
+ BUNITE_EXPORT void bunite_view_respond_dialog(
219
+ uint32_t view_id,
220
+ uint32_t request_id,
221
+ bool accept,
222
+ const char* text
223
+ );
224
+
225
+ /** Per-view automation capability bitset. Each backend returns the bits it
226
+ * actually supports — TS layer decodes to `SurfaceCapabilities` object.
227
+ * Bits are locked at ABI v6; append-only. */
228
+ enum BuniteCapBit {
229
+ BUNITE_CAP_EVALUATE = 1u << 0,
230
+ BUNITE_CAP_CROSS_ORIGIN_EVAL = 1u << 1,
231
+ BUNITE_CAP_SURFACE_EVENTS = 1u << 2,
232
+ BUNITE_CAP_NATIVE_INPUT_TRUSTED = 1u << 3, /* click/type/press/mouse all isTrusted=true */
233
+ BUNITE_CAP_CLICK = 1u << 4,
234
+ BUNITE_CAP_TYPE = 1u << 5,
235
+ BUNITE_CAP_PRESS = 1u << 6,
236
+ BUNITE_CAP_SCROLL = 1u << 7,
237
+ BUNITE_CAP_SCREENSHOT = 1u << 8,
238
+ BUNITE_CAP_FORMAT_PNG = 1u << 9,
239
+ BUNITE_CAP_FORMAT_JPEG = 1u << 10,
240
+ BUNITE_CAP_MOUSE = 1u << 11,
241
+ BUNITE_CAP_DIALOGS = 1u << 12,
242
+ BUNITE_CAP_CONSOLE = 1u << 13,
243
+ };
244
+ BUNITE_EXPORT uint32_t bunite_view_capabilities(uint32_t view_id);
245
+
246
+ /** Capture the visible viewport as a PNG/JPEG image. Async — result reported
247
+ * via the webview event handler as `screenshot-result` with payload
248
+ * { requestId, ok: true, format, mime, dataBase64 }
249
+ * { requestId, ok: false, code, message }
250
+ * Error codes: `not_supported`, `runtime_error`, `timeout`, `black_frame` (CEF
251
+ * compositor surface unreachable). `quality` is 0–100 for JPEG only — ignored
252
+ * for PNG, and ignored entirely on WebView2 (`CapturePreview` has no quality
253
+ * parameter; output uses Edge's default ~80). `dataBase64` is base64-encoded
254
+ * image bytes; TS layer decodes. */
255
+ BUNITE_EXPORT void bunite_view_screenshot(
256
+ uint32_t view_id,
257
+ uint32_t request_id,
258
+ const char* format,
259
+ int32_t quality
260
+ );
261
+
127
262
  BUNITE_EXPORT void bunite_view_open_devtools(uint32_t view_id);
128
263
  BUNITE_EXPORT void bunite_view_close_devtools(uint32_t view_id);
129
264
  BUNITE_EXPORT void bunite_view_toggle_devtools(uint32_t view_id);
@@ -85,8 +85,11 @@ class BuniteCefClient
85
85
  public CefRequestHandler,
86
86
  public CefResourceRequestHandler,
87
87
  public CefPermissionHandler,
88
- public CefDisplayHandler {
88
+ public CefDisplayHandler,
89
+ public CefJSDialogHandler {
89
90
  public:
91
+ // BuniteCefClient is constructed 1:1 with a `ViewHost*`; `last_title_` is
92
+ // therefore per-view. OnTitleChange runs on the CEF UI thread (single).
90
93
  explicit BuniteCefClient(ViewHost* view)
91
94
  : view_(view) {}
92
95
 
@@ -95,6 +98,49 @@ public:
95
98
  CefRefPtr<CefRequestHandler> GetRequestHandler() override { return this; }
96
99
  CefRefPtr<CefPermissionHandler> GetPermissionHandler() override { return this; }
97
100
  CefRefPtr<CefDisplayHandler> GetDisplayHandler() override { return this; }
101
+ CefRefPtr<CefJSDialogHandler> GetJSDialogHandler() override { return this; }
102
+
103
+ bool OnJSDialog(CefRefPtr<CefBrowser>, const CefString& /*origin_url*/,
104
+ JSDialogType dialog_type, const CefString& message_text,
105
+ const CefString& default_prompt_text,
106
+ CefRefPtr<CefJSDialogCallback> callback,
107
+ bool& suppress_message) override {
108
+ CEF_REQUIRE_UI_THREAD();
109
+ const char* kind = (dialog_type == JSDIALOGTYPE_ALERT) ? "alert"
110
+ : (dialog_type == JSDIALOGTYPE_CONFIRM) ? "confirm" : "prompt";
111
+ const uint32_t rid = view_->next_dialog_request_id++;
112
+ view_->pending_dialogs[rid] = callback;
113
+ suppress_message = true; // we control the dialog UX; host sends the answer.
114
+ std::string payload = "{\"requestId\":" + std::to_string(rid) +
115
+ ",\"kind\":\"" + kind +
116
+ "\",\"message\":\"" + bunite_win::escapeJsonString(message_text.ToString()) + "\"";
117
+ if (dialog_type == JSDIALOGTYPE_PROMPT) {
118
+ payload += ",\"defaultPrompt\":\"" + bunite_win::escapeJsonString(default_prompt_text.ToString()) + "\"";
119
+ }
120
+ payload += "}";
121
+ bunite_win::emitWebviewEvent(view_->id, "dialog", payload);
122
+ return true; // handled — CEF will not show its own UI.
123
+ }
124
+
125
+ bool OnBeforeUnloadDialog(CefRefPtr<CefBrowser>, const CefString& message_text,
126
+ bool /*is_reload*/,
127
+ CefRefPtr<CefJSDialogCallback> callback) override {
128
+ CEF_REQUIRE_UI_THREAD();
129
+ const uint32_t rid = view_->next_dialog_request_id++;
130
+ view_->pending_dialogs[rid] = callback;
131
+ std::string payload = "{\"requestId\":" + std::to_string(rid) +
132
+ ",\"kind\":\"beforeunload\",\"message\":\"" +
133
+ bunite_win::escapeJsonString(message_text.ToString()) + "\"}";
134
+ bunite_win::emitWebviewEvent(view_->id, "dialog", payload);
135
+ return true;
136
+ }
137
+
138
+ void OnResetDialogState(CefRefPtr<CefBrowser>) override {
139
+ CEF_REQUIRE_UI_THREAD();
140
+ // Tab navigation / reload clears any stuck dialogs. Drop the callbacks —
141
+ // CEF won't deliver them anymore, and the page is already moving on.
142
+ view_->pending_dialogs.clear();
143
+ }
98
144
 
99
145
  bool OnProcessMessageReceived(
100
146
  CefRefPtr<CefBrowser> /*browser*/,
@@ -122,10 +168,25 @@ public:
122
168
 
123
169
  void OnTitleChange(CefRefPtr<CefBrowser>, const CefString& title) override {
124
170
  CEF_REQUIRE_UI_THREAD();
125
- std::string payload = "{\"title\":\"" + bunite_win::escapeJsonString(title.ToString()) + "\"}";
171
+ std::string s = title.ToString();
172
+ // Filter the transients CEF emits during initial / mid-load (blank doc
173
+ // placeholder, momentary URL-as-title) and dedup on the last emitted value.
174
+ if (s.empty()) return;
175
+ if (s == last_title_) return;
176
+ last_title_ = s;
177
+ std::string payload = "{\"title\":\"" + bunite_win::escapeJsonString(s) + "\"}";
126
178
  bunite_win::emitWebviewEvent(view_->id, "title-changed", payload);
127
179
  }
128
180
 
181
+ void OnAddressChange(CefRefPtr<CefBrowser>, CefRefPtr<CefFrame> frame,
182
+ const CefString& url) override {
183
+ CEF_REQUIRE_UI_THREAD();
184
+ if (!frame->IsMain()) return;
185
+ // URL commit point — parity with WV2 SourceChanged / mac didCommitNavigation.
186
+ // Distinct from load-finish (which is OnLoadEnd).
187
+ bunite_win::emitWebviewEvent(view_->id, "did-navigate", url.ToString());
188
+ }
189
+
129
190
  void OnBeforeDevToolsPopup(
130
191
  CefRefPtr<CefBrowser>,
131
192
  CefWindowInfo&,
@@ -153,6 +214,7 @@ public:
153
214
  void OnAfterCreated(CefRefPtr<CefBrowser> browser) override {
154
215
  CEF_REQUIRE_UI_THREAD();
155
216
  view_->browser = browser;
217
+ bunite_win::registerCdpObserverForView(view_);
156
218
  {
157
219
  std::lock_guard<std::mutex> lock(g_runtime.object_mutex);
158
220
  g_runtime.browser_to_view_id[browser->GetIdentifier()] = view_->id;
@@ -270,6 +332,12 @@ public:
270
332
  return true;
271
333
  }
272
334
 
335
+ void OnLoadStart(CefRefPtr<CefBrowser>, CefRefPtr<CefFrame> frame, TransitionType) override {
336
+ CEF_REQUIRE_UI_THREAD();
337
+ if (!frame->IsMain()) return;
338
+ bunite_win::emitWebviewEvent(view_->id, "load-start", frame->GetURL().ToString());
339
+ }
340
+
273
341
  void OnLoadEnd(CefRefPtr<CefBrowser>, CefRefPtr<CefFrame> frame, int) override {
274
342
  CEF_REQUIRE_UI_THREAD();
275
343
  if (!frame->IsMain()) {
@@ -277,10 +345,24 @@ public:
277
345
  }
278
346
 
279
347
  const std::string url = frame->GetURL().ToString();
280
- bunite_win::emitWebviewEvent(view_->id, "did-navigate", url);
348
+ bunite_win::emitWebviewEvent(view_->id, "load-finish", url);
281
349
  bunite_win::emitWebviewEvent(view_->id, "dom-ready", url);
282
350
  }
283
351
 
352
+ void OnLoadError(CefRefPtr<CefBrowser>, CefRefPtr<CefFrame> frame,
353
+ ErrorCode errorCode, const CefString& errorText, const CefString& failedUrl) override {
354
+ CEF_REQUIRE_UI_THREAD();
355
+ if (!frame->IsMain()) return;
356
+ // ERR_ABORTED fires on user-initiated navigation cancellation — not a
357
+ // failure from the consumer's perspective. Filter to align with WV2.
358
+ if (errorCode == ERR_ABORTED) return;
359
+ std::string reason = errorText.ToString();
360
+ if (reason.empty()) reason = "ERR_" + std::to_string(static_cast<int>(errorCode));
361
+ std::string payload = "{\"url\":\"" + bunite_win::escapeJsonString(failedUrl.ToString()) +
362
+ "\",\"reason\":\"" + bunite_win::escapeJsonString(reason) + "\"}";
363
+ bunite_win::emitWebviewEvent(view_->id, "load-fail", payload);
364
+ }
365
+
284
366
  bool OnShowPermissionPrompt(
285
367
  CefRefPtr<CefBrowser>,
286
368
  uint64_t,
@@ -341,6 +423,7 @@ public:
341
423
 
342
424
  private:
343
425
  ViewHost* view_;
426
+ std::string last_title_;
344
427
 
345
428
  IMPLEMENT_REFCOUNTING(BuniteCefClient);
346
429
  };