bunite-core 0.0.1 → 0.0.3
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 +2 -2
- package/src/bun/core/App.ts +155 -15
- package/src/bun/core/BrowserView.ts +124 -44
- package/src/bun/core/BrowserWindow.ts +94 -47
- package/src/bun/core/Socket.ts +2 -1
- package/src/bun/core/SurfaceBrowserIPC.ts +65 -0
- package/src/bun/core/SurfaceManager.ts +201 -0
- package/src/bun/core/SurfaceRegistry.ts +60 -0
- package/src/bun/core/Utils.ts +275 -46
- package/src/bun/events/appEvents.ts +2 -1
- package/src/bun/events/webviewEvents.ts +1 -3
- package/src/bun/events/windowEvents.ts +2 -0
- package/src/bun/index.ts +4 -3
- package/src/bun/preload/inline.ts +19 -25
- package/src/bun/proc/native.ts +158 -122
- package/src/native/shared/callbacks.h +6 -6
- package/src/native/shared/ffi_exports.h +123 -119
- package/src/native/shared/log.h +24 -0
- package/src/native/shared/webview_storage.h +5 -5
- package/src/native/win/native_host_appres.cpp +258 -0
- package/src/native/win/native_host_cef.cpp +834 -0
- package/src/native/win/native_host_ffi.cpp +935 -0
- package/src/native/win/native_host_internal.h +285 -0
- package/src/native/win/native_host_runtime.cpp +286 -0
- package/src/native/win/native_host_utils.cpp +314 -0
- package/src/native/win/process_helper_win.cpp +126 -26
- package/src/preload/runtime.built.js +1 -1
- package/src/preload/runtime.ts +65 -42
- package/src/preload/tsconfig.json +2 -1
- package/src/preload/tsconfig.tsbuildinfo +1 -0
- package/src/preload/webviewElement.ts +307 -0
- package/src/shared/cefVersion.ts +2 -0
- package/src/shared/log.ts +40 -0
- package/src/shared/paths.ts +122 -52
- package/src/shared/rpc.ts +7 -1
- package/src/view/index.ts +8 -5
- package/src/native/shared/cef_response_filter.h +0 -116
- package/src/native/win/native_host.cpp +0 -2453
- package/src/types/config.ts +0 -29
|
@@ -0,0 +1,834 @@
|
|
|
1
|
+
#include "native_host_internal.h"
|
|
2
|
+
|
|
3
|
+
namespace {
|
|
4
|
+
|
|
5
|
+
class BuniteCefApp : public CefApp, public CefBrowserProcessHandler {
|
|
6
|
+
public:
|
|
7
|
+
CefRefPtr<CefBrowserProcessHandler> GetBrowserProcessHandler() override {
|
|
8
|
+
return this;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
void OnBeforeCommandLineProcessing(const CefString&, CefRefPtr<CefCommandLine> command_line) override {
|
|
12
|
+
if (!g_runtime.popup_blocking) {
|
|
13
|
+
command_line->AppendSwitch("disable-popup-blocking");
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
// --- Bunite defaults ---
|
|
17
|
+
if (g_runtime.chromium_flags.find("in-process-gpu") == g_runtime.chromium_flags.end()) {
|
|
18
|
+
g_runtime.chromium_flags["in-process-gpu"] = "true";
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
if (g_runtime.chromium_flags.find("enable-features") == g_runtime.chromium_flags.end()) {
|
|
22
|
+
g_runtime.chromium_flags["enable-features"] = "NetworkServiceInProcess";
|
|
23
|
+
} else {
|
|
24
|
+
std::string& existing = g_runtime.chromium_flags["enable-features"];
|
|
25
|
+
if (existing.find("NetworkServiceInProcess") == std::string::npos && existing != "false") {
|
|
26
|
+
if (existing.empty()) {
|
|
27
|
+
existing = "NetworkServiceInProcess";
|
|
28
|
+
} else {
|
|
29
|
+
existing += ",NetworkServiceInProcess";
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// --- Apply all flags ---
|
|
35
|
+
for (const auto& [key, value] : g_runtime.chromium_flags) {
|
|
36
|
+
if (value == "false") {
|
|
37
|
+
continue;
|
|
38
|
+
}
|
|
39
|
+
if (value.empty() || value == "true") {
|
|
40
|
+
command_line->AppendSwitch(key);
|
|
41
|
+
} else {
|
|
42
|
+
command_line->AppendSwitchWithValue(key, value);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
void OnRegisterCustomSchemes(CefRawPtr<CefSchemeRegistrar> registrar) override {
|
|
48
|
+
registrar->AddCustomScheme(
|
|
49
|
+
"appres",
|
|
50
|
+
CEF_SCHEME_OPTION_STANDARD |
|
|
51
|
+
CEF_SCHEME_OPTION_CORS_ENABLED |
|
|
52
|
+
CEF_SCHEME_OPTION_SECURE |
|
|
53
|
+
CEF_SCHEME_OPTION_CSP_BYPASSING |
|
|
54
|
+
CEF_SCHEME_OPTION_FETCH_ENABLED
|
|
55
|
+
);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
private:
|
|
59
|
+
IMPLEMENT_REFCOUNTING(BuniteCefApp);
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
class BuniteDevToolsClient : public CefClient, public CefLifeSpanHandler {
|
|
63
|
+
public:
|
|
64
|
+
CefRefPtr<CefLifeSpanHandler> GetLifeSpanHandler() override { return this; }
|
|
65
|
+
|
|
66
|
+
void OnAfterCreated(CefRefPtr<CefBrowser>) override {
|
|
67
|
+
CEF_REQUIRE_UI_THREAD();
|
|
68
|
+
g_runtime.devtools_browser_count.fetch_add(1);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
void OnBeforeClose(CefRefPtr<CefBrowser>) override {
|
|
72
|
+
CEF_REQUIRE_UI_THREAD();
|
|
73
|
+
g_runtime.devtools_browser_count.fetch_sub(1);
|
|
74
|
+
bunite_win::maybeCompleteShutdownOnUiThread();
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
private:
|
|
78
|
+
IMPLEMENT_REFCOUNTING(BuniteDevToolsClient);
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
class BuniteCefClient
|
|
82
|
+
: public CefClient,
|
|
83
|
+
public CefLifeSpanHandler,
|
|
84
|
+
public CefLoadHandler,
|
|
85
|
+
public CefRequestHandler,
|
|
86
|
+
public CefResourceRequestHandler,
|
|
87
|
+
public CefPermissionHandler {
|
|
88
|
+
public:
|
|
89
|
+
explicit BuniteCefClient(ViewHost* view)
|
|
90
|
+
: view_(view) {}
|
|
91
|
+
|
|
92
|
+
CefRefPtr<CefLifeSpanHandler> GetLifeSpanHandler() override { return this; }
|
|
93
|
+
CefRefPtr<CefLoadHandler> GetLoadHandler() override { return this; }
|
|
94
|
+
CefRefPtr<CefRequestHandler> GetRequestHandler() override { return this; }
|
|
95
|
+
CefRefPtr<CefPermissionHandler> GetPermissionHandler() override { return this; }
|
|
96
|
+
|
|
97
|
+
void OnBeforeDevToolsPopup(
|
|
98
|
+
CefRefPtr<CefBrowser>,
|
|
99
|
+
CefWindowInfo&,
|
|
100
|
+
CefRefPtr<CefClient>& client,
|
|
101
|
+
CefBrowserSettings&,
|
|
102
|
+
CefRefPtr<CefDictionaryValue>&,
|
|
103
|
+
bool*
|
|
104
|
+
) override {
|
|
105
|
+
// Inject our tracked client so F12, Ctrl+Shift+I, and Inspect Element
|
|
106
|
+
// all go through BuniteDevToolsClient for proper shutdown sequencing.
|
|
107
|
+
client = new BuniteDevToolsClient();
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
CefRefPtr<CefResourceRequestHandler> GetResourceRequestHandler(
|
|
111
|
+
CefRefPtr<CefBrowser>,
|
|
112
|
+
CefRefPtr<CefFrame>,
|
|
113
|
+
CefRefPtr<CefRequest>,
|
|
114
|
+
bool,
|
|
115
|
+
bool,
|
|
116
|
+
const CefString&,
|
|
117
|
+
bool&
|
|
118
|
+
) override {
|
|
119
|
+
return this;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
void OnAfterCreated(CefRefPtr<CefBrowser> browser) override {
|
|
123
|
+
CEF_REQUIRE_UI_THREAD();
|
|
124
|
+
view_->browser = browser;
|
|
125
|
+
{
|
|
126
|
+
std::lock_guard<std::mutex> lock(g_runtime.object_mutex);
|
|
127
|
+
g_runtime.browser_to_view_id[browser->GetIdentifier()] = view_->id;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// View was marked for closing while browser was being created async
|
|
131
|
+
if (view_->closing.load()) {
|
|
132
|
+
browser->GetHost()->CloseBrowser(true);
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
bunite_win::resizeViewToFit(view_);
|
|
137
|
+
|
|
138
|
+
// Apply pending state queued before HWND was available
|
|
139
|
+
HWND browser_hwnd = browser->GetHost()->GetWindowHandle();
|
|
140
|
+
if (browser_hwnd) {
|
|
141
|
+
if (view_->has_pending_bounds) {
|
|
142
|
+
view_->has_pending_bounds = false;
|
|
143
|
+
view_->bounds = view_->pending_bounds;
|
|
144
|
+
SetWindowPos(browser_hwnd, nullptr,
|
|
145
|
+
view_->bounds.left, view_->bounds.top,
|
|
146
|
+
view_->bounds.right - view_->bounds.left,
|
|
147
|
+
view_->bounds.bottom - view_->bounds.top,
|
|
148
|
+
SWP_NOZORDER | SWP_NOACTIVATE);
|
|
149
|
+
}
|
|
150
|
+
if (!view_->pending_visible) {
|
|
151
|
+
ShowWindow(browser_hwnd, SW_HIDE);
|
|
152
|
+
}
|
|
153
|
+
if (view_->pending_bring_to_front) {
|
|
154
|
+
view_->pending_bring_to_front = false;
|
|
155
|
+
SetWindowPos(browser_hwnd, HWND_TOP, 0, 0, 0, 0,
|
|
156
|
+
SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE);
|
|
157
|
+
}
|
|
158
|
+
if (view_->pending_passthrough) {
|
|
159
|
+
EnableWindow(browser_hwnd, FALSE);
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
bunite_win::emitWebviewEvent(view_->id, "view-ready");
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
bool DoClose(CefRefPtr<CefBrowser>) override {
|
|
167
|
+
CEF_REQUIRE_UI_THREAD();
|
|
168
|
+
return false;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
void OnBeforeClose(CefRefPtr<CefBrowser> browser) override {
|
|
172
|
+
CEF_REQUIRE_UI_THREAD();
|
|
173
|
+
bunite_win::removeBrowserMapping(browser->GetIdentifier());
|
|
174
|
+
view_->browser = nullptr;
|
|
175
|
+
if (view_->closing.load()) {
|
|
176
|
+
bunite_win::finalizeViewHost(view_);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
bool OnBeforeBrowse(
|
|
181
|
+
CefRefPtr<CefBrowser>,
|
|
182
|
+
CefRefPtr<CefFrame> frame,
|
|
183
|
+
CefRefPtr<CefRequest> request,
|
|
184
|
+
bool,
|
|
185
|
+
bool
|
|
186
|
+
) override {
|
|
187
|
+
CEF_REQUIRE_UI_THREAD();
|
|
188
|
+
const bool is_main_frame = frame && frame->IsMain();
|
|
189
|
+
const std::string url = request ? request->GetURL().ToString() : "";
|
|
190
|
+
const bool should_allow = !is_main_frame || bunite_win::shouldAllowNavigation(view_, url);
|
|
191
|
+
|
|
192
|
+
if (is_main_frame) {
|
|
193
|
+
bunite_win::emitWebviewEvent(view_->id, "will-navigate", url);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
return !should_allow;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
bool OnOpenURLFromTab(
|
|
200
|
+
CefRefPtr<CefBrowser>,
|
|
201
|
+
CefRefPtr<CefFrame>,
|
|
202
|
+
const CefString& target_url,
|
|
203
|
+
CefRequestHandler::WindowOpenDisposition target_disposition,
|
|
204
|
+
bool
|
|
205
|
+
) override {
|
|
206
|
+
CEF_REQUIRE_UI_THREAD();
|
|
207
|
+
if (target_disposition != CEF_WOD_CURRENT_TAB) {
|
|
208
|
+
bunite_win::emitWebviewEvent(
|
|
209
|
+
view_->id,
|
|
210
|
+
"new-window-open",
|
|
211
|
+
"{\"url\":\"" + bunite_win::escapeJsonString(target_url.ToString()) + "\"}"
|
|
212
|
+
);
|
|
213
|
+
return true;
|
|
214
|
+
}
|
|
215
|
+
return false;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
bool OnBeforePopup(
|
|
219
|
+
CefRefPtr<CefBrowser>,
|
|
220
|
+
CefRefPtr<CefFrame>,
|
|
221
|
+
int,
|
|
222
|
+
const CefString& target_url,
|
|
223
|
+
const CefString&,
|
|
224
|
+
CefLifeSpanHandler::WindowOpenDisposition,
|
|
225
|
+
bool,
|
|
226
|
+
const CefPopupFeatures&,
|
|
227
|
+
CefWindowInfo&,
|
|
228
|
+
CefRefPtr<CefClient>&,
|
|
229
|
+
CefBrowserSettings&,
|
|
230
|
+
CefRefPtr<CefDictionaryValue>&,
|
|
231
|
+
bool*
|
|
232
|
+
) override {
|
|
233
|
+
CEF_REQUIRE_UI_THREAD();
|
|
234
|
+
bunite_win::emitWebviewEvent(
|
|
235
|
+
view_->id,
|
|
236
|
+
"new-window-open",
|
|
237
|
+
"{\"url\":\"" + bunite_win::escapeJsonString(target_url.ToString()) + "\"}"
|
|
238
|
+
);
|
|
239
|
+
return true;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
void OnLoadEnd(CefRefPtr<CefBrowser>, CefRefPtr<CefFrame> frame, int) override {
|
|
243
|
+
CEF_REQUIRE_UI_THREAD();
|
|
244
|
+
if (!frame->IsMain()) {
|
|
245
|
+
return;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
const std::string url = frame->GetURL().ToString();
|
|
249
|
+
bunite_win::emitWebviewEvent(view_->id, "did-navigate", url);
|
|
250
|
+
bunite_win::emitWebviewEvent(view_->id, "dom-ready", url);
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
bool OnShowPermissionPrompt(
|
|
254
|
+
CefRefPtr<CefBrowser>,
|
|
255
|
+
uint64_t,
|
|
256
|
+
const CefString& requesting_origin,
|
|
257
|
+
uint32_t requested_permissions,
|
|
258
|
+
CefRefPtr<CefPermissionPromptCallback> callback
|
|
259
|
+
) override {
|
|
260
|
+
CEF_REQUIRE_UI_THREAD();
|
|
261
|
+
|
|
262
|
+
uint32_t request_id = 0;
|
|
263
|
+
{
|
|
264
|
+
std::lock_guard<std::mutex> lock(g_runtime.permission_mutex);
|
|
265
|
+
request_id = g_runtime.next_permission_request_id++;
|
|
266
|
+
g_runtime.pending_permissions.emplace(
|
|
267
|
+
request_id,
|
|
268
|
+
PendingPermissionRequest{ PermissionRequestKind::Prompt, requested_permissions, callback, nullptr }
|
|
269
|
+
);
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
bunite_win::emitWebviewEvent(
|
|
273
|
+
view_->id,
|
|
274
|
+
"permission-requested",
|
|
275
|
+
"{\"requestId\":" + std::to_string(request_id) +
|
|
276
|
+
",\"kind\":" + std::to_string(requested_permissions) +
|
|
277
|
+
",\"url\":\"" + bunite_win::escapeJsonString(requesting_origin.ToString()) + "\"}"
|
|
278
|
+
);
|
|
279
|
+
return true;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
bool OnRequestMediaAccessPermission(
|
|
283
|
+
CefRefPtr<CefBrowser>,
|
|
284
|
+
CefRefPtr<CefFrame>,
|
|
285
|
+
const CefString& requesting_origin,
|
|
286
|
+
uint32_t requested_permissions,
|
|
287
|
+
CefRefPtr<CefMediaAccessCallback> callback
|
|
288
|
+
) override {
|
|
289
|
+
CEF_REQUIRE_UI_THREAD();
|
|
290
|
+
|
|
291
|
+
uint32_t request_id = 0;
|
|
292
|
+
{
|
|
293
|
+
std::lock_guard<std::mutex> lock(g_runtime.permission_mutex);
|
|
294
|
+
request_id = g_runtime.next_permission_request_id++;
|
|
295
|
+
g_runtime.pending_permissions.emplace(
|
|
296
|
+
request_id,
|
|
297
|
+
PendingPermissionRequest{ PermissionRequestKind::MediaAccess, requested_permissions, nullptr, callback }
|
|
298
|
+
);
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
bunite_win::emitWebviewEvent(
|
|
302
|
+
view_->id,
|
|
303
|
+
"permission-requested",
|
|
304
|
+
"{\"requestId\":" + std::to_string(request_id) +
|
|
305
|
+
",\"kind\":" + std::to_string(requested_permissions) +
|
|
306
|
+
",\"url\":\"" + bunite_win::escapeJsonString(requesting_origin.ToString()) + "\"}"
|
|
307
|
+
);
|
|
308
|
+
return true;
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
private:
|
|
312
|
+
ViewHost* view_;
|
|
313
|
+
|
|
314
|
+
IMPLEMENT_REFCOUNTING(BuniteCefClient);
|
|
315
|
+
};
|
|
316
|
+
|
|
317
|
+
|
|
318
|
+
} // namespace
|
|
319
|
+
|
|
320
|
+
namespace bunite_win {
|
|
321
|
+
|
|
322
|
+
// ---------------------------------------------------------------------------
|
|
323
|
+
// Browser / view / window management
|
|
324
|
+
// ---------------------------------------------------------------------------
|
|
325
|
+
|
|
326
|
+
void removeBrowserMapping(int browser_id) {
|
|
327
|
+
std::lock_guard<std::mutex> lock(g_runtime.object_mutex);
|
|
328
|
+
g_runtime.browser_to_view_id.erase(browser_id);
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
void syncWindowFrame(WindowHost* window) {
|
|
332
|
+
if (!window || !window->hwnd) {
|
|
333
|
+
return;
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
RECT rect{};
|
|
337
|
+
GetWindowRect(window->hwnd, &rect);
|
|
338
|
+
window->frame = rect;
|
|
339
|
+
window->minimized = IsIconic(window->hwnd) != 0;
|
|
340
|
+
window->maximized = IsZoomed(window->hwnd) != 0;
|
|
341
|
+
if (!window->minimized) {
|
|
342
|
+
window->restore_maximized_after_minimize = false;
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
void resizeViewToFit(ViewHost* view) {
|
|
347
|
+
if (!view || !view->window || !view->window->hwnd) {
|
|
348
|
+
return;
|
|
349
|
+
}
|
|
350
|
+
if (view->closing.load()) {
|
|
351
|
+
return;
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
auto browser = view->browser; // CefRefPtr copy — atomic refcount
|
|
355
|
+
if (!browser) {
|
|
356
|
+
return;
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
HWND browser_hwnd = browser->GetHost()->GetWindowHandle();
|
|
360
|
+
if (!browser_hwnd) {
|
|
361
|
+
return;
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
RECT bounds = view->bounds;
|
|
365
|
+
switch (view->anchor_mode) {
|
|
366
|
+
case static_cast<int>(ViewAnchorMode::Fill): {
|
|
367
|
+
GetClientRect(view->window->hwnd, &bounds);
|
|
368
|
+
break;
|
|
369
|
+
}
|
|
370
|
+
case static_cast<int>(ViewAnchorMode::Top): {
|
|
371
|
+
RECT client;
|
|
372
|
+
GetClientRect(view->window->hwnd, &client);
|
|
373
|
+
bounds = { 0, 0, client.right, static_cast<LONG>(view->anchor_inset) };
|
|
374
|
+
break;
|
|
375
|
+
}
|
|
376
|
+
case static_cast<int>(ViewAnchorMode::BelowTop): {
|
|
377
|
+
RECT client;
|
|
378
|
+
GetClientRect(view->window->hwnd, &client);
|
|
379
|
+
LONG inset = static_cast<LONG>(view->anchor_inset);
|
|
380
|
+
LONG h = client.bottom - inset;
|
|
381
|
+
if (h < 0) h = 0;
|
|
382
|
+
bounds = { 0, inset, client.right, inset + h };
|
|
383
|
+
break;
|
|
384
|
+
}
|
|
385
|
+
default: // ViewAnchorMode::None - use stored bounds
|
|
386
|
+
break;
|
|
387
|
+
}
|
|
388
|
+
view->bounds = bounds;
|
|
389
|
+
|
|
390
|
+
SetWindowPos(
|
|
391
|
+
browser_hwnd,
|
|
392
|
+
nullptr,
|
|
393
|
+
bounds.left,
|
|
394
|
+
bounds.top,
|
|
395
|
+
bounds.right - bounds.left,
|
|
396
|
+
bounds.bottom - bounds.top,
|
|
397
|
+
SWP_NOZORDER | SWP_NOACTIVATE
|
|
398
|
+
);
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
void finalizeViewHost(ViewHost* view) {
|
|
402
|
+
if (!view) {
|
|
403
|
+
return;
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
bunite::WebviewContentStorage::instance().remove(view->id);
|
|
407
|
+
|
|
408
|
+
WindowHost* window = nullptr;
|
|
409
|
+
bool window_views_empty = false;
|
|
410
|
+
{
|
|
411
|
+
std::lock_guard<std::mutex> lock(g_runtime.object_mutex);
|
|
412
|
+
g_runtime.views_by_id.erase(view->id);
|
|
413
|
+
if (view->window) {
|
|
414
|
+
window = view->window;
|
|
415
|
+
auto& views = window->views;
|
|
416
|
+
views.erase(std::remove(views.begin(), views.end(), view), views.end());
|
|
417
|
+
window_views_empty = views.empty();
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
delete view;
|
|
422
|
+
|
|
423
|
+
// All views finalized — destroy parent HWND on Win32 thread
|
|
424
|
+
if (window && window->closing.load() && window_views_empty) {
|
|
425
|
+
postUiTask([window]() {
|
|
426
|
+
if (window->hwnd) {
|
|
427
|
+
DestroyWindow(window->hwnd);
|
|
428
|
+
}
|
|
429
|
+
});
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
maybeCompleteShutdownOnUiThread();
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
void closeViewHost(ViewHost* view) {
|
|
436
|
+
if (!view || view->closing.exchange(true)) {
|
|
437
|
+
return;
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
if (view->browser) {
|
|
441
|
+
postCefUiTask([view]() {
|
|
442
|
+
if (!view->browser) {
|
|
443
|
+
return;
|
|
444
|
+
}
|
|
445
|
+
// closeDevToolsForView() bails on closing views — call CloseDevTools directly.
|
|
446
|
+
view->browser->GetHost()->CloseDevTools();
|
|
447
|
+
view->browser->GetHost()->CloseBrowser(true);
|
|
448
|
+
});
|
|
449
|
+
return;
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
// Browser not yet created — OnAfterCreated will check closing flag.
|
|
453
|
+
// Safety net: if CreateBrowser failed entirely, clean up on CEF thread.
|
|
454
|
+
postCefUiTask([view]() {
|
|
455
|
+
if (view->browser) {
|
|
456
|
+
view->browser->GetHost()->CloseBrowser(true);
|
|
457
|
+
} else {
|
|
458
|
+
finalizeViewHost(view);
|
|
459
|
+
}
|
|
460
|
+
});
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
void destroyWindowHost(WindowHost* window) {
|
|
464
|
+
if (!window || !window->hwnd) {
|
|
465
|
+
return;
|
|
466
|
+
}
|
|
467
|
+
if (!window->closing.exchange(true)) {
|
|
468
|
+
emitWindowEvent(window->id, "close");
|
|
469
|
+
std::vector<ViewHost*> views_copy;
|
|
470
|
+
{
|
|
471
|
+
std::lock_guard<std::mutex> lock(g_runtime.object_mutex);
|
|
472
|
+
views_copy = window->views;
|
|
473
|
+
}
|
|
474
|
+
if (views_copy.empty()) {
|
|
475
|
+
// No views — destroy HWND immediately
|
|
476
|
+
DestroyWindow(window->hwnd);
|
|
477
|
+
} else {
|
|
478
|
+
for (auto* view : views_copy) {
|
|
479
|
+
closeViewHost(view);
|
|
480
|
+
}
|
|
481
|
+
// DestroyWindow deferred — finalizeViewHost posts it when last view is gone
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
void finalizeWindowHost(WindowHost* window) {
|
|
487
|
+
if (!window) {
|
|
488
|
+
return;
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
bool all_closed = false;
|
|
492
|
+
{
|
|
493
|
+
std::lock_guard<std::mutex> lock(g_runtime.object_mutex);
|
|
494
|
+
for (auto* view : window->views) {
|
|
495
|
+
if (view) {
|
|
496
|
+
view->window = nullptr;
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
window->views.clear();
|
|
500
|
+
g_runtime.windows_by_id.erase(window->id);
|
|
501
|
+
all_closed = g_runtime.windows_by_id.empty();
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
delete window;
|
|
505
|
+
|
|
506
|
+
if (all_closed && !g_runtime.shutting_down.load()) {
|
|
507
|
+
emitWindowEvent(0, "all-windows-closed");
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
// ---------------------------------------------------------------------------
|
|
512
|
+
// Lookup helpers
|
|
513
|
+
// ---------------------------------------------------------------------------
|
|
514
|
+
|
|
515
|
+
WindowHost* getWindowHostById(uint32_t window_id) {
|
|
516
|
+
std::lock_guard<std::mutex> lock(g_runtime.object_mutex);
|
|
517
|
+
auto it = g_runtime.windows_by_id.find(window_id);
|
|
518
|
+
return it != g_runtime.windows_by_id.end() ? it->second : nullptr;
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
ViewHost* getViewHostById(uint32_t view_id) {
|
|
522
|
+
std::lock_guard<std::mutex> lock(g_runtime.object_mutex);
|
|
523
|
+
auto it = g_runtime.views_by_id.find(view_id);
|
|
524
|
+
return it != g_runtime.views_by_id.end() ? it->second : nullptr;
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
DWORD makeWindowStyle(const std::wstring& title_bar_style) {
|
|
528
|
+
DWORD style = WS_OVERLAPPEDWINDOW | WS_CLIPCHILDREN;
|
|
529
|
+
if (title_bar_style == L"hidden" || title_bar_style == L"hiddenInset") {
|
|
530
|
+
style &= ~WS_CAPTION;
|
|
531
|
+
}
|
|
532
|
+
return style;
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
// ---------------------------------------------------------------------------
|
|
536
|
+
// DevTools
|
|
537
|
+
// ---------------------------------------------------------------------------
|
|
538
|
+
|
|
539
|
+
void openDevToolsForView(ViewHost* view) {
|
|
540
|
+
if (!view || view->closing.load() || !view->browser) {
|
|
541
|
+
return;
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
CefWindowInfo window_info;
|
|
545
|
+
window_info.SetAsPopup(nullptr, "Bunite DevTools");
|
|
546
|
+
|
|
547
|
+
CefBrowserSettings settings;
|
|
548
|
+
view->browser->GetHost()->ShowDevTools(window_info, new BuniteDevToolsClient(), settings, CefPoint());
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
void closeDevToolsForView(ViewHost* view) {
|
|
552
|
+
if (!view || view->closing.load() || !view->browser) {
|
|
553
|
+
return;
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
view->browser->GetHost()->CloseDevTools();
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
void toggleDevToolsForView(ViewHost* view) {
|
|
560
|
+
if (!view || view->closing.load() || !view->browser) {
|
|
561
|
+
return;
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
if (view->browser->GetHost()->HasDevTools()) {
|
|
565
|
+
closeDevToolsForView(view);
|
|
566
|
+
return;
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
openDevToolsForView(view);
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
// ---------------------------------------------------------------------------
|
|
573
|
+
// CEF initialization / shutdown
|
|
574
|
+
// ---------------------------------------------------------------------------
|
|
575
|
+
|
|
576
|
+
bool initializeCefOnUiThread() {
|
|
577
|
+
if (g_runtime.process_helper_path.empty() || g_runtime.cef_dir.empty()) {
|
|
578
|
+
BUNITE_ERROR("Missing process helper or CEF directory.");
|
|
579
|
+
return false;
|
|
580
|
+
}
|
|
581
|
+
if (!registerWindowClasses()) {
|
|
582
|
+
BUNITE_ERROR("Failed to register window classes.");
|
|
583
|
+
return false;
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
const std::filesystem::path cef_root(g_runtime.cef_dir);
|
|
587
|
+
const std::filesystem::path dll_dir = std::filesystem::exists(cef_root / "Release" / "libcef.dll")
|
|
588
|
+
? cef_root / "Release"
|
|
589
|
+
: cef_root;
|
|
590
|
+
const std::filesystem::path resource_dir = std::filesystem::exists(cef_root / "Resources" / "resources.pak")
|
|
591
|
+
? cef_root / "Resources"
|
|
592
|
+
: cef_root;
|
|
593
|
+
const std::filesystem::path locales_dir = std::filesystem::exists(resource_dir / "locales")
|
|
594
|
+
? resource_dir / "locales"
|
|
595
|
+
: (std::filesystem::exists(cef_root / "Resources" / "locales")
|
|
596
|
+
? cef_root / "Resources" / "locales"
|
|
597
|
+
: (std::filesystem::exists(cef_root / "locales") ? cef_root / "locales" : resource_dir / "locales"));
|
|
598
|
+
|
|
599
|
+
// Pre-flight: verify critical CEF files exist
|
|
600
|
+
const std::filesystem::path libcef_path = dll_dir / "libcef.dll";
|
|
601
|
+
const std::filesystem::path icudtl_path = resource_dir / "icudtl.dat";
|
|
602
|
+
const std::filesystem::path resources_pak_path = resource_dir / "resources.pak";
|
|
603
|
+
if (!std::filesystem::exists(libcef_path)) {
|
|
604
|
+
BUNITE_ERROR("libcef.dll not found at: %s", libcef_path.string().c_str());
|
|
605
|
+
return false;
|
|
606
|
+
}
|
|
607
|
+
if (!std::filesystem::exists(icudtl_path)) {
|
|
608
|
+
BUNITE_ERROR("icudtl.dat not found at: %s", icudtl_path.string().c_str());
|
|
609
|
+
return false;
|
|
610
|
+
}
|
|
611
|
+
if (!std::filesystem::exists(resources_pak_path)) {
|
|
612
|
+
BUNITE_ERROR("resources.pak not found at: %s", resources_pak_path.string().c_str());
|
|
613
|
+
return false;
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
// Pre-flight: check for cache lock from another instance
|
|
617
|
+
const char* user_data_dir = std::getenv("BUNITE_USER_DATA_DIR");
|
|
618
|
+
if (user_data_dir) {
|
|
619
|
+
const std::filesystem::path lock_path = std::filesystem::path(user_data_dir) / "lockfile";
|
|
620
|
+
if (std::filesystem::exists(lock_path)) {
|
|
621
|
+
BUNITE_WARN("Cache directory may be locked by another process: %s", user_data_dir);
|
|
622
|
+
}
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
SetDllDirectoryW(dll_dir.native().c_str());
|
|
626
|
+
|
|
627
|
+
CefMainArgs main_args(GetModuleHandleW(nullptr));
|
|
628
|
+
CefRefPtr<BuniteCefApp> app = new BuniteCefApp();
|
|
629
|
+
const int execute_result = CefExecuteProcess(main_args, app, nullptr);
|
|
630
|
+
if (execute_result >= 0) {
|
|
631
|
+
BUNITE_ERROR("CefExecuteProcess exited early with code %d.", execute_result);
|
|
632
|
+
return false;
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
CefSettings settings{};
|
|
636
|
+
settings.no_sandbox = true;
|
|
637
|
+
settings.multi_threaded_message_loop = true;
|
|
638
|
+
settings.external_message_pump = false;
|
|
639
|
+
|
|
640
|
+
CefString(&settings.browser_subprocess_path) = g_runtime.process_helper_path;
|
|
641
|
+
CefString(&settings.resources_dir_path) = resource_dir.wstring();
|
|
642
|
+
CefString(&settings.locales_dir_path) = locales_dir.wstring();
|
|
643
|
+
if (user_data_dir) {
|
|
644
|
+
CefString(&settings.cache_path) = user_data_dir;
|
|
645
|
+
}
|
|
646
|
+
if (const char* remote_debug_port = std::getenv("BUNITE_REMOTE_DEBUGGING_PORT")) {
|
|
647
|
+
const int parsed_port = std::atoi(remote_debug_port);
|
|
648
|
+
if (parsed_port > 0 && parsed_port <= 65535) {
|
|
649
|
+
settings.remote_debugging_port = parsed_port;
|
|
650
|
+
}
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
settings.log_severity = LOGSEVERITY_ERROR;
|
|
654
|
+
CefString(&settings.log_file) = "";
|
|
655
|
+
|
|
656
|
+
if (!CefInitialize(main_args, settings, app, nullptr)) {
|
|
657
|
+
const int exit_code = CefGetExitCode();
|
|
658
|
+
switch (exit_code) {
|
|
659
|
+
case CEF_RESULT_CODE_NORMAL_EXIT_PROCESS_NOTIFIED:
|
|
660
|
+
BUNITE_ERROR("CefInitialize failed: another instance is using the cache directory (%s).",
|
|
661
|
+
user_data_dir ? user_data_dir : "<default>");
|
|
662
|
+
break;
|
|
663
|
+
case CEF_RESULT_CODE_PROFILE_IN_USE:
|
|
664
|
+
BUNITE_ERROR("CefInitialize failed: cache profile is in use (%s).",
|
|
665
|
+
user_data_dir ? user_data_dir : "<default>");
|
|
666
|
+
break;
|
|
667
|
+
case CEF_RESULT_CODE_MISSING_DATA:
|
|
668
|
+
BUNITE_ERROR("CefInitialize failed: critical data files missing (resources_dir=%s).",
|
|
669
|
+
resource_dir.string().c_str());
|
|
670
|
+
break;
|
|
671
|
+
default:
|
|
672
|
+
BUNITE_ERROR("CefInitialize failed with exit code %d.", exit_code);
|
|
673
|
+
break;
|
|
674
|
+
}
|
|
675
|
+
return false;
|
|
676
|
+
}
|
|
677
|
+
|
|
678
|
+
g_runtime.cef_initialized = true;
|
|
679
|
+
registerAppResSchemeHandlers();
|
|
680
|
+
return true;
|
|
681
|
+
}
|
|
682
|
+
|
|
683
|
+
void shutdownCefOnUiThread() {
|
|
684
|
+
if (g_runtime.cef_initialized) {
|
|
685
|
+
CefClearSchemeHandlerFactories();
|
|
686
|
+
CefShutdown();
|
|
687
|
+
g_runtime.cef_initialized = false;
|
|
688
|
+
}
|
|
689
|
+
}
|
|
690
|
+
|
|
691
|
+
void cancelPendingPermissionRequestsOnUiThread() {
|
|
692
|
+
std::vector<PendingPermissionRequest> pending;
|
|
693
|
+
{
|
|
694
|
+
std::lock_guard<std::mutex> lock(g_runtime.permission_mutex);
|
|
695
|
+
for (auto& [_, request] : g_runtime.pending_permissions) {
|
|
696
|
+
pending.push_back(request);
|
|
697
|
+
}
|
|
698
|
+
g_runtime.pending_permissions.clear();
|
|
699
|
+
}
|
|
700
|
+
|
|
701
|
+
for (const auto& request : pending) {
|
|
702
|
+
if (request.kind == PermissionRequestKind::Prompt && request.prompt_callback) {
|
|
703
|
+
request.prompt_callback->Continue(CEF_PERMISSION_RESULT_DENY);
|
|
704
|
+
continue;
|
|
705
|
+
}
|
|
706
|
+
if (request.kind == PermissionRequestKind::MediaAccess && request.media_callback) {
|
|
707
|
+
request.media_callback->Cancel();
|
|
708
|
+
}
|
|
709
|
+
}
|
|
710
|
+
}
|
|
711
|
+
|
|
712
|
+
void cancelPendingRouteRequestsOnUiThread() {
|
|
713
|
+
std::vector<RuntimeState::PendingRouteRequest> pending;
|
|
714
|
+
{
|
|
715
|
+
std::lock_guard<std::mutex> lock(g_runtime.route_mutex);
|
|
716
|
+
for (auto& [_, request] : g_runtime.pending_routes) {
|
|
717
|
+
pending.push_back(std::move(request));
|
|
718
|
+
}
|
|
719
|
+
g_runtime.pending_routes.clear();
|
|
720
|
+
}
|
|
721
|
+
|
|
722
|
+
for (auto& request : pending) {
|
|
723
|
+
if (request.callback) {
|
|
724
|
+
request.callback->Cancel();
|
|
725
|
+
}
|
|
726
|
+
}
|
|
727
|
+
}
|
|
728
|
+
|
|
729
|
+
void maybeCompleteShutdownOnUiThread() {
|
|
730
|
+
if (!g_runtime.shutting_down.load()) {
|
|
731
|
+
return;
|
|
732
|
+
}
|
|
733
|
+
|
|
734
|
+
{
|
|
735
|
+
std::lock_guard<std::mutex> lock(g_runtime.object_mutex);
|
|
736
|
+
if (!g_runtime.views_by_id.empty()) {
|
|
737
|
+
return;
|
|
738
|
+
}
|
|
739
|
+
}
|
|
740
|
+
|
|
741
|
+
if (g_runtime.devtools_browser_count.load() > 0) {
|
|
742
|
+
return;
|
|
743
|
+
}
|
|
744
|
+
|
|
745
|
+
if (g_runtime.shutdown_finalize_posted.exchange(true)) {
|
|
746
|
+
return;
|
|
747
|
+
}
|
|
748
|
+
if (g_runtime.message_window) {
|
|
749
|
+
PostMessageW(g_runtime.message_window, kFinalizeShutdownMessage, 0, 0);
|
|
750
|
+
}
|
|
751
|
+
}
|
|
752
|
+
|
|
753
|
+
void closeAllWindowsForShutdown() {
|
|
754
|
+
std::vector<WindowHost*> windows;
|
|
755
|
+
std::vector<ViewHost*> orphan_views;
|
|
756
|
+
{
|
|
757
|
+
std::lock_guard<std::mutex> lock(g_runtime.object_mutex);
|
|
758
|
+
for (const auto& [_, window] : g_runtime.windows_by_id) {
|
|
759
|
+
windows.push_back(window);
|
|
760
|
+
}
|
|
761
|
+
for (const auto& [_, view] : g_runtime.views_by_id) {
|
|
762
|
+
if (!view->window) {
|
|
763
|
+
orphan_views.push_back(view);
|
|
764
|
+
}
|
|
765
|
+
}
|
|
766
|
+
}
|
|
767
|
+
|
|
768
|
+
for (auto* window : windows) {
|
|
769
|
+
destroyWindowHost(window);
|
|
770
|
+
}
|
|
771
|
+
|
|
772
|
+
for (auto* view : orphan_views) {
|
|
773
|
+
closeViewHost(view);
|
|
774
|
+
}
|
|
775
|
+
}
|
|
776
|
+
|
|
777
|
+
// ---------------------------------------------------------------------------
|
|
778
|
+
// Browser creation
|
|
779
|
+
// ---------------------------------------------------------------------------
|
|
780
|
+
|
|
781
|
+
bool createBrowserForView(ViewHost* view) {
|
|
782
|
+
auto* window = view->window;
|
|
783
|
+
if (!window || !window->hwnd) {
|
|
784
|
+
return false;
|
|
785
|
+
}
|
|
786
|
+
|
|
787
|
+
view->client = new BuniteCefClient(view);
|
|
788
|
+
if (!view->html.empty()) {
|
|
789
|
+
bunite::WebviewContentStorage::instance().set(view->id, view->html);
|
|
790
|
+
}
|
|
791
|
+
|
|
792
|
+
const std::string initial_url = !view->html.empty()
|
|
793
|
+
? "appres://app.internal/internal/index.html"
|
|
794
|
+
: (!view->url.empty() ? view->url : "about:blank");
|
|
795
|
+
|
|
796
|
+
CefWindowInfo window_info;
|
|
797
|
+
CefRect child_bounds(
|
|
798
|
+
view->bounds.left,
|
|
799
|
+
view->bounds.top,
|
|
800
|
+
view->bounds.right - view->bounds.left,
|
|
801
|
+
view->bounds.bottom - view->bounds.top
|
|
802
|
+
);
|
|
803
|
+
window_info.SetAsChild(window->hwnd, child_bounds);
|
|
804
|
+
|
|
805
|
+
CefBrowserSettings browser_settings;
|
|
806
|
+
|
|
807
|
+
CefRefPtr<CefDictionaryValue> extra_info;
|
|
808
|
+
if (!view->preload_script.empty() || !view->preload_origins.empty()) {
|
|
809
|
+
extra_info = CefDictionaryValue::Create();
|
|
810
|
+
if (!view->preload_script.empty()) {
|
|
811
|
+
extra_info->SetString("preloadScript", view->preload_script);
|
|
812
|
+
}
|
|
813
|
+
if (!view->preload_origins.empty()) {
|
|
814
|
+
auto list = CefListValue::Create();
|
|
815
|
+
for (size_t i = 0; i < view->preload_origins.size(); ++i) {
|
|
816
|
+
list->SetString(i, view->preload_origins[i]);
|
|
817
|
+
}
|
|
818
|
+
extra_info->SetList("preloadOrigins", list);
|
|
819
|
+
}
|
|
820
|
+
}
|
|
821
|
+
|
|
822
|
+
// CreateBrowser (async) — can be called from any browser process thread.
|
|
823
|
+
// Browser instance will be available in OnAfterCreated callback.
|
|
824
|
+
return CefBrowserHost::CreateBrowser(
|
|
825
|
+
window_info,
|
|
826
|
+
view->client,
|
|
827
|
+
initial_url,
|
|
828
|
+
browser_settings,
|
|
829
|
+
extra_info,
|
|
830
|
+
nullptr
|
|
831
|
+
);
|
|
832
|
+
}
|
|
833
|
+
|
|
834
|
+
} // namespace bunite_win
|