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
@@ -10,6 +10,10 @@ namespace {
10
10
 
11
11
  constexpr const char* kViewIdKey = "bunite-view-id";
12
12
 
13
+ // Forward decls — on_create references these to wire popup-minted views.
14
+ gboolean on_decide_policy(WebKitWebView*, WebKitPolicyDecision*, WebKitPolicyDecisionType, gpointer);
15
+ void on_title_changed(GObject*, GParamSpec*, gpointer);
16
+
13
17
  void emit_url(uint32_t view_id, const char* name, WebKitWebView* wv) {
14
18
  const char* uri = webkit_web_view_get_uri(wv);
15
19
  emitWebviewEvent(view_id, name, uri ? std::string(uri) : std::string{});
@@ -18,11 +22,15 @@ void emit_url(uint32_t view_id, const char* name, WebKitWebView* wv) {
18
22
  void on_load_changed(WebKitWebView* wv, WebKitLoadEvent event, gpointer user_data) {
19
23
  const uint32_t view_id = GPOINTER_TO_UINT(user_data);
20
24
  switch (event) {
25
+ case WEBKIT_LOAD_STARTED:
26
+ emit_url(view_id, "load-start", wv);
27
+ break;
21
28
  case WEBKIT_LOAD_COMMITTED:
22
29
  emit_url(view_id, "did-navigate", wv);
23
30
  queueViewRedraw(wv);
24
31
  break;
25
32
  case WEBKIT_LOAD_FINISHED:
33
+ emit_url(view_id, "load-finish", wv);
26
34
  emit_url(view_id, "dom-ready", wv);
27
35
  queueViewRedraw(wv);
28
36
  break;
@@ -30,14 +38,107 @@ void on_load_changed(WebKitWebView* wv, WebKitLoadEvent event, gpointer user_dat
30
38
  }
31
39
  }
32
40
 
33
- GtkWidget* on_create(WebKitWebView* wv, WebKitNavigationAction* action, gpointer user_data) {
41
+ gboolean on_load_failed(WebKitWebView* /*wv*/, WebKitLoadEvent /*ev*/, const char* failing_uri,
42
+ GError* error, gpointer user_data) {
43
+ const uint32_t view_id = GPOINTER_TO_UINT(user_data);
44
+ std::string payload = "{\"url\":\"" + escapeJsonString(failing_uri ? failing_uri : "") +
45
+ "\",\"reason\":\"" + escapeJsonString(error && error->message ? error->message : "") + "\"}";
46
+ emitWebviewEvent(view_id, "load-fail", payload);
47
+ return FALSE; // let WebKit show its default error page
48
+ }
49
+
50
+ gboolean on_load_failed_tls(WebKitWebView* /*wv*/, const char* failing_uri,
51
+ GTlsCertificate* /*cert*/, GTlsCertificateFlags /*errors*/,
52
+ gpointer user_data) {
53
+ const uint32_t view_id = GPOINTER_TO_UINT(user_data);
54
+ std::string payload = "{\"url\":\"" + escapeJsonString(failing_uri ? failing_uri : "") +
55
+ "\",\"reason\":\"tls-certificate-error\"}";
56
+ emitWebviewEvent(view_id, "load-fail", payload);
57
+ return FALSE; // do not override default certificate failure behavior
58
+ }
59
+
60
+ gboolean on_script_dialog(WebKitWebView* /*wv*/, WebKitScriptDialog* dialog, gpointer user_data) {
34
61
  const uint32_t view_id = GPOINTER_TO_UINT(user_data);
62
+ auto* v = findView(view_id);
63
+ if (!v) return FALSE;
64
+ WebKitScriptDialogType type = webkit_script_dialog_get_dialog_type(dialog);
65
+ const char* kind = nullptr;
66
+ switch (type) {
67
+ case WEBKIT_SCRIPT_DIALOG_ALERT: kind = "alert"; break;
68
+ case WEBKIT_SCRIPT_DIALOG_CONFIRM: kind = "confirm"; break;
69
+ case WEBKIT_SCRIPT_DIALOG_PROMPT: kind = "prompt"; break;
70
+ case WEBKIT_SCRIPT_DIALOG_BEFORE_UNLOAD_CONFIRM: kind = "beforeunload"; break;
71
+ default: return FALSE;
72
+ }
73
+ // Defer the dialog so the page execution stays paused until host responds.
74
+ webkit_script_dialog_ref(dialog);
75
+ const uint32_t rid = v->next_dialog_request_id++;
76
+ v->pending_dialogs[rid] = dialog;
77
+ const char* message = webkit_script_dialog_get_message(dialog);
78
+ std::string payload = "{\"requestId\":" + std::to_string(rid) +
79
+ ",\"kind\":\"" + kind +
80
+ "\",\"message\":\"" + escapeJsonString(message ? message : "") + "\"";
81
+ if (type == WEBKIT_SCRIPT_DIALOG_PROMPT) {
82
+ const char* def = webkit_script_dialog_prompt_get_default_text(dialog);
83
+ payload += ",\"defaultPrompt\":\"" + escapeJsonString(def ? def : "") + "\"";
84
+ }
85
+ payload += "}";
86
+ emitWebviewEvent(view_id, "dialog", payload);
87
+ return TRUE; // we handled it
88
+ }
89
+
90
+ GtkWidget* on_create(WebKitWebView* wv, WebKitNavigationAction* action, gpointer user_data) {
91
+ const uint32_t opener_view_id = GPOINTER_TO_UINT(user_data);
35
92
  WebKitURIRequest* req = webkit_navigation_action_get_request(action);
36
93
  const char* uri = webkit_uri_request_get_uri(req);
37
- std::string payload = "{\"url\":\"" + escapeJsonString(uri ? uri : "") + "\"}";
38
- emitWebviewEvent(view_id, "new-window-open", payload);
39
- (void)wv;
40
- return nullptr; // cancel; JS opens via load_url if desired
94
+ if (g_runtime.popup_blocking) {
95
+ std::string payload = "{\"url\":\"" + escapeJsonString(uri ? uri : "") + "\"}";
96
+ emitWebviewEvent(opener_view_id, "new-window-open", payload);
97
+ return nullptr;
98
+ }
99
+ static std::atomic<uint32_t> g_popup_seq{0x80000000u};
100
+ const uint32_t new_view_id = g_popup_seq.fetch_add(1);
101
+ // Share network-session + user-content-manager so cookies/preload-injection
102
+ // carry across the opener boundary.
103
+ WebKitWebView* popup = WEBKIT_WEB_VIEW(g_object_new(
104
+ WEBKIT_TYPE_WEB_VIEW,
105
+ "network-session", webkit_web_view_get_network_session(wv),
106
+ "user-content-manager", webkit_web_view_get_user_content_manager(wv),
107
+ nullptr));
108
+ g_object_ref_sink(popup);
109
+ g_object_set_data(G_OBJECT(popup), kViewIdKey, GUINT_TO_POINTER(new_view_id));
110
+ g_signal_connect(popup, "load-changed", G_CALLBACK(on_load_changed), GUINT_TO_POINTER(new_view_id));
111
+ g_signal_connect(popup, "load-failed", G_CALLBACK(on_load_failed), GUINT_TO_POINTER(new_view_id));
112
+ g_signal_connect(popup, "load-failed-with-tls-errors", G_CALLBACK(on_load_failed_tls), GUINT_TO_POINTER(new_view_id));
113
+ g_signal_connect(popup, "decide-policy", G_CALLBACK(on_decide_policy), GUINT_TO_POINTER(new_view_id));
114
+ g_signal_connect(popup, "create", G_CALLBACK(on_create), GUINT_TO_POINTER(new_view_id));
115
+ g_signal_connect(popup, "notify::title", G_CALLBACK(on_title_changed), GUINT_TO_POINTER(new_view_id));
116
+ g_signal_connect(popup, "script-dialog", G_CALLBACK(on_script_dialog), GUINT_TO_POINTER(new_view_id));
117
+ {
118
+ std::lock_guard<std::mutex> lock(g_runtime.object_mutex);
119
+ g_runtime.parked_popups[new_view_id] = popup;
120
+ auto& st = g_runtime.views[new_view_id];
121
+ st.webview = popup;
122
+ st.window_id = 0; // bound on adoption
123
+ st.container = GTK_WIDGET(popup);
124
+ }
125
+ if (g_runtime.popup_parent) {
126
+ GtkWidget* box = gtk_window_get_child(g_runtime.popup_parent);
127
+ if (box) gtk_box_append(GTK_BOX(box), GTK_WIDGET(popup));
128
+ }
129
+ std::string payload = "{\"newSurfaceId\":" + std::to_string(new_view_id) +
130
+ ",\"url\":\"" + escapeJsonString(uri ? uri : "") +
131
+ "\",\"disposition\":\"popup\"}";
132
+ emitWebviewEvent(opener_view_id, "popup-requested", payload);
133
+ return GTK_WIDGET(popup);
134
+ }
135
+
136
+ void on_title_changed(GObject* source, GParamSpec* /*pspec*/, gpointer user_data) {
137
+ const uint32_t view_id = GPOINTER_TO_UINT(user_data);
138
+ WebKitWebView* wv = WEBKIT_WEB_VIEW(source);
139
+ const char* title = webkit_web_view_get_title(wv);
140
+ std::string payload = "{\"title\":\"" + escapeJsonString(title ? title : "") + "\"}";
141
+ emitWebviewEvent(view_id, "title-changed", payload);
41
142
  }
42
143
 
43
144
  // WebKitGTK fires nav-action for sub-frames with no main-frame discriminator — iframes hit nav rules.
@@ -59,8 +160,126 @@ gboolean on_decide_policy(WebKitWebView* wv, WebKitPolicyDecision* decision,
59
160
  return TRUE;
60
161
  }
61
162
 
163
+ constexpr const char* kDownloadIdKey = "bunite-download-id";
164
+ std::atomic<uint64_t> g_download_seq{1};
165
+
166
+ uint32_t viewIdForDownload(WebKitDownload* download) {
167
+ WebKitWebView* wv = webkit_download_get_web_view(download);
168
+ if (!wv) return 0;
169
+ return GPOINTER_TO_UINT(g_object_get_data(G_OBJECT(wv), kViewIdKey));
170
+ }
171
+
172
+ void on_received_data(WebKitDownload* download, guint64 /*data_length*/, gpointer /*user_data*/) {
173
+ const uint32_t view_id = viewIdForDownload(download);
174
+ if (!view_id) return;
175
+ const uint64_t id = GPOINTER_TO_SIZE(g_object_get_data(G_OBJECT(download), kDownloadIdKey));
176
+ const guint64 received = webkit_download_get_received_data_length(download);
177
+ WebKitURIResponse* resp = webkit_download_get_response(download);
178
+ const guint64 total = resp ? webkit_uri_response_get_content_length(resp) : 0;
179
+ std::string payload = "{\"kind\":\"progress\",\"id\":\"linux-" + std::to_string(id) +
180
+ "\",\"receivedBytes\":" + std::to_string(received);
181
+ if (total > 0) payload += ",\"totalBytes\":" + std::to_string(total);
182
+ payload += "}";
183
+ emitWebviewEvent(view_id, "download-event", payload);
184
+ }
185
+
186
+ void on_download_finished(WebKitDownload* download, gpointer /*user_data*/) {
187
+ const uint32_t view_id = viewIdForDownload(download);
188
+ if (!view_id) return;
189
+ const uint64_t id = GPOINTER_TO_SIZE(g_object_get_data(G_OBJECT(download), kDownloadIdKey));
190
+ const char* dest_uri = webkit_download_get_destination(download);
191
+ std::string dest = dest_uri ? dest_uri : "";
192
+ if (dest.rfind("file://", 0) == 0) dest = dest.substr(7);
193
+ std::string payload = "{\"kind\":\"completed\",\"id\":\"linux-" + std::to_string(id) +
194
+ "\",\"localPath\":\"" + escapeJsonString(dest) + "\"}";
195
+ emitWebviewEvent(view_id, "download-event", payload);
196
+ }
197
+
198
+ void on_download_failed(WebKitDownload* download, GError* error, gpointer /*user_data*/) {
199
+ const uint32_t view_id = viewIdForDownload(download);
200
+ if (!view_id) return;
201
+ const uint64_t id = GPOINTER_TO_SIZE(g_object_get_data(G_OBJECT(download), kDownloadIdKey));
202
+ std::string reason = error && error->message ? error->message : "unknown";
203
+ std::string payload = "{\"kind\":\"failed\",\"id\":\"linux-" + std::to_string(id) +
204
+ "\",\"reason\":\"" + escapeJsonString(reason) + "\"}";
205
+ emitWebviewEvent(view_id, "download-event", payload);
206
+ }
207
+
208
+ // `decide-destination` is the gate: emits `started` / `blocked` and sets path.
209
+ // Returning TRUE tells WebKit we resolved (or cancelled) destination.
210
+ gboolean on_decide_destination(WebKitDownload* download, const gchar* suggested, gpointer /*user_data*/) {
211
+ const uint32_t view_id = viewIdForDownload(download);
212
+ if (!view_id) return FALSE;
213
+ ViewState* st = nullptr;
214
+ {
215
+ std::lock_guard<std::mutex> lk(g_runtime.object_mutex);
216
+ auto it = g_runtime.views.find(view_id);
217
+ if (it != g_runtime.views.end()) st = &it->second;
218
+ }
219
+ if (!st) return FALSE;
220
+ const uint64_t id = GPOINTER_TO_SIZE(g_object_get_data(G_OBJECT(download), kDownloadIdKey));
221
+ WebKitURIRequest* req = webkit_download_get_request(download);
222
+ const std::string url = (req && webkit_uri_request_get_uri(req)) ? webkit_uri_request_get_uri(req) : "";
223
+
224
+ const int32_t policy = st->download_policy.load();
225
+ if (policy != 0) {
226
+ const char* reason = (policy == 1) ? "ask-not-implemented" : "host-policy";
227
+ std::string payload = "{\"kind\":\"blocked\",\"id\":\"linux-" + std::to_string(id) +
228
+ "\",\"url\":\"" + escapeJsonString(url) +
229
+ "\",\"reason\":\"" + reason + "\"}";
230
+ emitWebviewEvent(view_id, "download-event", payload);
231
+ webkit_download_cancel(download);
232
+ return TRUE;
233
+ }
234
+
235
+ const std::string sug = suggested ? suggested : "download";
236
+ WebKitURIResponse* resp = webkit_download_get_response(download);
237
+ const std::string mime = (resp && webkit_uri_response_get_mime_type(resp)) ? webkit_uri_response_get_mime_type(resp) : "";
238
+ const guint64 total = resp ? webkit_uri_response_get_content_length(resp) : 0;
239
+ std::string started = "{\"kind\":\"started\",\"id\":\"linux-" + std::to_string(id) +
240
+ "\",\"url\":\"" + escapeJsonString(url) +
241
+ "\",\"suggestedFilename\":\"" + escapeJsonString(sug) +
242
+ "\",\"mimeType\":\"" + escapeJsonString(mime) + "\"";
243
+ if (total > 0) started += ",\"sizeBytes\":" + std::to_string(total);
244
+ started += "}";
245
+ emitWebviewEvent(view_id, "download-event", started);
246
+
247
+ std::string dir = st->download_dir;
248
+ if (dir.empty()) {
249
+ const char* d = g_get_user_special_dir(G_USER_DIRECTORY_DOWNLOAD);
250
+ dir = d ? d : "/tmp";
251
+ }
252
+ std::string path = dir + "/" + sug;
253
+ std::string uri = "file://" + path;
254
+ webkit_download_set_destination(download, uri.c_str());
255
+ return TRUE;
256
+ }
257
+
258
+ void on_download_started(WebKitNetworkSession* /*session*/, WebKitDownload* download, gpointer /*user_data*/) {
259
+ const uint32_t view_id = viewIdForDownload(download);
260
+ if (!view_id) {
261
+ webkit_download_cancel(download);
262
+ return;
263
+ }
264
+ const uint64_t id = g_download_seq.fetch_add(1);
265
+ g_object_set_data(G_OBJECT(download), kDownloadIdKey, GSIZE_TO_POINTER(id));
266
+ g_signal_connect(download, "decide-destination", G_CALLBACK(on_decide_destination), nullptr);
267
+ g_signal_connect(download, "received-data", G_CALLBACK(on_received_data), nullptr);
268
+ g_signal_connect(download, "finished", G_CALLBACK(on_download_finished), nullptr);
269
+ g_signal_connect(download, "failed", G_CALLBACK(on_download_failed), nullptr);
270
+ }
271
+
62
272
  } // namespace
63
273
 
274
+ void wireDownloadHandlers() {
275
+ static bool wired = false;
276
+ if (wired) return;
277
+ WebKitNetworkSession* session = webkit_network_session_get_default();
278
+ if (!session) return;
279
+ g_signal_connect(session, "download-started", G_CALLBACK(on_download_started), nullptr);
280
+ wired = true;
281
+ }
282
+
64
283
  ViewState* findView(uint32_t view_id) {
65
284
  std::lock_guard<std::mutex> lock(g_runtime.object_mutex);
66
285
  auto it = g_runtime.views.find(view_id);
@@ -72,6 +291,26 @@ uint32_t viewIdForWebView(WebKitWebView* wv) {
72
291
  return GPOINTER_TO_UINT(g_object_get_data(G_OBJECT(wv), kViewIdKey));
73
292
  }
74
293
 
294
+ void respondToDialogRequest(uint32_t view_id, uint32_t request_id, bool accept,
295
+ const std::string& text) {
296
+ auto* v = findView(view_id);
297
+ if (!v) return;
298
+ auto it = v->pending_dialogs.find(request_id);
299
+ if (it == v->pending_dialogs.end()) return;
300
+ WebKitScriptDialog* dialog = it->second;
301
+ v->pending_dialogs.erase(it);
302
+ if (!dialog) return;
303
+ WebKitScriptDialogType type = webkit_script_dialog_get_dialog_type(dialog);
304
+ if (type == WEBKIT_SCRIPT_DIALOG_CONFIRM || type == WEBKIT_SCRIPT_DIALOG_BEFORE_UNLOAD_CONFIRM) {
305
+ webkit_script_dialog_confirm_set_confirmed(dialog, accept ? TRUE : FALSE);
306
+ } else if (type == WEBKIT_SCRIPT_DIALOG_PROMPT) {
307
+ if (accept) webkit_script_dialog_prompt_set_text(dialog, text.c_str());
308
+ else webkit_script_dialog_prompt_set_text(dialog, nullptr);
309
+ }
310
+ webkit_script_dialog_close(dialog);
311
+ webkit_script_dialog_unref(dialog);
312
+ }
313
+
75
314
  bool createView(uint32_t view_id, uint32_t window_id,
76
315
  const char* url, const char* html, const char* preload, const char* appres_root,
77
316
  const char* navigation_rules_json, const char* preload_origins_json,
@@ -86,6 +325,8 @@ bool createView(uint32_t view_id, uint32_t window_id,
86
325
  return false;
87
326
  }
88
327
 
328
+ wireDownloadHandlers();
329
+
89
330
  WebKitUserContentManager* ucm = webkit_user_content_manager_new();
90
331
 
91
332
  // Origin-gate the preload so http(s) navigations don't inherit the RPC bridge.
@@ -122,8 +363,12 @@ bool createView(uint32_t view_id, uint32_t window_id,
122
363
  registerAppresScheme(webkit_web_view_get_context(wv));
123
364
 
124
365
  g_signal_connect(wv, "load-changed", G_CALLBACK(on_load_changed), GUINT_TO_POINTER(view_id));
366
+ g_signal_connect(wv, "load-failed", G_CALLBACK(on_load_failed), GUINT_TO_POINTER(view_id));
367
+ g_signal_connect(wv, "load-failed-with-tls-errors", G_CALLBACK(on_load_failed_tls), GUINT_TO_POINTER(view_id));
125
368
  g_signal_connect(wv, "decide-policy", G_CALLBACK(on_decide_policy), GUINT_TO_POINTER(view_id));
126
369
  g_signal_connect(wv, "create", G_CALLBACK(on_create), GUINT_TO_POINTER(view_id));
370
+ g_signal_connect(wv, "notify::title", G_CALLBACK(on_title_changed), GUINT_TO_POINTER(view_id));
371
+ g_signal_connect(wv, "script-dialog", G_CALLBACK(on_script_dialog), GUINT_TO_POINTER(view_id));
127
372
 
128
373
  GtkWidget* container = GTK_WIDGET(wv);
129
374
  if (auto_resize) {
@@ -206,6 +451,52 @@ void applyViewBounds(uint32_t view_id, double x, double y, double w, double h) {
206
451
  queueViewRedraw(v->webview);
207
452
  }
208
453
 
454
+ bool acceptParkedPopup(uint32_t new_view_id, uint32_t host_window_id, double x, double y, double w, double h) {
455
+ WebKitWebView* popup = nullptr;
456
+ {
457
+ std::lock_guard<std::mutex> lock(g_runtime.object_mutex);
458
+ auto it = g_runtime.parked_popups.find(new_view_id);
459
+ if (it == g_runtime.parked_popups.end()) return false;
460
+ popup = it->second;
461
+ g_runtime.parked_popups.erase(it);
462
+ }
463
+ auto* host = findWindow(host_window_id);
464
+ if (!host || !host->host) return false;
465
+ gtk_widget_unparent(GTK_WIDGET(popup));
466
+ GtkWidget* container = gtk_fixed_new();
467
+ gtk_widget_set_halign(container, GTK_ALIGN_START);
468
+ gtk_widget_set_valign(container, GTK_ALIGN_START);
469
+ gtk_widget_set_overflow(container, GTK_OVERFLOW_HIDDEN);
470
+ gtk_fixed_put(GTK_FIXED(container), GTK_WIDGET(popup), 0, 0);
471
+ gtk_overlay_add_overlay(host->host, container);
472
+ {
473
+ std::lock_guard<std::mutex> lock(g_runtime.object_mutex);
474
+ auto& st = g_runtime.views[new_view_id];
475
+ st.window_id = host_window_id;
476
+ st.container = container;
477
+ st.webview = popup;
478
+ }
479
+ applyViewBounds(new_view_id, x, y, w, h);
480
+ // Re-emit view-ready so TS BrowserView.adopt resolves its waiter — the
481
+ // initial `did-navigate` fired before the adopter registered.
482
+ emitWebviewEvent(new_view_id, "view-ready", std::string{});
483
+ return true;
484
+ }
485
+
486
+ void dismissParkedPopup(uint32_t new_view_id) {
487
+ WebKitWebView* popup = nullptr;
488
+ {
489
+ std::lock_guard<std::mutex> lock(g_runtime.object_mutex);
490
+ auto it = g_runtime.parked_popups.find(new_view_id);
491
+ if (it == g_runtime.parked_popups.end()) return;
492
+ popup = it->second;
493
+ g_runtime.parked_popups.erase(it);
494
+ g_runtime.views.erase(new_view_id);
495
+ }
496
+ gtk_widget_unparent(GTK_WIDGET(popup));
497
+ g_object_unref(popup);
498
+ }
499
+
209
500
  void detachViewSideState(uint32_t view_id) {
210
501
  bunite::WebviewContentStorage::instance().remove(view_id);
211
502
  std::vector<uint32_t> request_ids;