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.
Files changed (36) hide show
  1. package/package.json +4 -4
  2. package/src/host/core/App.ts +19 -2
  3. package/src/host/core/BrowserView.ts +515 -38
  4. package/src/host/core/SurfaceBrowserIPC.ts +53 -3
  5. package/src/host/core/SurfaceManager.ts +603 -30
  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 +25 -1
  9. package/src/host/log.ts +6 -1
  10. package/src/host/native.ts +263 -1
  11. package/src/host/preloadBundle.ts +7 -2
  12. package/src/native/linux/bunite_linux_ffi.cpp +427 -6
  13. package/src/native/linux/bunite_linux_internal.h +18 -0
  14. package/src/native/linux/bunite_linux_runtime.cpp +6 -1
  15. package/src/native/linux/bunite_linux_utils.cpp +2 -2
  16. package/src/native/linux/bunite_linux_view.cpp +296 -5
  17. package/src/native/mac/bunite_mac_ffi.mm +630 -8
  18. package/src/native/mac/bunite_mac_internal.h +19 -0
  19. package/src/native/mac/bunite_mac_utils.mm +2 -2
  20. package/src/native/mac/bunite_mac_view.mm +371 -9
  21. package/src/native/shared/ffi_exports.h +200 -2
  22. package/src/native/win/native_host_cef.cpp +186 -11
  23. package/src/native/win/native_host_ffi.cpp +1194 -1
  24. package/src/native/win/native_host_internal.h +35 -0
  25. package/src/native/win/native_host_utils.cpp +2 -1
  26. package/src/native/win/process_helper_win.cpp +54 -27
  27. package/src/native/win-webview2/bunite_webview2_ffi.cpp +1023 -12
  28. package/src/native/win-webview2/webview2_internal.h +25 -0
  29. package/src/native/win-webview2/webview2_runtime.cpp +403 -34
  30. package/src/native/win-webview2/webview2_utils.cpp +30 -12
  31. package/src/preload/runtime.built.js +1 -1
  32. package/src/preload/runtime.ts +97 -0
  33. package/src/rpc/framework.ts +340 -8
  34. package/src/rpc/index.ts +32 -0
  35. package/src/webview/native.ts +253 -51
  36. package/src/webview/polyfill.ts +283 -22
@@ -34,9 +34,11 @@
34
34
  #include "include/cef_app.h"
35
35
  #include "include/cef_browser.h"
36
36
  #include "include/cef_client.h"
37
+ #include "include/cef_devtools_message_observer.h"
37
38
  #include "include/cef_command_line.h"
38
39
  #include "include/cef_parser.h"
39
40
  #include "include/cef_permission_handler.h"
41
+ #include "include/cef_jsdialog_handler.h"
40
42
  #include "include/cef_resource_handler.h"
41
43
  #include "include/cef_resource_request_handler.h"
42
44
  #include "include/cef_scheme.h"
@@ -97,6 +99,27 @@ struct ViewHost {
97
99
  std::atomic<bool> closing = false;
98
100
  CefRefPtr<CefBrowser> browser;
99
101
  CefRefPtr<CefClient> client;
102
+ // DevTools observer registration — kept alive for the life of the view so
103
+ // CDP method results (screenshot etc.) can route back to bunite handlers.
104
+ CefRefPtr<CefRegistration> devtools_registration;
105
+
106
+ // Page-initiated dialog callbacks held until host responds via
107
+ // `respondToDialogRequest`. CEF holds the page execution while we wait.
108
+ std::unordered_map<uint32_t, CefRefPtr<CefJSDialogCallback>> pending_dialogs;
109
+ uint32_t next_dialog_request_id = 1;
110
+
111
+ // Download policy: 0=auto, 1=ask, 2=block. Default block.
112
+ std::atomic<int32_t> download_policy{2};
113
+ std::string download_dir;
114
+
115
+ // True for ViewHosts minted by OnBeforePopup; popup_accept binds them to a
116
+ // user window, popup_dismiss destroys them.
117
+ bool is_popup_pending = false;
118
+ // If popup_accept arrives before OnAfterCreated, stash parameters; the
119
+ // browser-create completion applies them.
120
+ struct PendingPopupAccept { uint32_t host_window_id; double x, y, w, h; };
121
+ std::optional<PendingPopupAccept> pending_popup_accept;
122
+ bool popup_dismiss_requested = false;
100
123
 
101
124
  // Pending state: applied in OnAfterCreated when browser HWND becomes available.
102
125
  bool pending_visible = true;
@@ -104,6 +127,12 @@ struct ViewHost {
104
127
  bool pending_passthrough = false;
105
128
  bool has_pending_bounds = false;
106
129
  RECT pending_bounds{0, 0, 0, 0};
130
+
131
+ // OOPIF input dispatch — populated by Target.attachedToTarget events after
132
+ // lazy Target.setAutoAttach. frameId → sessionId for flatten:true routing.
133
+ std::atomic<bool> oopif_autoattach_armed = false;
134
+ std::mutex oopif_sessions_mutex;
135
+ std::unordered_map<std::string, std::string> oopif_sessions; // frameId → sessionId
107
136
  };
108
137
 
109
138
  struct WindowHost {
@@ -198,6 +227,9 @@ bool shouldAllowNavigation(const ViewHost* view, const std::string& url);
198
227
  void emitWindowEvent(uint32_t window_id, const char* event_name, const std::string& payload = {});
199
228
  void emitWebviewEvent(uint32_t view_id, const char* event_name, const std::string& payload = {});
200
229
 
230
+ // Bind a popup ViewHost (created by OnBeforePopup) to a user window + bounds.
231
+ void applyPopupAccept(ViewHost* view, uint32_t host_window_id, double x, double y, double w, double h);
232
+
201
233
  std::string normalizeAppResPath(const std::string& url);
202
234
  std::string getMimeType(const std::filesystem::path& file_path);
203
235
  std::string getAppResRootForView(uint32_t view_id);
@@ -214,6 +246,9 @@ void finalizeWindowHost(WindowHost* window); // [UI thread]
214
246
  void openDevToolsForView(ViewHost* view); // [CEF UI thread]
215
247
  void closeDevToolsForView(ViewHost* view); // [CEF UI thread]
216
248
  void toggleDevToolsForView(ViewHost* view); // [CEF UI thread]
249
+ void registerCdpObserverForView(ViewHost* view); // [CEF UI thread]
250
+ void respondToDialogRequest(ViewHost* view, uint32_t request_id,
251
+ bool accept, const std::string& text); // [CEF UI thread]
217
252
  bool initializeCefOnUiThread(); // [UI thread]
218
253
  void shutdownCefOnUiThread(); // [UI thread]
219
254
  void cancelPendingPermissionRequestsOnUiThread(); // [CEF UI thread]
@@ -211,7 +211,8 @@ std::vector<std::string> parseNavigationRulesJson(const std::string& rules_json)
211
211
  }
212
212
 
213
213
  bool shouldAlwaysAllowNavigationUrl(const std::string& url) {
214
- return url == "about:blank" || url.rfind("appres://app.internal/internal/", 0) == 0;
214
+ // Exact-match prefix would let `../../evil` style paths bypass scrutiny.
215
+ return url == "about:blank" || url == "appres://app.internal/internal/index.html";
215
216
  }
216
217
 
217
218
  uint32_t cefPermissionsToBuniteKind(uint32_t cef_bits) {
@@ -48,10 +48,10 @@ public:
48
48
  std::string script = args->GetString(1).ToString();
49
49
 
50
50
  auto context = frame->GetV8Context();
51
+ auto reply = CefProcessMessage::Create("bunite.evaluate.result");
52
+ auto rl = reply->GetArgumentList();
53
+ rl->SetInt(0, static_cast<int>(request_id));
51
54
  if (!context) {
52
- auto reply = CefProcessMessage::Create("bunite.evaluate.result");
53
- auto rl = reply->GetArgumentList();
54
- rl->SetInt(0, static_cast<int>(request_id));
55
55
  rl->SetBool(1, false);
56
56
  rl->SetString(2, "runtime_error");
57
57
  rl->SetString(3, "no V8 context");
@@ -59,37 +59,64 @@ public:
59
59
  return true;
60
60
  }
61
61
 
62
+ // Same wrapper as WebView2/mac/linux: try/catch returning a JSON envelope
63
+ // string. SecurityError is detected locale-independently inside the JS.
64
+ std::string wrapped =
65
+ "(function(){try{return JSON.stringify({__bunite_ok:true,value:(" + script +
66
+ ")})}catch(e){var c=(e&&e.name===\"SecurityError\")?\"cross_origin\":\"runtime_error\";"
67
+ "return JSON.stringify({__bunite_ok:false,code:c,"
68
+ "message:(e&&e.message)?e.message:String(e),"
69
+ "name:(e&&e.name)||\"\"})}})()";
70
+
62
71
  context->Enter();
63
72
  CefRefPtr<CefV8Value> retval;
64
73
  CefRefPtr<CefV8Exception> exception;
65
- bool ok = context->Eval(script, "bunite://evaluate", 0, retval, exception);
66
-
67
- auto reply = CefProcessMessage::Create("bunite.evaluate.result");
68
- auto rl = reply->GetArgumentList();
69
- rl->SetInt(0, static_cast<int>(request_id));
70
- if (ok && retval) {
71
- // V8 JSON.stringify for cross-process transport.
72
- auto global = context->GetGlobal();
73
- auto json_obj = global->GetValue("JSON");
74
- auto stringify = json_obj ? json_obj->GetValue("stringify") : nullptr;
75
- std::string json_str;
76
- if (stringify && stringify->IsFunction()) {
77
- CefV8ValueList args2; args2.push_back(retval);
78
- auto json_v = stringify->ExecuteFunction(json_obj, args2);
79
- if (json_v && json_v->IsString()) json_str = json_v->GetStringValue().ToString();
74
+ bool ok = context->Eval(wrapped, "bunite://evaluate", 0, retval, exception);
75
+
76
+ if (ok && retval && retval->IsString()) {
77
+ std::string inner = retval->GetStringValue().ToString();
78
+ if (inner.find("\"__bunite_ok\":true") != std::string::npos) {
79
+ static const std::string prefix = "{\"__bunite_ok\":true,\"value\":";
80
+ std::string value_json = "null";
81
+ if (inner.compare(0, prefix.size(), prefix) == 0 &&
82
+ inner.size() > prefix.size() + 1) {
83
+ value_json = inner.substr(prefix.size(), inner.size() - prefix.size() - 1);
84
+ }
85
+ rl->SetBool(1, true);
86
+ rl->SetString(2, value_json);
87
+ rl->SetString(3, "");
88
+ } else {
89
+ // Anchor at the envelope prefix — user-controlled e.message could
90
+ // otherwise inject a fake "code" via the substring scan above.
91
+ static const std::string codePrefix = "{\"__bunite_ok\":false,\"code\":\"";
92
+ std::string code = "runtime_error";
93
+ std::string msg = "script threw";
94
+ if (inner.compare(0, codePrefix.size(), codePrefix) == 0) {
95
+ size_t start = codePrefix.size();
96
+ size_t end = start;
97
+ while (end < inner.size() && inner[end] != '"') ++end;
98
+ if (end > start) code = inner.substr(start, end - start);
99
+ static const std::string msgKey = "\",\"message\":\"";
100
+ if (end + msgKey.size() <= inner.size() &&
101
+ inner.compare(end, msgKey.size(), msgKey) == 0) {
102
+ size_t mstart = end + msgKey.size();
103
+ size_t mend = mstart;
104
+ while (mend < inner.size()) {
105
+ if (inner[mend] == '"' && (mend == mstart || inner[mend - 1] != '\\')) break;
106
+ ++mend;
107
+ }
108
+ if (mend > mstart) msg = inner.substr(mstart, mend - mstart);
109
+ }
110
+ }
111
+ rl->SetBool(1, false);
112
+ rl->SetString(2, code);
113
+ rl->SetString(3, msg);
80
114
  }
81
- if (json_str.empty()) json_str = "null";
82
- rl->SetBool(1, true);
83
- rl->SetString(2, json_str);
84
- rl->SetString(3, "");
85
115
  } else {
116
+ // Wrapper itself failed (syntax error in user script, or V8 internal).
86
117
  std::string msg = exception ? exception->GetMessage().ToString() : "eval failed";
87
- // SecurityError typically arises from cross-origin reach.
88
- std::string code = (msg.find("SecurityError") != std::string::npos ||
89
- msg.find("cross-origin") != std::string::npos)
90
- ? "cross_origin" : "runtime_error";
91
118
  rl->SetBool(1, false);
92
- rl->SetString(2, code);
119
+ rl->SetString(2, "runtime_error");
93
120
  rl->SetString(3, msg);
94
121
  }
95
122
  context->Exit();