bunite-core 0.0.1
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 +24 -0
- package/src/bun/core/App.ts +142 -0
- package/src/bun/core/BrowserView.ts +262 -0
- package/src/bun/core/BrowserWindow.ts +322 -0
- package/src/bun/core/Socket.ts +186 -0
- package/src/bun/core/Utils.ts +72 -0
- package/src/bun/core/windowIds.ts +7 -0
- package/src/bun/events/appEvents.ts +7 -0
- package/src/bun/events/event.ts +20 -0
- package/src/bun/events/eventEmitter.ts +28 -0
- package/src/bun/events/webviewEvents.ts +13 -0
- package/src/bun/events/windowEvents.ts +19 -0
- package/src/bun/index.ts +41 -0
- package/src/bun/preload/index.ts +73 -0
- package/src/bun/preload/inline.ts +87 -0
- package/src/bun/proc/native.ts +666 -0
- package/src/native/shared/callbacks.h +6 -0
- package/src/native/shared/cef_response_filter.h +116 -0
- package/src/native/shared/ffi_exports.h +119 -0
- package/src/native/shared/webview_storage.h +89 -0
- package/src/native/win/native_host.cpp +2453 -0
- package/src/native/win/process_helper_win.cpp +26 -0
- package/src/preload/runtime.built.js +1 -0
- package/src/preload/runtime.ts +215 -0
- package/src/preload/tsconfig.json +13 -0
- package/src/shared/paths.ts +133 -0
- package/src/shared/platform.ts +9 -0
- package/src/shared/rpc.ts +399 -0
- package/src/shared/rpcWire.ts +54 -0
- package/src/shared/rpcWireConstants.ts +3 -0
- package/src/types/config.ts +29 -0
- package/src/view/index.ts +159 -0
|
@@ -0,0 +1,2453 @@
|
|
|
1
|
+
#include "../shared/ffi_exports.h"
|
|
2
|
+
|
|
3
|
+
#include <windows.h>
|
|
4
|
+
|
|
5
|
+
#include <algorithm>
|
|
6
|
+
#include <atomic>
|
|
7
|
+
#include <chrono>
|
|
8
|
+
#include <cctype>
|
|
9
|
+
#include <condition_variable>
|
|
10
|
+
#include <cstdint>
|
|
11
|
+
#include <cstdio>
|
|
12
|
+
#include <cstring>
|
|
13
|
+
#include <cstdlib>
|
|
14
|
+
#include <filesystem>
|
|
15
|
+
#include <fstream>
|
|
16
|
+
#include <functional>
|
|
17
|
+
#include <future>
|
|
18
|
+
#include <map>
|
|
19
|
+
#include <memory>
|
|
20
|
+
#include <mutex>
|
|
21
|
+
#include <optional>
|
|
22
|
+
#include <queue>
|
|
23
|
+
#include <sstream>
|
|
24
|
+
#include <string>
|
|
25
|
+
#include <thread>
|
|
26
|
+
#include <vector>
|
|
27
|
+
|
|
28
|
+
#include "include/cef_app.h"
|
|
29
|
+
#include "include/cef_browser.h"
|
|
30
|
+
#include "include/cef_client.h"
|
|
31
|
+
#include "include/cef_command_line.h"
|
|
32
|
+
#include "include/cef_parser.h"
|
|
33
|
+
#include "include/cef_permission_handler.h"
|
|
34
|
+
#include "include/cef_resource_handler.h"
|
|
35
|
+
#include "include/cef_resource_request_handler.h"
|
|
36
|
+
#include "include/cef_scheme.h"
|
|
37
|
+
#include "include/cef_task.h"
|
|
38
|
+
#include "include/wrapper/cef_helpers.h"
|
|
39
|
+
|
|
40
|
+
#include "../shared/cef_response_filter.h"
|
|
41
|
+
#include "../shared/webview_storage.h"
|
|
42
|
+
|
|
43
|
+
namespace {
|
|
44
|
+
|
|
45
|
+
constexpr wchar_t kMessageWindowClass[] = L"BuniteMessageWindow";
|
|
46
|
+
constexpr wchar_t kWindowClass[] = L"BuniteWindowClass";
|
|
47
|
+
constexpr UINT kRunQueuedTaskMessage = WM_APP + 1;
|
|
48
|
+
|
|
49
|
+
struct WindowHost;
|
|
50
|
+
struct ViewHost;
|
|
51
|
+
class BuniteCefClient;
|
|
52
|
+
|
|
53
|
+
enum class PermissionRequestKind {
|
|
54
|
+
Prompt,
|
|
55
|
+
MediaAccess
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
struct PendingPermissionRequest {
|
|
59
|
+
PermissionRequestKind kind;
|
|
60
|
+
uint32_t permissions = 0;
|
|
61
|
+
CefRefPtr<CefPermissionPromptCallback> prompt_callback;
|
|
62
|
+
CefRefPtr<CefMediaAccessCallback> media_callback;
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
struct PendingMessageBoxRequest {
|
|
66
|
+
uint32_t view_id = 0;
|
|
67
|
+
int32_t cancel_id = -1;
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
// View anchor modes for automatic layout during window resize.
|
|
71
|
+
// 0 = none (manual bounds), 1 = fill client area,
|
|
72
|
+
// 2 = top strip (fixed height), 3 = below top strip.
|
|
73
|
+
enum ViewAnchorMode { ANCHOR_NONE = 0, ANCHOR_FILL = 1, ANCHOR_TOP = 2, ANCHOR_BELOW_TOP = 3 };
|
|
74
|
+
|
|
75
|
+
struct ViewHost {
|
|
76
|
+
uint32_t id = 0;
|
|
77
|
+
WindowHost* window = nullptr;
|
|
78
|
+
RECT bounds{0, 0, 0, 0};
|
|
79
|
+
std::string url;
|
|
80
|
+
std::string html;
|
|
81
|
+
std::string preload_script;
|
|
82
|
+
std::string views_root;
|
|
83
|
+
std::vector<std::string> navigation_rules;
|
|
84
|
+
int anchor_mode = ANCHOR_FILL;
|
|
85
|
+
double anchor_inset = 0;
|
|
86
|
+
bool sandbox = false;
|
|
87
|
+
std::atomic<bool> closing = false;
|
|
88
|
+
CefRefPtr<CefBrowser> browser;
|
|
89
|
+
CefRefPtr<BuniteCefClient> client;
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
struct WindowHost {
|
|
93
|
+
uint32_t id = 0;
|
|
94
|
+
HWND hwnd = nullptr;
|
|
95
|
+
std::wstring title;
|
|
96
|
+
std::wstring title_bar_style;
|
|
97
|
+
RECT frame{0, 0, 0, 0};
|
|
98
|
+
bool transparent = false;
|
|
99
|
+
bool hidden = false;
|
|
100
|
+
bool minimized = false;
|
|
101
|
+
bool maximized = false;
|
|
102
|
+
bool restore_maximized_after_minimize = false;
|
|
103
|
+
std::atomic<bool> closing = false;
|
|
104
|
+
std::vector<ViewHost*> views;
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
struct RuntimeState {
|
|
108
|
+
std::mutex lifecycle_mutex;
|
|
109
|
+
std::condition_variable lifecycle_cv;
|
|
110
|
+
bool init_finished = false;
|
|
111
|
+
bool init_success = false;
|
|
112
|
+
bool initialized = false;
|
|
113
|
+
bool shutting_down = false;
|
|
114
|
+
|
|
115
|
+
std::thread ui_thread;
|
|
116
|
+
DWORD ui_thread_id = 0;
|
|
117
|
+
HWND message_window = nullptr;
|
|
118
|
+
|
|
119
|
+
std::mutex task_mutex;
|
|
120
|
+
std::queue<std::function<void()>> queued_tasks;
|
|
121
|
+
|
|
122
|
+
std::mutex object_mutex;
|
|
123
|
+
std::map<uint32_t, WindowHost*> windows_by_id;
|
|
124
|
+
std::map<uint32_t, ViewHost*> views_by_id;
|
|
125
|
+
std::map<int, uint32_t> browser_to_view_id;
|
|
126
|
+
|
|
127
|
+
std::mutex permission_mutex;
|
|
128
|
+
std::map<uint32_t, PendingPermissionRequest> pending_permissions;
|
|
129
|
+
uint32_t next_permission_request_id = 1;
|
|
130
|
+
|
|
131
|
+
std::mutex message_box_mutex;
|
|
132
|
+
std::map<uint32_t, PendingMessageBoxRequest> pending_message_boxes;
|
|
133
|
+
uint32_t next_message_box_request_id = 1;
|
|
134
|
+
|
|
135
|
+
std::mutex route_mutex;
|
|
136
|
+
struct PendingRouteRequest {
|
|
137
|
+
std::string path;
|
|
138
|
+
CefRefPtr<CefCallback> callback;
|
|
139
|
+
CefRefPtr<CefRequest> cef_request;
|
|
140
|
+
};
|
|
141
|
+
std::map<uint32_t, PendingRouteRequest> pending_routes;
|
|
142
|
+
uint32_t next_route_request_id = 1;
|
|
143
|
+
|
|
144
|
+
BuniteWebviewEventHandler webview_event_handler = nullptr;
|
|
145
|
+
BuniteWindowEventHandler window_event_handler = nullptr;
|
|
146
|
+
|
|
147
|
+
bool cef_initialized = false;
|
|
148
|
+
std::string process_helper_path;
|
|
149
|
+
std::string cef_dir;
|
|
150
|
+
bool popup_blocking = false;
|
|
151
|
+
std::map<std::string, std::string> chromium_flags;
|
|
152
|
+
std::thread::id cef_owner_thread;
|
|
153
|
+
};
|
|
154
|
+
|
|
155
|
+
RuntimeState g_runtime;
|
|
156
|
+
|
|
157
|
+
void emitWindowEvent(uint32_t window_id, const char* event_name, const std::string& payload = {});
|
|
158
|
+
void emitWebviewEvent(uint32_t view_id, const char* event_name, const std::string& payload = {});
|
|
159
|
+
|
|
160
|
+
std::wstring utf8ToWide(const std::string& value) {
|
|
161
|
+
if (value.empty()) {
|
|
162
|
+
return {};
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
const int required = MultiByteToWideChar(CP_UTF8, 0, value.c_str(), -1, nullptr, 0);
|
|
166
|
+
if (required <= 0) {
|
|
167
|
+
return {};
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
std::wstring converted(required - 1, L'\0');
|
|
171
|
+
MultiByteToWideChar(CP_UTF8, 0, value.c_str(), -1, converted.data(), required);
|
|
172
|
+
return converted;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
std::string escapeJsonString(const std::string& value) {
|
|
176
|
+
std::string escaped;
|
|
177
|
+
escaped.reserve(value.size());
|
|
178
|
+
|
|
179
|
+
for (const unsigned char ch : value) {
|
|
180
|
+
switch (ch) {
|
|
181
|
+
case '\\':
|
|
182
|
+
escaped += "\\\\";
|
|
183
|
+
break;
|
|
184
|
+
case '"':
|
|
185
|
+
escaped += "\\\"";
|
|
186
|
+
break;
|
|
187
|
+
case '\n':
|
|
188
|
+
escaped += "\\n";
|
|
189
|
+
break;
|
|
190
|
+
case '\r':
|
|
191
|
+
escaped += "\\r";
|
|
192
|
+
break;
|
|
193
|
+
case '\t':
|
|
194
|
+
escaped += "\\t";
|
|
195
|
+
break;
|
|
196
|
+
default:
|
|
197
|
+
if (ch < 0x20) {
|
|
198
|
+
char buffer[7];
|
|
199
|
+
std::snprintf(buffer, sizeof(buffer), "\\u%04x", ch);
|
|
200
|
+
escaped += buffer;
|
|
201
|
+
} else {
|
|
202
|
+
escaped.push_back(static_cast<char>(ch));
|
|
203
|
+
}
|
|
204
|
+
break;
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
return escaped;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
std::vector<std::string> splitButtonLabels(const std::string& buttons_csv) {
|
|
212
|
+
std::vector<std::string> labels;
|
|
213
|
+
if (buttons_csv.empty()) {
|
|
214
|
+
return labels;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
std::stringstream stream(buttons_csv);
|
|
218
|
+
std::string label;
|
|
219
|
+
while (std::getline(stream, label, '\x1f')) {
|
|
220
|
+
const size_t first = label.find_first_not_of(" \t");
|
|
221
|
+
if (first == std::string::npos) {
|
|
222
|
+
continue;
|
|
223
|
+
}
|
|
224
|
+
const size_t last = label.find_last_not_of(" \t");
|
|
225
|
+
std::string normalized = label.substr(first, last - first + 1);
|
|
226
|
+
std::transform(
|
|
227
|
+
normalized.begin(),
|
|
228
|
+
normalized.end(),
|
|
229
|
+
normalized.begin(),
|
|
230
|
+
[](unsigned char ch) { return static_cast<char>(std::tolower(ch)); }
|
|
231
|
+
);
|
|
232
|
+
if (!normalized.empty()) {
|
|
233
|
+
labels.push_back(normalized);
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
return labels;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
std::string trimAsciiWhitespace(const std::string& value) {
|
|
241
|
+
const size_t first = value.find_first_not_of(" \t\r\n");
|
|
242
|
+
if (first == std::string::npos) {
|
|
243
|
+
return {};
|
|
244
|
+
}
|
|
245
|
+
const size_t last = value.find_last_not_of(" \t\r\n");
|
|
246
|
+
return value.substr(first, last - first + 1);
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
std::string toLowerAscii(std::string value) {
|
|
250
|
+
std::transform(
|
|
251
|
+
value.begin(),
|
|
252
|
+
value.end(),
|
|
253
|
+
value.begin(),
|
|
254
|
+
[](unsigned char ch) { return static_cast<char>(std::tolower(ch)); }
|
|
255
|
+
);
|
|
256
|
+
return value;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
std::string buildButtonLabelsJson(const std::vector<std::string>& labels) {
|
|
260
|
+
std::string json = "[";
|
|
261
|
+
for (size_t index = 0; index < labels.size(); index += 1) {
|
|
262
|
+
if (index > 0) {
|
|
263
|
+
json += ",";
|
|
264
|
+
}
|
|
265
|
+
json += "\"";
|
|
266
|
+
json += escapeJsonString(labels[index]);
|
|
267
|
+
json += "\"";
|
|
268
|
+
}
|
|
269
|
+
json += "]";
|
|
270
|
+
return json;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
std::optional<std::pair<uint32_t, int32_t>> parseMessageBoxResponseUrl(const std::string& url) {
|
|
274
|
+
constexpr std::string_view prefix = "views://internal/__bunite/message-box-response";
|
|
275
|
+
if (url.rfind(prefix.data(), 0) != 0) {
|
|
276
|
+
return std::nullopt;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
const size_t query_pos = url.find('?');
|
|
280
|
+
if (query_pos == std::string::npos || query_pos + 1 >= url.size()) {
|
|
281
|
+
return std::nullopt;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
uint32_t request_id = 0;
|
|
285
|
+
int32_t response = -1;
|
|
286
|
+
bool has_request_id = false;
|
|
287
|
+
bool has_response = false;
|
|
288
|
+
|
|
289
|
+
std::stringstream stream(url.substr(query_pos + 1));
|
|
290
|
+
std::string pair;
|
|
291
|
+
while (std::getline(stream, pair, '&')) {
|
|
292
|
+
const size_t equals_pos = pair.find('=');
|
|
293
|
+
if (equals_pos == std::string::npos) {
|
|
294
|
+
continue;
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
const std::string key = pair.substr(0, equals_pos);
|
|
298
|
+
const std::string value = pair.substr(equals_pos + 1);
|
|
299
|
+
if (key == "requestId") {
|
|
300
|
+
request_id = static_cast<uint32_t>(std::strtoul(value.c_str(), nullptr, 10));
|
|
301
|
+
has_request_id = true;
|
|
302
|
+
} else if (key == "response") {
|
|
303
|
+
response = static_cast<int32_t>(std::strtol(value.c_str(), nullptr, 10));
|
|
304
|
+
has_response = true;
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
if (!has_request_id || !has_response) {
|
|
309
|
+
return std::nullopt;
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
return std::make_pair(request_id, response);
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
bool tryResolvePendingMessageBoxRequest(uint32_t view_id, uint32_t request_id, int32_t response) {
|
|
316
|
+
std::optional<PendingMessageBoxRequest> request;
|
|
317
|
+
{
|
|
318
|
+
std::lock_guard<std::mutex> lock(g_runtime.message_box_mutex);
|
|
319
|
+
const auto it = g_runtime.pending_message_boxes.find(request_id);
|
|
320
|
+
if (it == g_runtime.pending_message_boxes.end()) {
|
|
321
|
+
return false;
|
|
322
|
+
}
|
|
323
|
+
request = it->second;
|
|
324
|
+
g_runtime.pending_message_boxes.erase(it);
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
if (!request || request->view_id != view_id) {
|
|
328
|
+
return false;
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
emitWebviewEvent(
|
|
332
|
+
view_id,
|
|
333
|
+
"message-box-response",
|
|
334
|
+
"{\"requestId\":" + std::to_string(request_id) +
|
|
335
|
+
",\"response\":" + std::to_string(response) + "}"
|
|
336
|
+
);
|
|
337
|
+
return true;
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
void cancelPendingMessageBoxesForView(uint32_t view_id) {
|
|
341
|
+
std::vector<std::pair<uint32_t, int32_t>> pending;
|
|
342
|
+
{
|
|
343
|
+
std::lock_guard<std::mutex> lock(g_runtime.message_box_mutex);
|
|
344
|
+
for (const auto& [request_id, request] : g_runtime.pending_message_boxes) {
|
|
345
|
+
if (request.view_id == view_id) {
|
|
346
|
+
pending.emplace_back(request_id, request.cancel_id);
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
for (const auto& [request_id, _] : pending) {
|
|
350
|
+
g_runtime.pending_message_boxes.erase(request_id);
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
for (const auto& [request_id, cancel_id] : pending) {
|
|
355
|
+
emitWebviewEvent(
|
|
356
|
+
view_id,
|
|
357
|
+
"message-box-response",
|
|
358
|
+
"{\"requestId\":" + std::to_string(request_id) +
|
|
359
|
+
",\"response\":" + std::to_string(cancel_id >= 0 ? cancel_id : -1) + "}"
|
|
360
|
+
);
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
void cancelPendingMessageBoxRequest(uint32_t request_id) {
|
|
365
|
+
std::lock_guard<std::mutex> lock(g_runtime.message_box_mutex);
|
|
366
|
+
g_runtime.pending_message_boxes.erase(request_id);
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
ViewHost* getPreferredMessageBoxView() {
|
|
370
|
+
std::lock_guard<std::mutex> lock(g_runtime.object_mutex);
|
|
371
|
+
const HWND foreground = GetForegroundWindow();
|
|
372
|
+
|
|
373
|
+
if (foreground) {
|
|
374
|
+
for (const auto& [_, window] : g_runtime.windows_by_id) {
|
|
375
|
+
if (!window || window->hwnd != foreground) {
|
|
376
|
+
continue;
|
|
377
|
+
}
|
|
378
|
+
for (auto* view : window->views) {
|
|
379
|
+
if (view && view->browser && !view->closing.load()) {
|
|
380
|
+
return view;
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
for (const auto& [_, window] : g_runtime.windows_by_id) {
|
|
387
|
+
if (!window || window->hidden) {
|
|
388
|
+
continue;
|
|
389
|
+
}
|
|
390
|
+
for (auto* view : window->views) {
|
|
391
|
+
if (view && view->browser && !view->closing.load()) {
|
|
392
|
+
return view;
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
for (const auto& [_, view] : g_runtime.views_by_id) {
|
|
398
|
+
if (view && view->browser && !view->closing.load()) {
|
|
399
|
+
return view;
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
return nullptr;
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
std::string buildBrowserMessageBoxScript(
|
|
407
|
+
uint32_t request_id,
|
|
408
|
+
const std::string& type,
|
|
409
|
+
const std::string& title,
|
|
410
|
+
const std::string& message,
|
|
411
|
+
const std::string& detail,
|
|
412
|
+
const std::vector<std::string>& buttons,
|
|
413
|
+
int32_t default_id,
|
|
414
|
+
int32_t cancel_id
|
|
415
|
+
) {
|
|
416
|
+
const int32_t safe_default_id =
|
|
417
|
+
buttons.empty() ? 0 : std::clamp<int32_t>(default_id, 0, static_cast<int32_t>(buttons.size() - 1));
|
|
418
|
+
const int32_t safe_cancel_id =
|
|
419
|
+
cancel_id >= 0 && !buttons.empty()
|
|
420
|
+
? std::clamp<int32_t>(cancel_id, 0, static_cast<int32_t>(buttons.size() - 1))
|
|
421
|
+
: cancel_id;
|
|
422
|
+
|
|
423
|
+
return R"JS((() => {
|
|
424
|
+
const spec = {
|
|
425
|
+
requestId: )JS" + std::to_string(request_id) + R"JS(,
|
|
426
|
+
type: ")JS" + escapeJsonString(type) + R"JS(",
|
|
427
|
+
title: ")JS" + escapeJsonString(title) + R"JS(",
|
|
428
|
+
message: ")JS" + escapeJsonString(message) + R"JS(",
|
|
429
|
+
detail: ")JS" + escapeJsonString(detail) + R"JS(",
|
|
430
|
+
buttons: )JS" + buildButtonLabelsJson(buttons) + R"JS(,
|
|
431
|
+
defaultId: )JS" + std::to_string(safe_default_id) + R"JS(,
|
|
432
|
+
cancelId: )JS" + std::to_string(safe_cancel_id) + R"JS(
|
|
433
|
+
};
|
|
434
|
+
const rootId = `__bunite_message_box_${spec.requestId}`;
|
|
435
|
+
if (document.getElementById(rootId)) {
|
|
436
|
+
return;
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
const submit = (response) => {
|
|
440
|
+
const params = new URLSearchParams({
|
|
441
|
+
requestId: String(spec.requestId),
|
|
442
|
+
response: String(response)
|
|
443
|
+
});
|
|
444
|
+
fetch(`views://internal/__bunite/message-box-response?${params.toString()}`, {
|
|
445
|
+
method: "GET",
|
|
446
|
+
cache: "no-store"
|
|
447
|
+
}).catch(() => {});
|
|
448
|
+
};
|
|
449
|
+
|
|
450
|
+
const mount = () => {
|
|
451
|
+
const host = document.body ?? document.documentElement;
|
|
452
|
+
if (!host) {
|
|
453
|
+
return;
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
const overlay = document.createElement("div");
|
|
457
|
+
overlay.id = rootId;
|
|
458
|
+
overlay.dataset.buniteMessageBox = "true";
|
|
459
|
+
overlay.dataset.buniteMessageBoxRequestId = String(spec.requestId);
|
|
460
|
+
overlay.tabIndex = -1;
|
|
461
|
+
overlay.style.cssText = [
|
|
462
|
+
"position:fixed",
|
|
463
|
+
"inset:0",
|
|
464
|
+
"display:flex",
|
|
465
|
+
"align-items:center",
|
|
466
|
+
"justify-content:center",
|
|
467
|
+
"padding:24px",
|
|
468
|
+
"background:rgba(15,23,42,0.42)",
|
|
469
|
+
"backdrop-filter:blur(6px)",
|
|
470
|
+
"z-index:2147483647",
|
|
471
|
+
"font-family:Segoe UI, Arial, sans-serif"
|
|
472
|
+
].join(";");
|
|
473
|
+
|
|
474
|
+
const panel = document.createElement("div");
|
|
475
|
+
panel.style.cssText = [
|
|
476
|
+
"width:min(480px, calc(100vw - 48px))",
|
|
477
|
+
"border-radius:16px",
|
|
478
|
+
"border:1px solid rgba(15,23,42,0.10)",
|
|
479
|
+
"background:#ffffff",
|
|
480
|
+
"box-shadow:0 24px 80px rgba(15,23,42,0.28)",
|
|
481
|
+
"padding:20px 20px 18px",
|
|
482
|
+
"color:#0f172a"
|
|
483
|
+
].join(";");
|
|
484
|
+
|
|
485
|
+
const accent = document.createElement("div");
|
|
486
|
+
const accentColor =
|
|
487
|
+
spec.type === "error" ? "#dc2626" :
|
|
488
|
+
spec.type === "warning" ? "#d97706" :
|
|
489
|
+
spec.type === "question" ? "#2563eb" :
|
|
490
|
+
"#0f766e";
|
|
491
|
+
accent.style.cssText = `width:48px;height:4px;border-radius:999px;background:${accentColor};margin-bottom:14px;`;
|
|
492
|
+
panel.appendChild(accent);
|
|
493
|
+
|
|
494
|
+
if (spec.title) {
|
|
495
|
+
const heading = document.createElement("h1");
|
|
496
|
+
heading.textContent = spec.title;
|
|
497
|
+
heading.style.cssText = "margin:0 0 8px;font-size:20px;line-height:1.25;font-weight:700;";
|
|
498
|
+
panel.appendChild(heading);
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
if (spec.message) {
|
|
502
|
+
const body = document.createElement("p");
|
|
503
|
+
body.textContent = spec.message;
|
|
504
|
+
body.style.cssText = "margin:0;font-size:14px;line-height:1.55;white-space:pre-wrap;";
|
|
505
|
+
panel.appendChild(body);
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
if (spec.detail) {
|
|
509
|
+
const detail = document.createElement("p");
|
|
510
|
+
detail.textContent = spec.detail;
|
|
511
|
+
detail.style.cssText = "margin:10px 0 0;font-size:12px;line-height:1.55;color:#475569;white-space:pre-wrap;";
|
|
512
|
+
panel.appendChild(detail);
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
const buttonRow = document.createElement("div");
|
|
516
|
+
buttonRow.style.cssText = "display:flex;justify-content:flex-end;gap:10px;flex-wrap:wrap;margin-top:18px;";
|
|
517
|
+
spec.buttons.forEach((label, index) => {
|
|
518
|
+
const button = document.createElement("button");
|
|
519
|
+
button.type = "button";
|
|
520
|
+
button.textContent = label;
|
|
521
|
+
button.dataset.buniteMessageBoxButtonIndex = String(index);
|
|
522
|
+
button.style.cssText =
|
|
523
|
+
index === spec.defaultId
|
|
524
|
+
? "appearance:none;border:0;border-radius:999px;background:#111827;color:#ffffff;padding:10px 16px;font:600 13px Segoe UI, Arial, sans-serif;cursor:pointer;"
|
|
525
|
+
: "appearance:none;border:1px solid rgba(15,23,42,0.14);border-radius:999px;background:#f8fafc;color:#0f172a;padding:10px 16px;font:600 13px Segoe UI, Arial, sans-serif;cursor:pointer;";
|
|
526
|
+
button.addEventListener("click", () => {
|
|
527
|
+
overlay.remove();
|
|
528
|
+
submit(index);
|
|
529
|
+
});
|
|
530
|
+
buttonRow.appendChild(button);
|
|
531
|
+
});
|
|
532
|
+
panel.appendChild(buttonRow);
|
|
533
|
+
overlay.appendChild(panel);
|
|
534
|
+
host.appendChild(overlay);
|
|
535
|
+
|
|
536
|
+
overlay.addEventListener("click", (event) => {
|
|
537
|
+
if (event.target !== overlay) {
|
|
538
|
+
return;
|
|
539
|
+
}
|
|
540
|
+
overlay.remove();
|
|
541
|
+
submit(spec.cancelId >= 0 ? spec.cancelId : spec.defaultId);
|
|
542
|
+
});
|
|
543
|
+
|
|
544
|
+
overlay.addEventListener("keydown", (event) => {
|
|
545
|
+
if (event.key !== "Escape") {
|
|
546
|
+
return;
|
|
547
|
+
}
|
|
548
|
+
event.preventDefault();
|
|
549
|
+
overlay.remove();
|
|
550
|
+
submit(spec.cancelId >= 0 ? spec.cancelId : spec.defaultId);
|
|
551
|
+
});
|
|
552
|
+
|
|
553
|
+
requestAnimationFrame(() => {
|
|
554
|
+
overlay.focus();
|
|
555
|
+
const defaultButton = overlay.querySelector(`[data-bunite-message-box-button-index="${spec.defaultId}"]`);
|
|
556
|
+
if (defaultButton instanceof HTMLButtonElement) {
|
|
557
|
+
defaultButton.focus();
|
|
558
|
+
}
|
|
559
|
+
});
|
|
560
|
+
};
|
|
561
|
+
|
|
562
|
+
if (document.readyState === "loading") {
|
|
563
|
+
document.addEventListener("DOMContentLoaded", mount, { once: true });
|
|
564
|
+
} else {
|
|
565
|
+
mount();
|
|
566
|
+
}
|
|
567
|
+
})();)JS";
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
bool globMatchCaseInsensitive(const std::string& pattern, const std::string& value) {
|
|
571
|
+
size_t pattern_index = 0;
|
|
572
|
+
size_t value_index = 0;
|
|
573
|
+
size_t star_pattern_index = std::string::npos;
|
|
574
|
+
size_t star_value_index = 0;
|
|
575
|
+
|
|
576
|
+
while (value_index < value.size()) {
|
|
577
|
+
if (
|
|
578
|
+
pattern_index < pattern.size() &&
|
|
579
|
+
std::tolower(static_cast<unsigned char>(pattern[pattern_index])) ==
|
|
580
|
+
std::tolower(static_cast<unsigned char>(value[value_index]))
|
|
581
|
+
) {
|
|
582
|
+
pattern_index += 1;
|
|
583
|
+
value_index += 1;
|
|
584
|
+
} else if (pattern_index < pattern.size() && pattern[pattern_index] == '*') {
|
|
585
|
+
star_pattern_index = pattern_index++;
|
|
586
|
+
star_value_index = value_index;
|
|
587
|
+
} else if (star_pattern_index != std::string::npos) {
|
|
588
|
+
pattern_index = star_pattern_index + 1;
|
|
589
|
+
value_index = ++star_value_index;
|
|
590
|
+
} else {
|
|
591
|
+
return false;
|
|
592
|
+
}
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
while (pattern_index < pattern.size() && pattern[pattern_index] == '*') {
|
|
596
|
+
pattern_index += 1;
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
return pattern_index == pattern.size();
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
std::vector<std::string> parseNavigationRulesJson(const std::string& rules_json) {
|
|
603
|
+
std::vector<std::string> rules;
|
|
604
|
+
if (rules_json.empty()) {
|
|
605
|
+
return rules;
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
CefRefPtr<CefValue> parsed = CefParseJSON(rules_json, JSON_PARSER_RFC);
|
|
609
|
+
if (!parsed || parsed->GetType() != VTYPE_LIST) {
|
|
610
|
+
return rules;
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
CefRefPtr<CefListValue> list = parsed->GetList();
|
|
614
|
+
if (!list) {
|
|
615
|
+
return rules;
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
rules.reserve(list->GetSize());
|
|
619
|
+
for (size_t index = 0; index < list->GetSize(); index += 1) {
|
|
620
|
+
if (list->GetType(index) != VTYPE_STRING) {
|
|
621
|
+
continue;
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
const std::string rule = list->GetString(index).ToString();
|
|
625
|
+
if (!rule.empty()) {
|
|
626
|
+
rules.push_back(rule);
|
|
627
|
+
}
|
|
628
|
+
}
|
|
629
|
+
|
|
630
|
+
return rules;
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
// Simple flat JSON object parser for chromiumFlags.
|
|
634
|
+
// Input is always a JSON-serialized Record<string, string | boolean> from TS.
|
|
635
|
+
// Does not depend on CEF, so it can run before CefInitialize.
|
|
636
|
+
std::map<std::string, std::string> parseChromiumFlagsJson(const std::string& json) {
|
|
637
|
+
std::map<std::string, std::string> flags;
|
|
638
|
+
if (json.empty()) {
|
|
639
|
+
return flags;
|
|
640
|
+
}
|
|
641
|
+
|
|
642
|
+
size_t pos = json.find('{');
|
|
643
|
+
if (pos == std::string::npos) {
|
|
644
|
+
return flags;
|
|
645
|
+
}
|
|
646
|
+
++pos;
|
|
647
|
+
|
|
648
|
+
auto skipWhitespace = [&]() {
|
|
649
|
+
while (pos < json.size() && (json[pos] == ' ' || json[pos] == '\t' || json[pos] == '\n' || json[pos] == '\r')) {
|
|
650
|
+
++pos;
|
|
651
|
+
}
|
|
652
|
+
};
|
|
653
|
+
|
|
654
|
+
auto parseString = [&]() -> std::string {
|
|
655
|
+
if (pos >= json.size() || json[pos] != '"') {
|
|
656
|
+
return {};
|
|
657
|
+
}
|
|
658
|
+
++pos;
|
|
659
|
+
std::string result;
|
|
660
|
+
while (pos < json.size() && json[pos] != '"') {
|
|
661
|
+
if (json[pos] == '\\' && pos + 1 < json.size()) {
|
|
662
|
+
++pos;
|
|
663
|
+
}
|
|
664
|
+
result += json[pos++];
|
|
665
|
+
}
|
|
666
|
+
if (pos < json.size()) {
|
|
667
|
+
++pos; // closing quote
|
|
668
|
+
}
|
|
669
|
+
return result;
|
|
670
|
+
};
|
|
671
|
+
|
|
672
|
+
while (pos < json.size()) {
|
|
673
|
+
skipWhitespace();
|
|
674
|
+
if (pos >= json.size() || json[pos] == '}') {
|
|
675
|
+
break;
|
|
676
|
+
}
|
|
677
|
+
if (json[pos] == ',') {
|
|
678
|
+
++pos;
|
|
679
|
+
continue;
|
|
680
|
+
}
|
|
681
|
+
|
|
682
|
+
std::string key = parseString();
|
|
683
|
+
if (key.empty()) {
|
|
684
|
+
break;
|
|
685
|
+
}
|
|
686
|
+
|
|
687
|
+
skipWhitespace();
|
|
688
|
+
if (pos >= json.size() || json[pos] != ':') {
|
|
689
|
+
break;
|
|
690
|
+
}
|
|
691
|
+
++pos;
|
|
692
|
+
skipWhitespace();
|
|
693
|
+
|
|
694
|
+
if (pos >= json.size()) {
|
|
695
|
+
break;
|
|
696
|
+
}
|
|
697
|
+
|
|
698
|
+
if (json[pos] == '"') {
|
|
699
|
+
flags[key] = parseString();
|
|
700
|
+
} else if (json.compare(pos, 4, "true") == 0) {
|
|
701
|
+
flags[key] = "true";
|
|
702
|
+
pos += 4;
|
|
703
|
+
} else if (json.compare(pos, 5, "false") == 0) {
|
|
704
|
+
flags[key] = "false";
|
|
705
|
+
pos += 5;
|
|
706
|
+
} else {
|
|
707
|
+
// skip unknown value
|
|
708
|
+
while (pos < json.size() && json[pos] != ',' && json[pos] != '}') {
|
|
709
|
+
++pos;
|
|
710
|
+
}
|
|
711
|
+
}
|
|
712
|
+
}
|
|
713
|
+
|
|
714
|
+
return flags;
|
|
715
|
+
}
|
|
716
|
+
|
|
717
|
+
bool shouldAlwaysAllowNavigationUrl(const std::string& url) {
|
|
718
|
+
return url == "about:blank" || url.rfind("views://internal/", 0) == 0;
|
|
719
|
+
}
|
|
720
|
+
|
|
721
|
+
bool shouldAllowNavigation(const ViewHost* view, const std::string& url) {
|
|
722
|
+
if (!view || shouldAlwaysAllowNavigationUrl(url) || view->navigation_rules.empty()) {
|
|
723
|
+
return true;
|
|
724
|
+
}
|
|
725
|
+
|
|
726
|
+
bool allowed = true; // Match electrobun's last-match-wins, default-allow semantics.
|
|
727
|
+
for (const std::string& raw_rule : view->navigation_rules) {
|
|
728
|
+
const bool is_block_rule = !raw_rule.empty() && raw_rule.front() == '^';
|
|
729
|
+
const std::string pattern = is_block_rule ? raw_rule.substr(1) : raw_rule;
|
|
730
|
+
|
|
731
|
+
if (pattern.empty()) {
|
|
732
|
+
continue;
|
|
733
|
+
}
|
|
734
|
+
if (globMatchCaseInsensitive(pattern, url)) {
|
|
735
|
+
allowed = !is_block_rule;
|
|
736
|
+
}
|
|
737
|
+
}
|
|
738
|
+
|
|
739
|
+
return allowed;
|
|
740
|
+
}
|
|
741
|
+
|
|
742
|
+
void emitWindowEvent(uint32_t window_id, const char* event_name, const std::string& payload) {
|
|
743
|
+
BuniteWindowEventHandler handler = nullptr;
|
|
744
|
+
{
|
|
745
|
+
std::lock_guard<std::mutex> lock(g_runtime.lifecycle_mutex);
|
|
746
|
+
handler = g_runtime.window_event_handler;
|
|
747
|
+
}
|
|
748
|
+
if (handler) {
|
|
749
|
+
handler(window_id, _strdup(event_name ? event_name : ""), _strdup(payload.c_str()));
|
|
750
|
+
}
|
|
751
|
+
}
|
|
752
|
+
|
|
753
|
+
void emitWebviewEvent(uint32_t view_id, const char* event_name, const std::string& payload) {
|
|
754
|
+
BuniteWebviewEventHandler handler = nullptr;
|
|
755
|
+
{
|
|
756
|
+
std::lock_guard<std::mutex> lock(g_runtime.lifecycle_mutex);
|
|
757
|
+
handler = g_runtime.webview_event_handler;
|
|
758
|
+
}
|
|
759
|
+
if (handler) {
|
|
760
|
+
handler(view_id, _strdup(event_name ? event_name : ""), _strdup(payload.c_str()));
|
|
761
|
+
}
|
|
762
|
+
}
|
|
763
|
+
|
|
764
|
+
class DeferredCefTask : public CefTask {
|
|
765
|
+
public:
|
|
766
|
+
explicit DeferredCefTask(std::function<void()> task)
|
|
767
|
+
: task_(std::move(task)) {}
|
|
768
|
+
|
|
769
|
+
void Execute() override {
|
|
770
|
+
task_();
|
|
771
|
+
}
|
|
772
|
+
|
|
773
|
+
private:
|
|
774
|
+
std::function<void()> task_;
|
|
775
|
+
|
|
776
|
+
IMPLEMENT_REFCOUNTING(DeferredCefTask);
|
|
777
|
+
};
|
|
778
|
+
|
|
779
|
+
void postTask(std::function<void()> task) {
|
|
780
|
+
CefPostTask(TID_UI, new DeferredCefTask(std::move(task)));
|
|
781
|
+
}
|
|
782
|
+
|
|
783
|
+
template <typename Result>
|
|
784
|
+
Result runOnUiThreadSync(std::function<Result()> task) {
|
|
785
|
+
if (CefCurrentlyOn(TID_UI)) {
|
|
786
|
+
return task();
|
|
787
|
+
}
|
|
788
|
+
|
|
789
|
+
auto packaged = std::make_shared<std::packaged_task<Result()>>(std::move(task));
|
|
790
|
+
auto future = packaged->get_future();
|
|
791
|
+
postTask([packaged]() { (*packaged)(); });
|
|
792
|
+
return future.get();
|
|
793
|
+
}
|
|
794
|
+
|
|
795
|
+
template <>
|
|
796
|
+
void runOnUiThreadSync<void>(std::function<void()> task) {
|
|
797
|
+
if (CefCurrentlyOn(TID_UI)) {
|
|
798
|
+
task();
|
|
799
|
+
return;
|
|
800
|
+
}
|
|
801
|
+
|
|
802
|
+
auto packaged = std::make_shared<std::packaged_task<void()>>(std::move(task));
|
|
803
|
+
auto future = packaged->get_future();
|
|
804
|
+
postTask([packaged]() { (*packaged)(); });
|
|
805
|
+
future.get();
|
|
806
|
+
}
|
|
807
|
+
|
|
808
|
+
std::string normalizeViewsPath(const std::string& url) {
|
|
809
|
+
std::string path = url;
|
|
810
|
+
if (path.rfind("views://", 0) == 0) {
|
|
811
|
+
path = path.substr(8);
|
|
812
|
+
}
|
|
813
|
+
|
|
814
|
+
const auto query_pos = path.find_first_of("?#");
|
|
815
|
+
if (query_pos != std::string::npos) {
|
|
816
|
+
path = path.substr(0, query_pos);
|
|
817
|
+
}
|
|
818
|
+
|
|
819
|
+
while (!path.empty() && (path.front() == '/' || path.front() == '\\')) {
|
|
820
|
+
path.erase(path.begin());
|
|
821
|
+
}
|
|
822
|
+
|
|
823
|
+
while (!path.empty() && (path.back() == '/' || path.back() == '\\')) {
|
|
824
|
+
path.pop_back();
|
|
825
|
+
}
|
|
826
|
+
|
|
827
|
+
return path.empty() ? "index.html" : path;
|
|
828
|
+
}
|
|
829
|
+
|
|
830
|
+
std::string getMimeType(const std::filesystem::path& file_path) {
|
|
831
|
+
const auto extension = file_path.extension().string();
|
|
832
|
+
if (extension == ".html" || extension == ".htm") return "text/html";
|
|
833
|
+
if (extension == ".js" || extension == ".mjs") return "text/javascript";
|
|
834
|
+
if (extension == ".css") return "text/css";
|
|
835
|
+
if (extension == ".json") return "application/json";
|
|
836
|
+
if (extension == ".svg") return "image/svg+xml";
|
|
837
|
+
if (extension == ".png") return "image/png";
|
|
838
|
+
if (extension == ".jpg" || extension == ".jpeg") return "image/jpeg";
|
|
839
|
+
if (extension == ".woff2") return "font/woff2";
|
|
840
|
+
if (extension == ".woff") return "font/woff";
|
|
841
|
+
if (extension == ".ttf") return "font/ttf";
|
|
842
|
+
return "application/octet-stream";
|
|
843
|
+
}
|
|
844
|
+
|
|
845
|
+
std::string getViewsRootForView(uint32_t view_id) {
|
|
846
|
+
std::lock_guard<std::mutex> lock(g_runtime.object_mutex);
|
|
847
|
+
const auto it = g_runtime.views_by_id.find(view_id);
|
|
848
|
+
if (it == g_runtime.views_by_id.end() || !it->second) {
|
|
849
|
+
return {};
|
|
850
|
+
}
|
|
851
|
+
return it->second->views_root;
|
|
852
|
+
}
|
|
853
|
+
|
|
854
|
+
std::optional<std::string> loadViewsResource(uint32_t view_id, const std::string& url, std::string& mime_type) {
|
|
855
|
+
const std::string views_root = getViewsRootForView(view_id);
|
|
856
|
+
|
|
857
|
+
const std::string path = normalizeViewsPath(url);
|
|
858
|
+
if (path == "internal/index.html") {
|
|
859
|
+
mime_type = "text/html";
|
|
860
|
+
return bunite::WebviewContentStorage::instance().get(view_id);
|
|
861
|
+
}
|
|
862
|
+
|
|
863
|
+
if (views_root.empty()) {
|
|
864
|
+
return std::nullopt;
|
|
865
|
+
}
|
|
866
|
+
|
|
867
|
+
if (!std::filesystem::exists(views_root)) {
|
|
868
|
+
return std::nullopt;
|
|
869
|
+
}
|
|
870
|
+
|
|
871
|
+
const std::filesystem::path root = std::filesystem::weakly_canonical(std::filesystem::path(views_root));
|
|
872
|
+
std::filesystem::path candidate = std::filesystem::weakly_canonical(root / std::filesystem::path(path));
|
|
873
|
+
if (std::filesystem::exists(candidate) && std::filesystem::is_directory(candidate)) {
|
|
874
|
+
candidate = std::filesystem::weakly_canonical(candidate / "index.html");
|
|
875
|
+
} else if (!std::filesystem::exists(candidate) && !candidate.has_extension()) {
|
|
876
|
+
candidate = std::filesystem::weakly_canonical(std::filesystem::path(candidate.native() + L".html"));
|
|
877
|
+
}
|
|
878
|
+
const auto root_string = root.native();
|
|
879
|
+
const auto candidate_string = candidate.native();
|
|
880
|
+
const bool in_root = candidate_string == root_string ||
|
|
881
|
+
(candidate_string.size() > root_string.size() &&
|
|
882
|
+
candidate_string.rfind(root_string, 0) == 0 &&
|
|
883
|
+
(candidate_string[root_string.size()] == L'\\' || candidate_string[root_string.size()] == L'/'));
|
|
884
|
+
|
|
885
|
+
if (!in_root || !std::filesystem::exists(candidate) || std::filesystem::is_directory(candidate)) {
|
|
886
|
+
return std::nullopt;
|
|
887
|
+
}
|
|
888
|
+
|
|
889
|
+
std::ifstream stream(candidate, std::ios::binary);
|
|
890
|
+
if (!stream) {
|
|
891
|
+
return std::nullopt;
|
|
892
|
+
}
|
|
893
|
+
|
|
894
|
+
std::ostringstream contents;
|
|
895
|
+
contents << stream.rdbuf();
|
|
896
|
+
mime_type = getMimeType(candidate);
|
|
897
|
+
return contents.str();
|
|
898
|
+
}
|
|
899
|
+
|
|
900
|
+
void removeBrowserMapping(int browser_id) {
|
|
901
|
+
std::lock_guard<std::mutex> lock(g_runtime.object_mutex);
|
|
902
|
+
g_runtime.browser_to_view_id.erase(browser_id);
|
|
903
|
+
}
|
|
904
|
+
|
|
905
|
+
void syncWindowFrame(WindowHost* window) {
|
|
906
|
+
if (!window || !window->hwnd) {
|
|
907
|
+
return;
|
|
908
|
+
}
|
|
909
|
+
|
|
910
|
+
RECT rect{};
|
|
911
|
+
GetWindowRect(window->hwnd, &rect);
|
|
912
|
+
window->frame = rect;
|
|
913
|
+
window->minimized = IsIconic(window->hwnd) != 0;
|
|
914
|
+
window->maximized = IsZoomed(window->hwnd) != 0;
|
|
915
|
+
if (!window->minimized) {
|
|
916
|
+
window->restore_maximized_after_minimize = false;
|
|
917
|
+
}
|
|
918
|
+
}
|
|
919
|
+
|
|
920
|
+
void resizeViewToFit(ViewHost* view) {
|
|
921
|
+
if (!view || !view->browser || !view->window || !view->window->hwnd) {
|
|
922
|
+
return;
|
|
923
|
+
}
|
|
924
|
+
|
|
925
|
+
HWND browser_hwnd = view->browser->GetHost()->GetWindowHandle();
|
|
926
|
+
if (!browser_hwnd) {
|
|
927
|
+
return;
|
|
928
|
+
}
|
|
929
|
+
|
|
930
|
+
RECT bounds = view->bounds;
|
|
931
|
+
switch (view->anchor_mode) {
|
|
932
|
+
case ANCHOR_FILL: {
|
|
933
|
+
GetClientRect(view->window->hwnd, &bounds);
|
|
934
|
+
break;
|
|
935
|
+
}
|
|
936
|
+
case ANCHOR_TOP: {
|
|
937
|
+
RECT client;
|
|
938
|
+
GetClientRect(view->window->hwnd, &client);
|
|
939
|
+
bounds = { 0, 0, client.right, static_cast<LONG>(view->anchor_inset) };
|
|
940
|
+
break;
|
|
941
|
+
}
|
|
942
|
+
case ANCHOR_BELOW_TOP: {
|
|
943
|
+
RECT client;
|
|
944
|
+
GetClientRect(view->window->hwnd, &client);
|
|
945
|
+
LONG inset = static_cast<LONG>(view->anchor_inset);
|
|
946
|
+
LONG h = client.bottom - inset;
|
|
947
|
+
if (h < 0) h = 0;
|
|
948
|
+
bounds = { 0, inset, client.right, inset + h };
|
|
949
|
+
break;
|
|
950
|
+
}
|
|
951
|
+
default: // ANCHOR_NONE — use stored bounds
|
|
952
|
+
break;
|
|
953
|
+
}
|
|
954
|
+
view->bounds = bounds;
|
|
955
|
+
|
|
956
|
+
SetWindowPos(
|
|
957
|
+
browser_hwnd,
|
|
958
|
+
nullptr,
|
|
959
|
+
bounds.left,
|
|
960
|
+
bounds.top,
|
|
961
|
+
bounds.right - bounds.left,
|
|
962
|
+
bounds.bottom - bounds.top,
|
|
963
|
+
SWP_NOZORDER | SWP_NOACTIVATE
|
|
964
|
+
);
|
|
965
|
+
}
|
|
966
|
+
|
|
967
|
+
void openDevToolsForView(ViewHost* view);
|
|
968
|
+
void closeDevToolsForView(ViewHost* view);
|
|
969
|
+
void toggleDevToolsForView(ViewHost* view);
|
|
970
|
+
|
|
971
|
+
void finalizeViewHost(ViewHost* view) {
|
|
972
|
+
if (!view) {
|
|
973
|
+
return;
|
|
974
|
+
}
|
|
975
|
+
|
|
976
|
+
cancelPendingMessageBoxesForView(view->id);
|
|
977
|
+
bunite::WebviewContentStorage::instance().remove(view->id);
|
|
978
|
+
|
|
979
|
+
{
|
|
980
|
+
std::lock_guard<std::mutex> lock(g_runtime.object_mutex);
|
|
981
|
+
g_runtime.views_by_id.erase(view->id);
|
|
982
|
+
if (view->window) {
|
|
983
|
+
auto& views = view->window->views;
|
|
984
|
+
views.erase(std::remove(views.begin(), views.end(), view), views.end());
|
|
985
|
+
}
|
|
986
|
+
}
|
|
987
|
+
|
|
988
|
+
delete view;
|
|
989
|
+
}
|
|
990
|
+
|
|
991
|
+
void closeViewHost(ViewHost* view) {
|
|
992
|
+
if (!view || view->closing.exchange(true)) {
|
|
993
|
+
return;
|
|
994
|
+
}
|
|
995
|
+
|
|
996
|
+
if (view->browser) {
|
|
997
|
+
closeDevToolsForView(view);
|
|
998
|
+
view->browser->GetHost()->CloseBrowser(true);
|
|
999
|
+
return;
|
|
1000
|
+
}
|
|
1001
|
+
|
|
1002
|
+
finalizeViewHost(view);
|
|
1003
|
+
}
|
|
1004
|
+
|
|
1005
|
+
void openDevToolsForView(ViewHost* view);
|
|
1006
|
+
void closeDevToolsForView(ViewHost* view);
|
|
1007
|
+
void toggleDevToolsForView(ViewHost* view);
|
|
1008
|
+
|
|
1009
|
+
void finalizeWindowHost(WindowHost* window) {
|
|
1010
|
+
if (!window) {
|
|
1011
|
+
return;
|
|
1012
|
+
}
|
|
1013
|
+
|
|
1014
|
+
for (auto* view : window->views) {
|
|
1015
|
+
if (view) {
|
|
1016
|
+
view->window = nullptr;
|
|
1017
|
+
}
|
|
1018
|
+
}
|
|
1019
|
+
window->views.clear();
|
|
1020
|
+
|
|
1021
|
+
{
|
|
1022
|
+
std::lock_guard<std::mutex> lock(g_runtime.object_mutex);
|
|
1023
|
+
g_runtime.windows_by_id.erase(window->id);
|
|
1024
|
+
}
|
|
1025
|
+
|
|
1026
|
+
delete window;
|
|
1027
|
+
}
|
|
1028
|
+
|
|
1029
|
+
class BuniteSchemeHandler : public CefResourceHandler {
|
|
1030
|
+
public:
|
|
1031
|
+
explicit BuniteSchemeHandler(uint32_t view_id)
|
|
1032
|
+
: view_id_(view_id) {}
|
|
1033
|
+
|
|
1034
|
+
bool Open(CefRefPtr<CefRequest> request, bool& handle_request, CefRefPtr<CefCallback> callback) override {
|
|
1035
|
+
CEF_REQUIRE_IO_THREAD();
|
|
1036
|
+
handle_request = true;
|
|
1037
|
+
|
|
1038
|
+
if (request) {
|
|
1039
|
+
const auto message_box_response = parseMessageBoxResponseUrl(request->GetURL().ToString());
|
|
1040
|
+
if (message_box_response) {
|
|
1041
|
+
tryResolvePendingMessageBoxRequest(
|
|
1042
|
+
view_id_,
|
|
1043
|
+
message_box_response->first,
|
|
1044
|
+
message_box_response->second
|
|
1045
|
+
);
|
|
1046
|
+
status_code_ = 204;
|
|
1047
|
+
status_text_ = "No Content";
|
|
1048
|
+
mime_type_ = "text/plain";
|
|
1049
|
+
data_.clear();
|
|
1050
|
+
return true;
|
|
1051
|
+
}
|
|
1052
|
+
}
|
|
1053
|
+
|
|
1054
|
+
const std::string url = request ? request->GetURL().ToString() : "";
|
|
1055
|
+
const std::string normalized_path = normalizeViewsPath(url);
|
|
1056
|
+
|
|
1057
|
+
// Dynamic route: handler lives on the Bun side, request is async
|
|
1058
|
+
if (bunite::ViewsRouteStorage::instance().hasRoute(normalized_path)) {
|
|
1059
|
+
handle_request = false; // async — we'll call callback->Continue() later
|
|
1060
|
+
uint32_t request_id;
|
|
1061
|
+
{
|
|
1062
|
+
std::lock_guard<std::mutex> lock(g_runtime.route_mutex);
|
|
1063
|
+
request_id = g_runtime.next_route_request_id++;
|
|
1064
|
+
g_runtime.pending_routes[request_id] = { normalized_path, callback, request };
|
|
1065
|
+
}
|
|
1066
|
+
pending_route_request_id_ = request_id;
|
|
1067
|
+
if (g_runtime.webview_event_handler) {
|
|
1068
|
+
auto* name = strdup("route-request");
|
|
1069
|
+
auto* payload = strdup(
|
|
1070
|
+
("{\"requestId\":" + std::to_string(request_id) + ",\"path\":\"" + normalized_path + "\"}").c_str()
|
|
1071
|
+
);
|
|
1072
|
+
g_runtime.webview_event_handler(view_id_, name, payload);
|
|
1073
|
+
}
|
|
1074
|
+
return true;
|
|
1075
|
+
}
|
|
1076
|
+
|
|
1077
|
+
std::string mime_type;
|
|
1078
|
+
const auto content = loadViewsResource(view_id_, url, mime_type);
|
|
1079
|
+
if (!content) {
|
|
1080
|
+
const std::string views_root = getViewsRootForView(view_id_);
|
|
1081
|
+
std::fprintf(
|
|
1082
|
+
stderr,
|
|
1083
|
+
"[bunite/native] views:// resource not found (view=%u, url=%s, normalized=%s, root=%s)\n",
|
|
1084
|
+
view_id_,
|
|
1085
|
+
url.c_str(),
|
|
1086
|
+
normalized_path.c_str(),
|
|
1087
|
+
views_root.c_str()
|
|
1088
|
+
);
|
|
1089
|
+
status_code_ = 404;
|
|
1090
|
+
status_text_ = "Not Found";
|
|
1091
|
+
mime_type_ = "text/plain";
|
|
1092
|
+
data_ =
|
|
1093
|
+
"bunite could not resolve the requested views:// resource.\n"
|
|
1094
|
+
"url: " + url + "\n" +
|
|
1095
|
+
"normalized: " + normalized_path;
|
|
1096
|
+
return true;
|
|
1097
|
+
}
|
|
1098
|
+
|
|
1099
|
+
status_code_ = 200;
|
|
1100
|
+
status_text_ = "OK";
|
|
1101
|
+
mime_type_ = mime_type;
|
|
1102
|
+
data_ = *content;
|
|
1103
|
+
return true;
|
|
1104
|
+
}
|
|
1105
|
+
|
|
1106
|
+
void GetResponseHeaders(CefRefPtr<CefResponse> response, int64_t& response_length, CefString&) override {
|
|
1107
|
+
if (pending_route_request_id_ != 0) {
|
|
1108
|
+
auto content = bunite::ViewsRouteStorage::instance().takeResponse(pending_route_request_id_);
|
|
1109
|
+
pending_route_request_id_ = 0;
|
|
1110
|
+
if (content) {
|
|
1111
|
+
status_code_ = 200;
|
|
1112
|
+
status_text_ = "OK";
|
|
1113
|
+
mime_type_ = "text/html";
|
|
1114
|
+
data_ = std::move(*content);
|
|
1115
|
+
} else {
|
|
1116
|
+
status_code_ = 500;
|
|
1117
|
+
status_text_ = "Handler Error";
|
|
1118
|
+
mime_type_ = "text/plain";
|
|
1119
|
+
data_ = "Route handler did not produce a response.";
|
|
1120
|
+
}
|
|
1121
|
+
}
|
|
1122
|
+
response->SetStatus(status_code_);
|
|
1123
|
+
response->SetStatusText(status_text_);
|
|
1124
|
+
response->SetMimeType(mime_type_);
|
|
1125
|
+
response_length = static_cast<int64_t>(data_.size());
|
|
1126
|
+
}
|
|
1127
|
+
|
|
1128
|
+
bool Read(void* data_out, int bytes_to_read, int& bytes_read, CefRefPtr<CefResourceReadCallback>) override {
|
|
1129
|
+
CEF_REQUIRE_IO_THREAD();
|
|
1130
|
+
bytes_read = 0;
|
|
1131
|
+
if (offset_ >= data_.size()) {
|
|
1132
|
+
return false;
|
|
1133
|
+
}
|
|
1134
|
+
|
|
1135
|
+
const size_t remaining = data_.size() - offset_;
|
|
1136
|
+
const size_t count = std::min<size_t>(remaining, static_cast<size_t>(bytes_to_read));
|
|
1137
|
+
std::memcpy(data_out, data_.data() + offset_, count);
|
|
1138
|
+
offset_ += count;
|
|
1139
|
+
bytes_read = static_cast<int>(count);
|
|
1140
|
+
return true;
|
|
1141
|
+
}
|
|
1142
|
+
|
|
1143
|
+
void Cancel() override {
|
|
1144
|
+
CEF_REQUIRE_IO_THREAD();
|
|
1145
|
+
}
|
|
1146
|
+
|
|
1147
|
+
private:
|
|
1148
|
+
uint32_t view_id_;
|
|
1149
|
+
uint32_t pending_route_request_id_ = 0;
|
|
1150
|
+
std::string data_;
|
|
1151
|
+
std::string mime_type_ = "text/plain";
|
|
1152
|
+
std::string status_text_ = "OK";
|
|
1153
|
+
size_t offset_ = 0;
|
|
1154
|
+
int status_code_ = 200;
|
|
1155
|
+
|
|
1156
|
+
IMPLEMENT_REFCOUNTING(BuniteSchemeHandler);
|
|
1157
|
+
};
|
|
1158
|
+
|
|
1159
|
+
class BuniteSchemeHandlerFactory : public CefSchemeHandlerFactory {
|
|
1160
|
+
public:
|
|
1161
|
+
CefRefPtr<CefResourceHandler> Create(
|
|
1162
|
+
CefRefPtr<CefBrowser> browser,
|
|
1163
|
+
CefRefPtr<CefFrame>,
|
|
1164
|
+
const CefString&,
|
|
1165
|
+
CefRefPtr<CefRequest>
|
|
1166
|
+
) override {
|
|
1167
|
+
uint32_t view_id = 0;
|
|
1168
|
+
if (browser) {
|
|
1169
|
+
std::lock_guard<std::mutex> lock(g_runtime.object_mutex);
|
|
1170
|
+
const auto it = g_runtime.browser_to_view_id.find(browser->GetIdentifier());
|
|
1171
|
+
if (it != g_runtime.browser_to_view_id.end()) {
|
|
1172
|
+
view_id = it->second;
|
|
1173
|
+
}
|
|
1174
|
+
}
|
|
1175
|
+
return new BuniteSchemeHandler(view_id);
|
|
1176
|
+
}
|
|
1177
|
+
|
|
1178
|
+
private:
|
|
1179
|
+
IMPLEMENT_REFCOUNTING(BuniteSchemeHandlerFactory);
|
|
1180
|
+
};
|
|
1181
|
+
|
|
1182
|
+
class BuniteCefApp : public CefApp, public CefBrowserProcessHandler {
|
|
1183
|
+
public:
|
|
1184
|
+
CefRefPtr<CefBrowserProcessHandler> GetBrowserProcessHandler() override {
|
|
1185
|
+
return this;
|
|
1186
|
+
}
|
|
1187
|
+
|
|
1188
|
+
void OnBeforeCommandLineProcessing(const CefString&, CefRefPtr<CefCommandLine> command_line) override {
|
|
1189
|
+
if (!g_runtime.popup_blocking) {
|
|
1190
|
+
// Bunite handles popup attempts in OnBeforePopup and surfaces them to Bun.
|
|
1191
|
+
// Keep Chromium's popup blocker off by default so scripted window.open()
|
|
1192
|
+
// still reaches the runtime-level handler.
|
|
1193
|
+
command_line->AppendSwitch("disable-popup-blocking");
|
|
1194
|
+
}
|
|
1195
|
+
|
|
1196
|
+
// --- Bunite defaults (injected into the flags map so the loop handles them uniformly) ---
|
|
1197
|
+
// Run GPU in-process to avoid EGL/D3D11 shared context failures.
|
|
1198
|
+
// Override with { "in-process-gpu": false }.
|
|
1199
|
+
if (g_runtime.chromium_flags.find("in-process-gpu") == g_runtime.chromium_flags.end()) {
|
|
1200
|
+
g_runtime.chromium_flags["in-process-gpu"] = "true";
|
|
1201
|
+
}
|
|
1202
|
+
|
|
1203
|
+
// Run network service in-process to avoid subprocess crashes when
|
|
1204
|
+
// multiple BrowserViews issue concurrent requests.
|
|
1205
|
+
// Override with { "enable-features": false } to suppress, or pass a
|
|
1206
|
+
// custom value to replace the default entirely.
|
|
1207
|
+
if (g_runtime.chromium_flags.find("enable-features") == g_runtime.chromium_flags.end()) {
|
|
1208
|
+
g_runtime.chromium_flags["enable-features"] = "NetworkServiceInProcess";
|
|
1209
|
+
} else {
|
|
1210
|
+
std::string& existing = g_runtime.chromium_flags["enable-features"];
|
|
1211
|
+
if (existing.find("NetworkServiceInProcess") == std::string::npos && existing != "false") {
|
|
1212
|
+
if (existing.empty()) {
|
|
1213
|
+
existing = "NetworkServiceInProcess";
|
|
1214
|
+
} else {
|
|
1215
|
+
existing += ",NetworkServiceInProcess";
|
|
1216
|
+
}
|
|
1217
|
+
}
|
|
1218
|
+
}
|
|
1219
|
+
|
|
1220
|
+
// --- Apply all flags ---
|
|
1221
|
+
for (const auto& [key, value] : g_runtime.chromium_flags) {
|
|
1222
|
+
if (value == "false") {
|
|
1223
|
+
// Explicit false: skip the flag entirely (allows overriding defaults).
|
|
1224
|
+
continue;
|
|
1225
|
+
}
|
|
1226
|
+
if (value.empty() || value == "true") {
|
|
1227
|
+
command_line->AppendSwitch(key);
|
|
1228
|
+
} else {
|
|
1229
|
+
command_line->AppendSwitchWithValue(key, value);
|
|
1230
|
+
}
|
|
1231
|
+
}
|
|
1232
|
+
}
|
|
1233
|
+
|
|
1234
|
+
void OnRegisterCustomSchemes(CefRawPtr<CefSchemeRegistrar> registrar) override {
|
|
1235
|
+
registrar->AddCustomScheme(
|
|
1236
|
+
"views",
|
|
1237
|
+
CEF_SCHEME_OPTION_STANDARD |
|
|
1238
|
+
CEF_SCHEME_OPTION_CORS_ENABLED |
|
|
1239
|
+
CEF_SCHEME_OPTION_SECURE |
|
|
1240
|
+
CEF_SCHEME_OPTION_CSP_BYPASSING |
|
|
1241
|
+
CEF_SCHEME_OPTION_FETCH_ENABLED
|
|
1242
|
+
);
|
|
1243
|
+
}
|
|
1244
|
+
|
|
1245
|
+
private:
|
|
1246
|
+
IMPLEMENT_REFCOUNTING(BuniteCefApp);
|
|
1247
|
+
};
|
|
1248
|
+
|
|
1249
|
+
class BuniteCefClient
|
|
1250
|
+
: public CefClient,
|
|
1251
|
+
public CefLifeSpanHandler,
|
|
1252
|
+
public CefLoadHandler,
|
|
1253
|
+
public CefRequestHandler,
|
|
1254
|
+
public CefResourceRequestHandler,
|
|
1255
|
+
public CefPermissionHandler {
|
|
1256
|
+
public:
|
|
1257
|
+
explicit BuniteCefClient(ViewHost* view)
|
|
1258
|
+
: view_(view),
|
|
1259
|
+
preload_script_(view ? view->preload_script : "") {}
|
|
1260
|
+
|
|
1261
|
+
CefRefPtr<CefLifeSpanHandler> GetLifeSpanHandler() override { return this; }
|
|
1262
|
+
CefRefPtr<CefLoadHandler> GetLoadHandler() override { return this; }
|
|
1263
|
+
CefRefPtr<CefRequestHandler> GetRequestHandler() override { return this; }
|
|
1264
|
+
CefRefPtr<CefPermissionHandler> GetPermissionHandler() override { return this; }
|
|
1265
|
+
|
|
1266
|
+
CefRefPtr<CefResourceRequestHandler> GetResourceRequestHandler(
|
|
1267
|
+
CefRefPtr<CefBrowser>,
|
|
1268
|
+
CefRefPtr<CefFrame>,
|
|
1269
|
+
CefRefPtr<CefRequest>,
|
|
1270
|
+
bool,
|
|
1271
|
+
bool,
|
|
1272
|
+
const CefString&,
|
|
1273
|
+
bool&
|
|
1274
|
+
) override {
|
|
1275
|
+
return this;
|
|
1276
|
+
}
|
|
1277
|
+
|
|
1278
|
+
CefRefPtr<CefResponseFilter> GetResourceResponseFilter(
|
|
1279
|
+
CefRefPtr<CefBrowser>,
|
|
1280
|
+
CefRefPtr<CefFrame> frame,
|
|
1281
|
+
CefRefPtr<CefRequest> request,
|
|
1282
|
+
CefRefPtr<CefResponse> response
|
|
1283
|
+
) override {
|
|
1284
|
+
if (!frame->IsMain() || !response) {
|
|
1285
|
+
return nullptr;
|
|
1286
|
+
}
|
|
1287
|
+
|
|
1288
|
+
const std::string mime_type = response->GetMimeType().ToString();
|
|
1289
|
+
const std::string url = request ? request->GetURL().ToString() : "";
|
|
1290
|
+
if (
|
|
1291
|
+
mime_type.find("html") == std::string::npos ||
|
|
1292
|
+
preload_script_.empty() ||
|
|
1293
|
+
url.rfind("views://", 0) != 0
|
|
1294
|
+
) {
|
|
1295
|
+
return nullptr;
|
|
1296
|
+
}
|
|
1297
|
+
|
|
1298
|
+
return new BuniteResponseFilter(preload_script_);
|
|
1299
|
+
}
|
|
1300
|
+
|
|
1301
|
+
void OnAfterCreated(CefRefPtr<CefBrowser> browser) override {
|
|
1302
|
+
CEF_REQUIRE_UI_THREAD();
|
|
1303
|
+
view_->browser = browser;
|
|
1304
|
+
{
|
|
1305
|
+
std::lock_guard<std::mutex> lock(g_runtime.object_mutex);
|
|
1306
|
+
g_runtime.browser_to_view_id[browser->GetIdentifier()] = view_->id;
|
|
1307
|
+
}
|
|
1308
|
+
resizeViewToFit(view_);
|
|
1309
|
+
}
|
|
1310
|
+
|
|
1311
|
+
bool DoClose(CefRefPtr<CefBrowser>) override {
|
|
1312
|
+
CEF_REQUIRE_UI_THREAD();
|
|
1313
|
+
return false;
|
|
1314
|
+
}
|
|
1315
|
+
|
|
1316
|
+
void OnBeforeClose(CefRefPtr<CefBrowser> browser) override {
|
|
1317
|
+
CEF_REQUIRE_UI_THREAD();
|
|
1318
|
+
removeBrowserMapping(browser->GetIdentifier());
|
|
1319
|
+
view_->browser = nullptr;
|
|
1320
|
+
if (view_->closing.load()) {
|
|
1321
|
+
finalizeViewHost(view_);
|
|
1322
|
+
}
|
|
1323
|
+
}
|
|
1324
|
+
|
|
1325
|
+
bool OnBeforeBrowse(
|
|
1326
|
+
CefRefPtr<CefBrowser>,
|
|
1327
|
+
CefRefPtr<CefFrame> frame,
|
|
1328
|
+
CefRefPtr<CefRequest> request,
|
|
1329
|
+
bool,
|
|
1330
|
+
bool
|
|
1331
|
+
) override {
|
|
1332
|
+
CEF_REQUIRE_UI_THREAD();
|
|
1333
|
+
const bool is_main_frame = frame && frame->IsMain();
|
|
1334
|
+
const std::string url = request ? request->GetURL().ToString() : "";
|
|
1335
|
+
const bool should_allow = !is_main_frame || shouldAllowNavigation(view_, url);
|
|
1336
|
+
|
|
1337
|
+
if (is_main_frame) {
|
|
1338
|
+
cancelPendingMessageBoxesForView(view_->id);
|
|
1339
|
+
emitWebviewEvent(view_->id, "will-navigate", url);
|
|
1340
|
+
}
|
|
1341
|
+
|
|
1342
|
+
return !should_allow;
|
|
1343
|
+
}
|
|
1344
|
+
|
|
1345
|
+
bool OnOpenURLFromTab(
|
|
1346
|
+
CefRefPtr<CefBrowser>,
|
|
1347
|
+
CefRefPtr<CefFrame>,
|
|
1348
|
+
const CefString& target_url,
|
|
1349
|
+
CefRequestHandler::WindowOpenDisposition target_disposition,
|
|
1350
|
+
bool
|
|
1351
|
+
) override {
|
|
1352
|
+
CEF_REQUIRE_UI_THREAD();
|
|
1353
|
+
if (target_disposition != CEF_WOD_CURRENT_TAB) {
|
|
1354
|
+
emitWebviewEvent(
|
|
1355
|
+
view_->id,
|
|
1356
|
+
"new-window-open",
|
|
1357
|
+
"{\"url\":\"" + escapeJsonString(target_url.ToString()) + "\"}"
|
|
1358
|
+
);
|
|
1359
|
+
return true;
|
|
1360
|
+
}
|
|
1361
|
+
return false;
|
|
1362
|
+
}
|
|
1363
|
+
|
|
1364
|
+
bool OnBeforePopup(
|
|
1365
|
+
CefRefPtr<CefBrowser>,
|
|
1366
|
+
CefRefPtr<CefFrame>,
|
|
1367
|
+
int,
|
|
1368
|
+
const CefString& target_url,
|
|
1369
|
+
const CefString&,
|
|
1370
|
+
CefLifeSpanHandler::WindowOpenDisposition,
|
|
1371
|
+
bool,
|
|
1372
|
+
const CefPopupFeatures&,
|
|
1373
|
+
CefWindowInfo&,
|
|
1374
|
+
CefRefPtr<CefClient>&,
|
|
1375
|
+
CefBrowserSettings&,
|
|
1376
|
+
CefRefPtr<CefDictionaryValue>&,
|
|
1377
|
+
bool*
|
|
1378
|
+
) override {
|
|
1379
|
+
CEF_REQUIRE_UI_THREAD();
|
|
1380
|
+
emitWebviewEvent(
|
|
1381
|
+
view_->id,
|
|
1382
|
+
"new-window-open",
|
|
1383
|
+
"{\"url\":\"" + escapeJsonString(target_url.ToString()) + "\"}"
|
|
1384
|
+
);
|
|
1385
|
+
return true;
|
|
1386
|
+
}
|
|
1387
|
+
|
|
1388
|
+
void OnLoadEnd(CefRefPtr<CefBrowser>, CefRefPtr<CefFrame> frame, int) override {
|
|
1389
|
+
CEF_REQUIRE_UI_THREAD();
|
|
1390
|
+
if (!frame->IsMain()) {
|
|
1391
|
+
return;
|
|
1392
|
+
}
|
|
1393
|
+
|
|
1394
|
+
const std::string url = frame->GetURL().ToString();
|
|
1395
|
+
emitWebviewEvent(view_->id, "did-navigate", url);
|
|
1396
|
+
emitWebviewEvent(view_->id, "dom-ready", url);
|
|
1397
|
+
}
|
|
1398
|
+
|
|
1399
|
+
bool OnShowPermissionPrompt(
|
|
1400
|
+
CefRefPtr<CefBrowser>,
|
|
1401
|
+
uint64_t,
|
|
1402
|
+
const CefString& requesting_origin,
|
|
1403
|
+
uint32_t requested_permissions,
|
|
1404
|
+
CefRefPtr<CefPermissionPromptCallback> callback
|
|
1405
|
+
) override;
|
|
1406
|
+
|
|
1407
|
+
bool OnRequestMediaAccessPermission(
|
|
1408
|
+
CefRefPtr<CefBrowser>,
|
|
1409
|
+
CefRefPtr<CefFrame>,
|
|
1410
|
+
const CefString& requesting_origin,
|
|
1411
|
+
uint32_t requested_permissions,
|
|
1412
|
+
CefRefPtr<CefMediaAccessCallback> callback
|
|
1413
|
+
) override;
|
|
1414
|
+
|
|
1415
|
+
private:
|
|
1416
|
+
ViewHost* view_;
|
|
1417
|
+
std::string preload_script_;
|
|
1418
|
+
|
|
1419
|
+
IMPLEMENT_REFCOUNTING(BuniteCefClient);
|
|
1420
|
+
};
|
|
1421
|
+
|
|
1422
|
+
bool BuniteCefClient::OnShowPermissionPrompt(
|
|
1423
|
+
CefRefPtr<CefBrowser>,
|
|
1424
|
+
uint64_t,
|
|
1425
|
+
const CefString& requesting_origin,
|
|
1426
|
+
uint32_t requested_permissions,
|
|
1427
|
+
CefRefPtr<CefPermissionPromptCallback> callback
|
|
1428
|
+
) {
|
|
1429
|
+
CEF_REQUIRE_UI_THREAD();
|
|
1430
|
+
|
|
1431
|
+
uint32_t request_id = 0;
|
|
1432
|
+
{
|
|
1433
|
+
std::lock_guard<std::mutex> lock(g_runtime.permission_mutex);
|
|
1434
|
+
request_id = g_runtime.next_permission_request_id++;
|
|
1435
|
+
g_runtime.pending_permissions.emplace(
|
|
1436
|
+
request_id,
|
|
1437
|
+
PendingPermissionRequest{ PermissionRequestKind::Prompt, requested_permissions, callback, nullptr }
|
|
1438
|
+
);
|
|
1439
|
+
}
|
|
1440
|
+
|
|
1441
|
+
emitWebviewEvent(
|
|
1442
|
+
view_->id,
|
|
1443
|
+
"permission-requested",
|
|
1444
|
+
"{\"requestId\":" + std::to_string(request_id) +
|
|
1445
|
+
",\"kind\":" + std::to_string(requested_permissions) +
|
|
1446
|
+
",\"url\":\"" + escapeJsonString(requesting_origin.ToString()) + "\"}"
|
|
1447
|
+
);
|
|
1448
|
+
return true;
|
|
1449
|
+
}
|
|
1450
|
+
|
|
1451
|
+
bool BuniteCefClient::OnRequestMediaAccessPermission(
|
|
1452
|
+
CefRefPtr<CefBrowser>,
|
|
1453
|
+
CefRefPtr<CefFrame>,
|
|
1454
|
+
const CefString& requesting_origin,
|
|
1455
|
+
uint32_t requested_permissions,
|
|
1456
|
+
CefRefPtr<CefMediaAccessCallback> callback
|
|
1457
|
+
) {
|
|
1458
|
+
CEF_REQUIRE_UI_THREAD();
|
|
1459
|
+
|
|
1460
|
+
uint32_t request_id = 0;
|
|
1461
|
+
{
|
|
1462
|
+
std::lock_guard<std::mutex> lock(g_runtime.permission_mutex);
|
|
1463
|
+
request_id = g_runtime.next_permission_request_id++;
|
|
1464
|
+
g_runtime.pending_permissions.emplace(
|
|
1465
|
+
request_id,
|
|
1466
|
+
PendingPermissionRequest{ PermissionRequestKind::MediaAccess, requested_permissions, nullptr, callback }
|
|
1467
|
+
);
|
|
1468
|
+
}
|
|
1469
|
+
|
|
1470
|
+
emitWebviewEvent(
|
|
1471
|
+
view_->id,
|
|
1472
|
+
"permission-requested",
|
|
1473
|
+
"{\"requestId\":" + std::to_string(request_id) +
|
|
1474
|
+
",\"kind\":" + std::to_string(requested_permissions) +
|
|
1475
|
+
",\"url\":\"" + escapeJsonString(requesting_origin.ToString()) + "\"}"
|
|
1476
|
+
);
|
|
1477
|
+
return true;
|
|
1478
|
+
}
|
|
1479
|
+
|
|
1480
|
+
void openDevToolsForView(ViewHost* view) {
|
|
1481
|
+
if (!view || view->closing.load() || !view->browser) {
|
|
1482
|
+
return;
|
|
1483
|
+
}
|
|
1484
|
+
|
|
1485
|
+
CefWindowInfo window_info;
|
|
1486
|
+
window_info.SetAsPopup(nullptr, "Bunite DevTools");
|
|
1487
|
+
|
|
1488
|
+
CefBrowserSettings settings;
|
|
1489
|
+
view->browser->GetHost()->ShowDevTools(window_info, nullptr, settings, CefPoint());
|
|
1490
|
+
}
|
|
1491
|
+
|
|
1492
|
+
void closeDevToolsForView(ViewHost* view) {
|
|
1493
|
+
if (!view || view->closing.load() || !view->browser) {
|
|
1494
|
+
return;
|
|
1495
|
+
}
|
|
1496
|
+
|
|
1497
|
+
view->browser->GetHost()->CloseDevTools();
|
|
1498
|
+
}
|
|
1499
|
+
|
|
1500
|
+
void toggleDevToolsForView(ViewHost* view) {
|
|
1501
|
+
if (!view || view->closing.load() || !view->browser) {
|
|
1502
|
+
return;
|
|
1503
|
+
}
|
|
1504
|
+
|
|
1505
|
+
if (view->browser->GetHost()->HasDevTools()) {
|
|
1506
|
+
closeDevToolsForView(view);
|
|
1507
|
+
return;
|
|
1508
|
+
}
|
|
1509
|
+
|
|
1510
|
+
openDevToolsForView(view);
|
|
1511
|
+
}
|
|
1512
|
+
|
|
1513
|
+
LRESULT CALLBACK messageWindowProc(HWND hwnd, UINT message, WPARAM, LPARAM) {
|
|
1514
|
+
return DefWindowProcW(hwnd, message, 0, 0);
|
|
1515
|
+
}
|
|
1516
|
+
|
|
1517
|
+
LRESULT CALLBACK buniteWindowProc(HWND hwnd, UINT message, WPARAM w_param, LPARAM l_param) {
|
|
1518
|
+
WindowHost* window = reinterpret_cast<WindowHost*>(GetWindowLongPtrW(hwnd, GWLP_USERDATA));
|
|
1519
|
+
|
|
1520
|
+
if (message == WM_NCCREATE) {
|
|
1521
|
+
auto* create_struct = reinterpret_cast<CREATESTRUCTW*>(l_param);
|
|
1522
|
+
window = static_cast<WindowHost*>(create_struct->lpCreateParams);
|
|
1523
|
+
SetWindowLongPtrW(hwnd, GWLP_USERDATA, reinterpret_cast<LONG_PTR>(window));
|
|
1524
|
+
return DefWindowProcW(hwnd, message, w_param, l_param);
|
|
1525
|
+
}
|
|
1526
|
+
|
|
1527
|
+
switch (message) {
|
|
1528
|
+
case WM_SETFOCUS:
|
|
1529
|
+
if (window) {
|
|
1530
|
+
emitWindowEvent(window->id, "focus");
|
|
1531
|
+
}
|
|
1532
|
+
break;
|
|
1533
|
+
|
|
1534
|
+
case WM_KILLFOCUS:
|
|
1535
|
+
if (window) {
|
|
1536
|
+
emitWindowEvent(window->id, "blur");
|
|
1537
|
+
}
|
|
1538
|
+
break;
|
|
1539
|
+
|
|
1540
|
+
case WM_MOVE:
|
|
1541
|
+
if (window) {
|
|
1542
|
+
syncWindowFrame(window);
|
|
1543
|
+
emitWindowEvent(
|
|
1544
|
+
window->id,
|
|
1545
|
+
"move",
|
|
1546
|
+
"{\"x\":" + std::to_string(window->frame.left) +
|
|
1547
|
+
",\"y\":" + std::to_string(window->frame.top) +
|
|
1548
|
+
",\"maximized\":" + (window->maximized ? "true" : "false") +
|
|
1549
|
+
",\"minimized\":" + (window->minimized ? "true" : "false") + "}"
|
|
1550
|
+
);
|
|
1551
|
+
}
|
|
1552
|
+
break;
|
|
1553
|
+
|
|
1554
|
+
case WM_SIZE:
|
|
1555
|
+
if (window) {
|
|
1556
|
+
syncWindowFrame(window);
|
|
1557
|
+
|
|
1558
|
+
for (auto* view : window->views) {
|
|
1559
|
+
resizeViewToFit(view);
|
|
1560
|
+
}
|
|
1561
|
+
|
|
1562
|
+
emitWindowEvent(
|
|
1563
|
+
window->id,
|
|
1564
|
+
"resize",
|
|
1565
|
+
"{\"x\":" + std::to_string(window->frame.left) +
|
|
1566
|
+
",\"y\":" + std::to_string(window->frame.top) +
|
|
1567
|
+
",\"width\":" + std::to_string(window->frame.right - window->frame.left) +
|
|
1568
|
+
",\"height\":" + std::to_string(window->frame.bottom - window->frame.top) +
|
|
1569
|
+
",\"maximized\":" + (window->maximized ? "true" : "false") +
|
|
1570
|
+
",\"minimized\":" + (window->minimized ? "true" : "false") + "}"
|
|
1571
|
+
);
|
|
1572
|
+
}
|
|
1573
|
+
break;
|
|
1574
|
+
|
|
1575
|
+
case WM_CLOSE:
|
|
1576
|
+
if (window && !window->closing.exchange(true)) {
|
|
1577
|
+
emitWindowEvent(window->id, "close");
|
|
1578
|
+
const auto views = window->views;
|
|
1579
|
+
for (auto* view : views) {
|
|
1580
|
+
closeViewHost(view);
|
|
1581
|
+
}
|
|
1582
|
+
}
|
|
1583
|
+
DestroyWindow(hwnd);
|
|
1584
|
+
return 0;
|
|
1585
|
+
|
|
1586
|
+
case WM_NCDESTROY:
|
|
1587
|
+
if (window) {
|
|
1588
|
+
window->hwnd = nullptr;
|
|
1589
|
+
SetWindowLongPtrW(hwnd, GWLP_USERDATA, 0);
|
|
1590
|
+
finalizeWindowHost(window);
|
|
1591
|
+
}
|
|
1592
|
+
return 0;
|
|
1593
|
+
}
|
|
1594
|
+
|
|
1595
|
+
return DefWindowProcW(hwnd, message, w_param, l_param);
|
|
1596
|
+
}
|
|
1597
|
+
|
|
1598
|
+
bool registerWindowClasses() {
|
|
1599
|
+
static std::once_flag once;
|
|
1600
|
+
static bool registered = false;
|
|
1601
|
+
|
|
1602
|
+
std::call_once(once, []() {
|
|
1603
|
+
WNDCLASSW window_class{};
|
|
1604
|
+
window_class.lpfnWndProc = buniteWindowProc;
|
|
1605
|
+
window_class.hInstance = GetModuleHandleW(nullptr);
|
|
1606
|
+
window_class.lpszClassName = kWindowClass;
|
|
1607
|
+
window_class.hCursor = LoadCursorW(nullptr, IDC_ARROW);
|
|
1608
|
+
window_class.hbrBackground = reinterpret_cast<HBRUSH>(COLOR_WINDOW + 1);
|
|
1609
|
+
|
|
1610
|
+
registered = RegisterClassW(&window_class) != 0;
|
|
1611
|
+
});
|
|
1612
|
+
|
|
1613
|
+
return registered;
|
|
1614
|
+
}
|
|
1615
|
+
|
|
1616
|
+
bool initializeCefOnUiThread() {
|
|
1617
|
+
if (g_runtime.process_helper_path.empty() || g_runtime.cef_dir.empty()) {
|
|
1618
|
+
std::fprintf(stderr, "[bunite/native] Missing process helper or CEF directory.\n");
|
|
1619
|
+
return false;
|
|
1620
|
+
}
|
|
1621
|
+
if (!registerWindowClasses()) {
|
|
1622
|
+
std::fprintf(stderr, "[bunite/native] Failed to register window classes.\n");
|
|
1623
|
+
return false;
|
|
1624
|
+
}
|
|
1625
|
+
|
|
1626
|
+
const std::filesystem::path cef_root(g_runtime.cef_dir);
|
|
1627
|
+
const std::filesystem::path dll_dir = std::filesystem::exists(cef_root / "Release" / "libcef.dll")
|
|
1628
|
+
? cef_root / "Release"
|
|
1629
|
+
: cef_root;
|
|
1630
|
+
const std::filesystem::path resource_dir = std::filesystem::exists(cef_root / "Resources" / "resources.pak")
|
|
1631
|
+
? cef_root / "Resources"
|
|
1632
|
+
: cef_root;
|
|
1633
|
+
const std::filesystem::path locales_dir = std::filesystem::exists(resource_dir / "locales")
|
|
1634
|
+
? resource_dir / "locales"
|
|
1635
|
+
: (std::filesystem::exists(cef_root / "Resources" / "locales")
|
|
1636
|
+
? cef_root / "Resources" / "locales"
|
|
1637
|
+
: (std::filesystem::exists(cef_root / "locales") ? cef_root / "locales" : resource_dir / "locales"));
|
|
1638
|
+
|
|
1639
|
+
SetDllDirectoryW(dll_dir.native().c_str());
|
|
1640
|
+
|
|
1641
|
+
CefMainArgs main_args(GetModuleHandleW(nullptr));
|
|
1642
|
+
CefRefPtr<BuniteCefApp> app = new BuniteCefApp();
|
|
1643
|
+
const int execute_result = CefExecuteProcess(main_args, app, nullptr);
|
|
1644
|
+
if (execute_result >= 0) {
|
|
1645
|
+
std::fprintf(stderr, "[bunite/native] CefExecuteProcess exited early with code %d.\n", execute_result);
|
|
1646
|
+
return false;
|
|
1647
|
+
}
|
|
1648
|
+
|
|
1649
|
+
CefSettings settings{};
|
|
1650
|
+
settings.no_sandbox = true;
|
|
1651
|
+
settings.multi_threaded_message_loop = true;
|
|
1652
|
+
settings.external_message_pump = false;
|
|
1653
|
+
|
|
1654
|
+
CefString(&settings.browser_subprocess_path) = g_runtime.process_helper_path;
|
|
1655
|
+
CefString(&settings.resources_dir_path) = resource_dir.wstring();
|
|
1656
|
+
CefString(&settings.locales_dir_path) = locales_dir.wstring();
|
|
1657
|
+
if (const char* user_data_dir = std::getenv("BUNITE_USER_DATA_DIR")) {
|
|
1658
|
+
CefString(&settings.cache_path) = user_data_dir;
|
|
1659
|
+
}
|
|
1660
|
+
|
|
1661
|
+
settings.log_severity = LOGSEVERITY_ERROR;
|
|
1662
|
+
CefString(&settings.log_file) = "";
|
|
1663
|
+
|
|
1664
|
+
if (!CefInitialize(main_args, settings, app, nullptr)) {
|
|
1665
|
+
std::fprintf(stderr, "[bunite/native] CefInitialize returned false.\n");
|
|
1666
|
+
return false;
|
|
1667
|
+
}
|
|
1668
|
+
|
|
1669
|
+
g_runtime.cef_initialized = true;
|
|
1670
|
+
CefRegisterSchemeHandlerFactory("views", "", new BuniteSchemeHandlerFactory());
|
|
1671
|
+
return true;
|
|
1672
|
+
}
|
|
1673
|
+
|
|
1674
|
+
void shutdownCefOnUiThread() {
|
|
1675
|
+
if (g_runtime.cef_initialized) {
|
|
1676
|
+
CefClearSchemeHandlerFactories();
|
|
1677
|
+
CefShutdown();
|
|
1678
|
+
g_runtime.cef_initialized = false;
|
|
1679
|
+
}
|
|
1680
|
+
}
|
|
1681
|
+
|
|
1682
|
+
void closeAllWindowsForShutdown() {
|
|
1683
|
+
std::vector<WindowHost*> windows;
|
|
1684
|
+
std::vector<ViewHost*> orphan_views;
|
|
1685
|
+
{
|
|
1686
|
+
std::lock_guard<std::mutex> lock(g_runtime.object_mutex);
|
|
1687
|
+
for (const auto& [_, window] : g_runtime.windows_by_id) {
|
|
1688
|
+
windows.push_back(window);
|
|
1689
|
+
}
|
|
1690
|
+
for (const auto& [_, view] : g_runtime.views_by_id) {
|
|
1691
|
+
if (!view->window) {
|
|
1692
|
+
orphan_views.push_back(view);
|
|
1693
|
+
}
|
|
1694
|
+
}
|
|
1695
|
+
}
|
|
1696
|
+
|
|
1697
|
+
for (auto* window : windows) {
|
|
1698
|
+
if (window && window->hwnd) {
|
|
1699
|
+
SendMessageW(window->hwnd, WM_CLOSE, 0, 0);
|
|
1700
|
+
}
|
|
1701
|
+
}
|
|
1702
|
+
|
|
1703
|
+
for (auto* view : orphan_views) {
|
|
1704
|
+
closeViewHost(view);
|
|
1705
|
+
}
|
|
1706
|
+
}
|
|
1707
|
+
|
|
1708
|
+
bool waitForAllViewsToClose(int timeout_ms) {
|
|
1709
|
+
const auto deadline = std::chrono::steady_clock::now() + std::chrono::milliseconds(timeout_ms);
|
|
1710
|
+
while (std::chrono::steady_clock::now() < deadline) {
|
|
1711
|
+
{
|
|
1712
|
+
std::lock_guard<std::mutex> lock(g_runtime.object_mutex);
|
|
1713
|
+
if (g_runtime.views_by_id.empty()) {
|
|
1714
|
+
return true;
|
|
1715
|
+
}
|
|
1716
|
+
}
|
|
1717
|
+
Sleep(10);
|
|
1718
|
+
}
|
|
1719
|
+
|
|
1720
|
+
return false;
|
|
1721
|
+
}
|
|
1722
|
+
|
|
1723
|
+
WindowHost* getWindowHost(void* window_ptr) {
|
|
1724
|
+
return static_cast<WindowHost*>(window_ptr);
|
|
1725
|
+
}
|
|
1726
|
+
|
|
1727
|
+
ViewHost* getViewHost(void* view_ptr) {
|
|
1728
|
+
return static_cast<ViewHost*>(view_ptr);
|
|
1729
|
+
}
|
|
1730
|
+
|
|
1731
|
+
DWORD makeWindowStyle(const std::wstring& title_bar_style) {
|
|
1732
|
+
DWORD style = WS_OVERLAPPEDWINDOW;
|
|
1733
|
+
if (title_bar_style == L"hidden" || title_bar_style == L"hiddenInset") {
|
|
1734
|
+
style &= ~WS_CAPTION;
|
|
1735
|
+
}
|
|
1736
|
+
return style;
|
|
1737
|
+
}
|
|
1738
|
+
|
|
1739
|
+
bool createBrowserForView(ViewHost* view) {
|
|
1740
|
+
auto* window = view->window;
|
|
1741
|
+
if (!window || !window->hwnd) {
|
|
1742
|
+
return false;
|
|
1743
|
+
}
|
|
1744
|
+
|
|
1745
|
+
view->client = new BuniteCefClient(view);
|
|
1746
|
+
if (!view->html.empty()) {
|
|
1747
|
+
bunite::WebviewContentStorage::instance().set(view->id, view->html);
|
|
1748
|
+
}
|
|
1749
|
+
|
|
1750
|
+
const std::string initial_url = !view->html.empty()
|
|
1751
|
+
? "views://internal/index.html"
|
|
1752
|
+
: (!view->url.empty() ? view->url : "about:blank");
|
|
1753
|
+
|
|
1754
|
+
CefWindowInfo window_info;
|
|
1755
|
+
CefRect child_bounds(
|
|
1756
|
+
view->bounds.left,
|
|
1757
|
+
view->bounds.top,
|
|
1758
|
+
view->bounds.right - view->bounds.left,
|
|
1759
|
+
view->bounds.bottom - view->bounds.top
|
|
1760
|
+
);
|
|
1761
|
+
window_info.SetAsChild(window->hwnd, child_bounds);
|
|
1762
|
+
|
|
1763
|
+
CefBrowserSettings browser_settings;
|
|
1764
|
+
CefRefPtr<CefBrowser> browser = CefBrowserHost::CreateBrowserSync(
|
|
1765
|
+
window_info,
|
|
1766
|
+
view->client,
|
|
1767
|
+
initial_url,
|
|
1768
|
+
browser_settings,
|
|
1769
|
+
nullptr,
|
|
1770
|
+
nullptr
|
|
1771
|
+
);
|
|
1772
|
+
|
|
1773
|
+
view->browser = browser;
|
|
1774
|
+
return browser != nullptr;
|
|
1775
|
+
}
|
|
1776
|
+
|
|
1777
|
+
} // namespace
|
|
1778
|
+
|
|
1779
|
+
extern "C" BUNITE_EXPORT bool bunite_init(
|
|
1780
|
+
const char* process_helper_path,
|
|
1781
|
+
const char* cef_dir,
|
|
1782
|
+
bool hide_console,
|
|
1783
|
+
bool popup_blocking,
|
|
1784
|
+
const char* chromium_flags_json
|
|
1785
|
+
) {
|
|
1786
|
+
std::lock_guard<std::mutex> lock(g_runtime.lifecycle_mutex);
|
|
1787
|
+
if (g_runtime.initialized) {
|
|
1788
|
+
return true;
|
|
1789
|
+
}
|
|
1790
|
+
g_runtime.shutting_down = false;
|
|
1791
|
+
g_runtime.process_helper_path = process_helper_path ? process_helper_path : "";
|
|
1792
|
+
g_runtime.cef_dir = cef_dir ? cef_dir : "";
|
|
1793
|
+
g_runtime.popup_blocking = popup_blocking;
|
|
1794
|
+
g_runtime.chromium_flags = parseChromiumFlagsJson(
|
|
1795
|
+
chromium_flags_json ? chromium_flags_json : "");
|
|
1796
|
+
g_runtime.cef_owner_thread = std::this_thread::get_id();
|
|
1797
|
+
|
|
1798
|
+
if (hide_console) {
|
|
1799
|
+
if (HWND console = GetConsoleWindow()) {
|
|
1800
|
+
ShowWindow(console, SW_HIDE);
|
|
1801
|
+
}
|
|
1802
|
+
}
|
|
1803
|
+
|
|
1804
|
+
g_runtime.init_success = initializeCefOnUiThread();
|
|
1805
|
+
g_runtime.init_finished = true;
|
|
1806
|
+
g_runtime.initialized = g_runtime.init_success;
|
|
1807
|
+
return g_runtime.init_success;
|
|
1808
|
+
}
|
|
1809
|
+
|
|
1810
|
+
extern "C" BUNITE_EXPORT void bunite_run_loop(void) {
|
|
1811
|
+
// The native UI thread owns the Win32 + CEF loop as soon as bunite_init succeeds.
|
|
1812
|
+
}
|
|
1813
|
+
|
|
1814
|
+
extern "C" BUNITE_EXPORT void bunite_free_cstring(const char* value) {
|
|
1815
|
+
std::free(const_cast<char*>(value));
|
|
1816
|
+
}
|
|
1817
|
+
|
|
1818
|
+
extern "C" BUNITE_EXPORT void bunite_quit(void) {
|
|
1819
|
+
bool should_shutdown = false;
|
|
1820
|
+
{
|
|
1821
|
+
std::lock_guard<std::mutex> lock(g_runtime.lifecycle_mutex);
|
|
1822
|
+
if (!g_runtime.initialized) {
|
|
1823
|
+
return;
|
|
1824
|
+
}
|
|
1825
|
+
if (g_runtime.cef_owner_thread != std::this_thread::get_id()) {
|
|
1826
|
+
std::fprintf(stderr, "[bunite/native] bunite_quit must run on the same thread as bunite_init.\n");
|
|
1827
|
+
return;
|
|
1828
|
+
}
|
|
1829
|
+
g_runtime.shutting_down = true;
|
|
1830
|
+
g_runtime.initialized = false;
|
|
1831
|
+
g_runtime.init_finished = false;
|
|
1832
|
+
g_runtime.init_success = false;
|
|
1833
|
+
should_shutdown = g_runtime.cef_initialized;
|
|
1834
|
+
}
|
|
1835
|
+
|
|
1836
|
+
if (!should_shutdown) {
|
|
1837
|
+
return;
|
|
1838
|
+
}
|
|
1839
|
+
|
|
1840
|
+
runOnUiThreadSync<void>([]() { closeAllWindowsForShutdown(); });
|
|
1841
|
+
if (!waitForAllViewsToClose(2000)) {
|
|
1842
|
+
std::fprintf(stderr, "[bunite/native] Skipping CefShutdown because browsers are still closing.\n");
|
|
1843
|
+
return;
|
|
1844
|
+
}
|
|
1845
|
+
shutdownCefOnUiThread();
|
|
1846
|
+
}
|
|
1847
|
+
|
|
1848
|
+
extern "C" BUNITE_EXPORT void bunite_set_webview_event_handler(BuniteWebviewEventHandler handler) {
|
|
1849
|
+
std::lock_guard<std::mutex> lock(g_runtime.lifecycle_mutex);
|
|
1850
|
+
g_runtime.webview_event_handler = handler;
|
|
1851
|
+
}
|
|
1852
|
+
|
|
1853
|
+
extern "C" BUNITE_EXPORT void bunite_set_window_event_handler(BuniteWindowEventHandler handler) {
|
|
1854
|
+
std::lock_guard<std::mutex> lock(g_runtime.lifecycle_mutex);
|
|
1855
|
+
g_runtime.window_event_handler = handler;
|
|
1856
|
+
}
|
|
1857
|
+
|
|
1858
|
+
extern "C" BUNITE_EXPORT void* bunite_window_create(
|
|
1859
|
+
uint32_t window_id,
|
|
1860
|
+
double x,
|
|
1861
|
+
double y,
|
|
1862
|
+
double width,
|
|
1863
|
+
double height,
|
|
1864
|
+
const char* title,
|
|
1865
|
+
const char* title_bar_style,
|
|
1866
|
+
bool transparent,
|
|
1867
|
+
bool hidden,
|
|
1868
|
+
bool minimized,
|
|
1869
|
+
bool maximized
|
|
1870
|
+
) {
|
|
1871
|
+
return runOnUiThreadSync<void*>([=]() -> void* {
|
|
1872
|
+
auto* window = new WindowHost{
|
|
1873
|
+
window_id,
|
|
1874
|
+
nullptr,
|
|
1875
|
+
utf8ToWide(title ? title : ""),
|
|
1876
|
+
utf8ToWide(title_bar_style ? title_bar_style : ""),
|
|
1877
|
+
RECT{
|
|
1878
|
+
static_cast<LONG>(x),
|
|
1879
|
+
static_cast<LONG>(y),
|
|
1880
|
+
static_cast<LONG>(x + width),
|
|
1881
|
+
static_cast<LONG>(y + height)
|
|
1882
|
+
},
|
|
1883
|
+
transparent,
|
|
1884
|
+
hidden,
|
|
1885
|
+
minimized,
|
|
1886
|
+
maximized,
|
|
1887
|
+
false
|
|
1888
|
+
};
|
|
1889
|
+
|
|
1890
|
+
window->hwnd = CreateWindowExW(
|
|
1891
|
+
0,
|
|
1892
|
+
kWindowClass,
|
|
1893
|
+
window->title.c_str(),
|
|
1894
|
+
makeWindowStyle(window->title_bar_style),
|
|
1895
|
+
static_cast<int>(x),
|
|
1896
|
+
static_cast<int>(y),
|
|
1897
|
+
static_cast<int>(std::max(width, 100.0)),
|
|
1898
|
+
static_cast<int>(std::max(height, 100.0)),
|
|
1899
|
+
nullptr,
|
|
1900
|
+
nullptr,
|
|
1901
|
+
GetModuleHandleW(nullptr),
|
|
1902
|
+
window
|
|
1903
|
+
);
|
|
1904
|
+
|
|
1905
|
+
if (!window->hwnd) {
|
|
1906
|
+
delete window;
|
|
1907
|
+
return nullptr;
|
|
1908
|
+
}
|
|
1909
|
+
|
|
1910
|
+
{
|
|
1911
|
+
std::lock_guard<std::mutex> lock(g_runtime.object_mutex);
|
|
1912
|
+
g_runtime.windows_by_id[window_id] = window;
|
|
1913
|
+
}
|
|
1914
|
+
|
|
1915
|
+
if (!hidden) {
|
|
1916
|
+
ShowWindow(window->hwnd, minimized ? SW_SHOWMINIMIZED : (maximized ? SW_SHOWMAXIMIZED : SW_SHOW));
|
|
1917
|
+
UpdateWindow(window->hwnd);
|
|
1918
|
+
}
|
|
1919
|
+
|
|
1920
|
+
return window;
|
|
1921
|
+
});
|
|
1922
|
+
}
|
|
1923
|
+
|
|
1924
|
+
extern "C" BUNITE_EXPORT void bunite_window_show(void* window_ptr) {
|
|
1925
|
+
runOnUiThreadSync<void>([window = getWindowHost(window_ptr)]() {
|
|
1926
|
+
if (!window || !window->hwnd) {
|
|
1927
|
+
return;
|
|
1928
|
+
}
|
|
1929
|
+
window->hidden = false;
|
|
1930
|
+
ShowWindow(
|
|
1931
|
+
window->hwnd,
|
|
1932
|
+
window->minimized ? SW_SHOWMINIMIZED : (window->maximized ? SW_SHOWMAXIMIZED : SW_SHOW)
|
|
1933
|
+
);
|
|
1934
|
+
SetForegroundWindow(window->hwnd);
|
|
1935
|
+
});
|
|
1936
|
+
}
|
|
1937
|
+
|
|
1938
|
+
extern "C" BUNITE_EXPORT void bunite_window_close(void* window_ptr) {
|
|
1939
|
+
runOnUiThreadSync<void>([window = getWindowHost(window_ptr)]() {
|
|
1940
|
+
if (!window || !window->hwnd) {
|
|
1941
|
+
return;
|
|
1942
|
+
}
|
|
1943
|
+
SendMessageW(window->hwnd, WM_CLOSE, 0, 0);
|
|
1944
|
+
});
|
|
1945
|
+
}
|
|
1946
|
+
|
|
1947
|
+
extern "C" BUNITE_EXPORT void bunite_window_set_title(void* window_ptr, const char* title) {
|
|
1948
|
+
runOnUiThreadSync<void>([window = getWindowHost(window_ptr), value = std::string(title ? title : "")]() {
|
|
1949
|
+
if (!window || !window->hwnd) {
|
|
1950
|
+
return;
|
|
1951
|
+
}
|
|
1952
|
+
window->title = utf8ToWide(value);
|
|
1953
|
+
SetWindowTextW(window->hwnd, window->title.c_str());
|
|
1954
|
+
});
|
|
1955
|
+
}
|
|
1956
|
+
|
|
1957
|
+
extern "C" BUNITE_EXPORT void bunite_window_minimize(void* window_ptr) {
|
|
1958
|
+
runOnUiThreadSync<void>([window = getWindowHost(window_ptr)]() {
|
|
1959
|
+
if (!window || !window->hwnd) {
|
|
1960
|
+
return;
|
|
1961
|
+
}
|
|
1962
|
+
|
|
1963
|
+
window->restore_maximized_after_minimize = window->maximized;
|
|
1964
|
+
window->minimized = true;
|
|
1965
|
+
window->maximized = false;
|
|
1966
|
+
if (window->hidden) {
|
|
1967
|
+
return;
|
|
1968
|
+
}
|
|
1969
|
+
|
|
1970
|
+
ShowWindow(window->hwnd, SW_MINIMIZE);
|
|
1971
|
+
});
|
|
1972
|
+
}
|
|
1973
|
+
|
|
1974
|
+
extern "C" BUNITE_EXPORT void bunite_window_unminimize(void* window_ptr) {
|
|
1975
|
+
runOnUiThreadSync<void>([window = getWindowHost(window_ptr)]() {
|
|
1976
|
+
if (!window || !window->hwnd) {
|
|
1977
|
+
return;
|
|
1978
|
+
}
|
|
1979
|
+
|
|
1980
|
+
window->minimized = false;
|
|
1981
|
+
if (window->hidden) {
|
|
1982
|
+
window->maximized = window->restore_maximized_after_minimize;
|
|
1983
|
+
window->restore_maximized_after_minimize = false;
|
|
1984
|
+
return;
|
|
1985
|
+
}
|
|
1986
|
+
|
|
1987
|
+
ShowWindow(window->hwnd, SW_RESTORE);
|
|
1988
|
+
});
|
|
1989
|
+
}
|
|
1990
|
+
|
|
1991
|
+
extern "C" BUNITE_EXPORT bool bunite_window_is_minimized(void* window_ptr) {
|
|
1992
|
+
return runOnUiThreadSync<bool>([window = getWindowHost(window_ptr)]() -> bool {
|
|
1993
|
+
if (!window || !window->hwnd) {
|
|
1994
|
+
return false;
|
|
1995
|
+
}
|
|
1996
|
+
if (window->hidden) {
|
|
1997
|
+
return window->minimized;
|
|
1998
|
+
}
|
|
1999
|
+
|
|
2000
|
+
window->minimized = IsIconic(window->hwnd) != 0;
|
|
2001
|
+
return window->minimized;
|
|
2002
|
+
});
|
|
2003
|
+
}
|
|
2004
|
+
|
|
2005
|
+
extern "C" BUNITE_EXPORT void bunite_window_maximize(void* window_ptr) {
|
|
2006
|
+
runOnUiThreadSync<void>([window = getWindowHost(window_ptr)]() {
|
|
2007
|
+
if (!window || !window->hwnd) {
|
|
2008
|
+
return;
|
|
2009
|
+
}
|
|
2010
|
+
|
|
2011
|
+
window->minimized = false;
|
|
2012
|
+
window->restore_maximized_after_minimize = false;
|
|
2013
|
+
window->maximized = true;
|
|
2014
|
+
if (window->hidden) {
|
|
2015
|
+
return;
|
|
2016
|
+
}
|
|
2017
|
+
|
|
2018
|
+
ShowWindow(window->hwnd, SW_MAXIMIZE);
|
|
2019
|
+
});
|
|
2020
|
+
}
|
|
2021
|
+
|
|
2022
|
+
extern "C" BUNITE_EXPORT void bunite_window_unmaximize(void* window_ptr) {
|
|
2023
|
+
runOnUiThreadSync<void>([window = getWindowHost(window_ptr)]() {
|
|
2024
|
+
if (!window || !window->hwnd) {
|
|
2025
|
+
return;
|
|
2026
|
+
}
|
|
2027
|
+
|
|
2028
|
+
window->minimized = false;
|
|
2029
|
+
window->restore_maximized_after_minimize = false;
|
|
2030
|
+
window->maximized = false;
|
|
2031
|
+
if (window->hidden) {
|
|
2032
|
+
return;
|
|
2033
|
+
}
|
|
2034
|
+
|
|
2035
|
+
ShowWindow(window->hwnd, SW_RESTORE);
|
|
2036
|
+
});
|
|
2037
|
+
}
|
|
2038
|
+
|
|
2039
|
+
extern "C" BUNITE_EXPORT bool bunite_window_is_maximized(void* window_ptr) {
|
|
2040
|
+
return runOnUiThreadSync<bool>([window = getWindowHost(window_ptr)]() -> bool {
|
|
2041
|
+
if (!window || !window->hwnd) {
|
|
2042
|
+
return false;
|
|
2043
|
+
}
|
|
2044
|
+
if (window->hidden) {
|
|
2045
|
+
return window->maximized;
|
|
2046
|
+
}
|
|
2047
|
+
|
|
2048
|
+
window->maximized = IsZoomed(window->hwnd) != 0;
|
|
2049
|
+
return window->maximized;
|
|
2050
|
+
});
|
|
2051
|
+
}
|
|
2052
|
+
|
|
2053
|
+
extern "C" BUNITE_EXPORT void bunite_window_set_frame(
|
|
2054
|
+
void* window_ptr,
|
|
2055
|
+
double x,
|
|
2056
|
+
double y,
|
|
2057
|
+
double width,
|
|
2058
|
+
double height
|
|
2059
|
+
) {
|
|
2060
|
+
runOnUiThreadSync<void>([window = getWindowHost(window_ptr), x, y, width, height]() {
|
|
2061
|
+
if (!window || !window->hwnd) {
|
|
2062
|
+
return;
|
|
2063
|
+
}
|
|
2064
|
+
|
|
2065
|
+
SetWindowPos(
|
|
2066
|
+
window->hwnd,
|
|
2067
|
+
nullptr,
|
|
2068
|
+
static_cast<int>(x),
|
|
2069
|
+
static_cast<int>(y),
|
|
2070
|
+
static_cast<int>(std::max(width, 100.0)),
|
|
2071
|
+
static_cast<int>(std::max(height, 100.0)),
|
|
2072
|
+
SWP_NOZORDER | SWP_NOACTIVATE
|
|
2073
|
+
);
|
|
2074
|
+
});
|
|
2075
|
+
}
|
|
2076
|
+
|
|
2077
|
+
extern "C" BUNITE_EXPORT void* bunite_view_create(
|
|
2078
|
+
uint32_t view_id,
|
|
2079
|
+
void* window_ptr,
|
|
2080
|
+
const char* url,
|
|
2081
|
+
const char* html,
|
|
2082
|
+
const char* preload,
|
|
2083
|
+
const char* views_root,
|
|
2084
|
+
const char* navigation_rules_json,
|
|
2085
|
+
double x,
|
|
2086
|
+
double y,
|
|
2087
|
+
double width,
|
|
2088
|
+
double height,
|
|
2089
|
+
bool auto_resize,
|
|
2090
|
+
bool sandbox
|
|
2091
|
+
) {
|
|
2092
|
+
return runOnUiThreadSync<void*>([=]() -> void* {
|
|
2093
|
+
auto* window = getWindowHost(window_ptr);
|
|
2094
|
+
if (!window || !window->hwnd) {
|
|
2095
|
+
return nullptr;
|
|
2096
|
+
}
|
|
2097
|
+
|
|
2098
|
+
auto* view = new ViewHost{
|
|
2099
|
+
view_id,
|
|
2100
|
+
window,
|
|
2101
|
+
RECT{
|
|
2102
|
+
static_cast<LONG>(x),
|
|
2103
|
+
static_cast<LONG>(y),
|
|
2104
|
+
static_cast<LONG>(x + width),
|
|
2105
|
+
static_cast<LONG>(y + height)
|
|
2106
|
+
},
|
|
2107
|
+
url ? url : "",
|
|
2108
|
+
html ? html : "",
|
|
2109
|
+
preload ? preload : "",
|
|
2110
|
+
views_root ? views_root : "",
|
|
2111
|
+
parseNavigationRulesJson(navigation_rules_json ? navigation_rules_json : ""),
|
|
2112
|
+
auto_resize ? ANCHOR_FILL : ANCHOR_NONE,
|
|
2113
|
+
0.0,
|
|
2114
|
+
sandbox
|
|
2115
|
+
};
|
|
2116
|
+
|
|
2117
|
+
{
|
|
2118
|
+
std::lock_guard<std::mutex> lock(g_runtime.object_mutex);
|
|
2119
|
+
g_runtime.views_by_id[view_id] = view;
|
|
2120
|
+
window->views.push_back(view);
|
|
2121
|
+
}
|
|
2122
|
+
|
|
2123
|
+
if (!createBrowserForView(view)) {
|
|
2124
|
+
{
|
|
2125
|
+
std::lock_guard<std::mutex> lock(g_runtime.object_mutex);
|
|
2126
|
+
g_runtime.views_by_id.erase(view_id);
|
|
2127
|
+
window->views.erase(std::remove(window->views.begin(), window->views.end(), view), window->views.end());
|
|
2128
|
+
}
|
|
2129
|
+
delete view;
|
|
2130
|
+
return nullptr;
|
|
2131
|
+
}
|
|
2132
|
+
|
|
2133
|
+
return view;
|
|
2134
|
+
});
|
|
2135
|
+
}
|
|
2136
|
+
|
|
2137
|
+
extern "C" BUNITE_EXPORT void bunite_view_load_url(void* view_ptr, const char* url) {
|
|
2138
|
+
runOnUiThreadSync<void>([view = getViewHost(view_ptr), next_url = std::string(url ? url : "")]() {
|
|
2139
|
+
if (!view) {
|
|
2140
|
+
return;
|
|
2141
|
+
}
|
|
2142
|
+
|
|
2143
|
+
view->url = next_url;
|
|
2144
|
+
view->html.clear();
|
|
2145
|
+
bunite::WebviewContentStorage::instance().remove(view->id);
|
|
2146
|
+
if (view->browser && view->browser->GetMainFrame()) {
|
|
2147
|
+
view->browser->GetMainFrame()->LoadURL(next_url);
|
|
2148
|
+
}
|
|
2149
|
+
});
|
|
2150
|
+
}
|
|
2151
|
+
|
|
2152
|
+
extern "C" BUNITE_EXPORT void bunite_view_load_html(void* view_ptr, const char* html) {
|
|
2153
|
+
runOnUiThreadSync<void>([view = getViewHost(view_ptr), content = std::string(html ? html : "")]() {
|
|
2154
|
+
if (!view) {
|
|
2155
|
+
return;
|
|
2156
|
+
}
|
|
2157
|
+
|
|
2158
|
+
view->html = content;
|
|
2159
|
+
bunite::WebviewContentStorage::instance().set(view->id, content);
|
|
2160
|
+
if (view->browser && view->browser->GetMainFrame()) {
|
|
2161
|
+
view->browser->GetMainFrame()->LoadURL("views://internal/index.html");
|
|
2162
|
+
}
|
|
2163
|
+
});
|
|
2164
|
+
}
|
|
2165
|
+
|
|
2166
|
+
extern "C" BUNITE_EXPORT void bunite_view_set_visible(void* view_ptr, bool visible) {
|
|
2167
|
+
runOnUiThreadSync<void>([view = getViewHost(view_ptr), visible]() {
|
|
2168
|
+
if (!view || !view->browser) {
|
|
2169
|
+
return;
|
|
2170
|
+
}
|
|
2171
|
+
HWND browser_hwnd = view->browser->GetHost()->GetWindowHandle();
|
|
2172
|
+
if (browser_hwnd) {
|
|
2173
|
+
ShowWindow(browser_hwnd, visible ? SW_SHOW : SW_HIDE);
|
|
2174
|
+
}
|
|
2175
|
+
});
|
|
2176
|
+
}
|
|
2177
|
+
|
|
2178
|
+
extern "C" BUNITE_EXPORT void bunite_view_set_bounds(
|
|
2179
|
+
void* view_ptr,
|
|
2180
|
+
double x,
|
|
2181
|
+
double y,
|
|
2182
|
+
double width,
|
|
2183
|
+
double height
|
|
2184
|
+
) {
|
|
2185
|
+
runOnUiThreadSync<void>([view = getViewHost(view_ptr), x, y, width, height]() {
|
|
2186
|
+
if (!view || !view->browser) {
|
|
2187
|
+
return;
|
|
2188
|
+
}
|
|
2189
|
+
view->anchor_mode = ANCHOR_NONE;
|
|
2190
|
+
view->bounds = RECT{
|
|
2191
|
+
static_cast<LONG>(x),
|
|
2192
|
+
static_cast<LONG>(y),
|
|
2193
|
+
static_cast<LONG>(x + width),
|
|
2194
|
+
static_cast<LONG>(y + height)
|
|
2195
|
+
};
|
|
2196
|
+
HWND browser_hwnd = view->browser->GetHost()->GetWindowHandle();
|
|
2197
|
+
if (browser_hwnd) {
|
|
2198
|
+
SetWindowPos(
|
|
2199
|
+
browser_hwnd,
|
|
2200
|
+
nullptr,
|
|
2201
|
+
view->bounds.left,
|
|
2202
|
+
view->bounds.top,
|
|
2203
|
+
view->bounds.right - view->bounds.left,
|
|
2204
|
+
view->bounds.bottom - view->bounds.top,
|
|
2205
|
+
SWP_NOZORDER | SWP_NOACTIVATE
|
|
2206
|
+
);
|
|
2207
|
+
}
|
|
2208
|
+
});
|
|
2209
|
+
}
|
|
2210
|
+
|
|
2211
|
+
extern "C" BUNITE_EXPORT void bunite_register_view_route(const char* path) {
|
|
2212
|
+
bunite::ViewsRouteStorage::instance().registerRoute(path ? path : "");
|
|
2213
|
+
}
|
|
2214
|
+
|
|
2215
|
+
extern "C" BUNITE_EXPORT void bunite_unregister_view_route(const char* path) {
|
|
2216
|
+
bunite::ViewsRouteStorage::instance().unregisterRoute(path ? path : "");
|
|
2217
|
+
}
|
|
2218
|
+
|
|
2219
|
+
extern "C" BUNITE_EXPORT void bunite_complete_route_request(uint32_t request_id, const char* html) {
|
|
2220
|
+
std::lock_guard<std::mutex> lock(g_runtime.route_mutex);
|
|
2221
|
+
const auto it = g_runtime.pending_routes.find(request_id);
|
|
2222
|
+
if (it == g_runtime.pending_routes.end()) {
|
|
2223
|
+
return;
|
|
2224
|
+
}
|
|
2225
|
+
|
|
2226
|
+
auto pending = std::move(it->second);
|
|
2227
|
+
g_runtime.pending_routes.erase(it);
|
|
2228
|
+
|
|
2229
|
+
bunite::ViewsRouteStorage::instance().setResponse(request_id, html ? html : "");
|
|
2230
|
+
|
|
2231
|
+
// Store response data on the scheme handler via the request_id.
|
|
2232
|
+
// The scheme handler's GetResponseHeaders/ReadResponse will use it.
|
|
2233
|
+
// We signal the callback on the IO thread.
|
|
2234
|
+
if (pending.callback) {
|
|
2235
|
+
pending.callback->Continue();
|
|
2236
|
+
}
|
|
2237
|
+
}
|
|
2238
|
+
|
|
2239
|
+
extern "C" BUNITE_EXPORT void bunite_view_set_anchor(void* view_ptr, int mode, double inset) {
|
|
2240
|
+
runOnUiThreadSync<void>([view = getViewHost(view_ptr), mode, inset]() {
|
|
2241
|
+
if (!view) {
|
|
2242
|
+
return;
|
|
2243
|
+
}
|
|
2244
|
+
view->anchor_mode = mode;
|
|
2245
|
+
view->anchor_inset = inset;
|
|
2246
|
+
resizeViewToFit(view);
|
|
2247
|
+
});
|
|
2248
|
+
}
|
|
2249
|
+
|
|
2250
|
+
extern "C" BUNITE_EXPORT void bunite_view_go_back(void* view_ptr) {
|
|
2251
|
+
runOnUiThreadSync<void>([view = getViewHost(view_ptr)]() {
|
|
2252
|
+
if (view && view->browser) {
|
|
2253
|
+
view->browser->GoBack();
|
|
2254
|
+
}
|
|
2255
|
+
});
|
|
2256
|
+
}
|
|
2257
|
+
|
|
2258
|
+
extern "C" BUNITE_EXPORT void bunite_view_reload(void* view_ptr) {
|
|
2259
|
+
runOnUiThreadSync<void>([view = getViewHost(view_ptr)]() {
|
|
2260
|
+
if (view && view->browser) {
|
|
2261
|
+
view->browser->Reload();
|
|
2262
|
+
}
|
|
2263
|
+
});
|
|
2264
|
+
}
|
|
2265
|
+
|
|
2266
|
+
extern "C" BUNITE_EXPORT void bunite_view_remove(void* view_ptr) {
|
|
2267
|
+
runOnUiThreadSync<void>([view = getViewHost(view_ptr)]() { closeViewHost(view); });
|
|
2268
|
+
}
|
|
2269
|
+
|
|
2270
|
+
extern "C" BUNITE_EXPORT void bunite_view_open_devtools(void* view_ptr) {
|
|
2271
|
+
runOnUiThreadSync<void>([view = getViewHost(view_ptr)]() { openDevToolsForView(view); });
|
|
2272
|
+
}
|
|
2273
|
+
|
|
2274
|
+
extern "C" BUNITE_EXPORT void bunite_view_close_devtools(void* view_ptr) {
|
|
2275
|
+
runOnUiThreadSync<void>([view = getViewHost(view_ptr)]() { closeDevToolsForView(view); });
|
|
2276
|
+
}
|
|
2277
|
+
|
|
2278
|
+
extern "C" BUNITE_EXPORT void bunite_view_toggle_devtools(void* view_ptr) {
|
|
2279
|
+
runOnUiThreadSync<void>([view = getViewHost(view_ptr)]() { toggleDevToolsForView(view); });
|
|
2280
|
+
}
|
|
2281
|
+
|
|
2282
|
+
extern "C" BUNITE_EXPORT void bunite_complete_permission_request(uint32_t request_id, uint32_t state) {
|
|
2283
|
+
runOnUiThreadSync<void>([=]() {
|
|
2284
|
+
std::optional<PendingPermissionRequest> request;
|
|
2285
|
+
{
|
|
2286
|
+
std::lock_guard<std::mutex> lock(g_runtime.permission_mutex);
|
|
2287
|
+
const auto it = g_runtime.pending_permissions.find(request_id);
|
|
2288
|
+
if (it == g_runtime.pending_permissions.end()) {
|
|
2289
|
+
return;
|
|
2290
|
+
}
|
|
2291
|
+
request = it->second;
|
|
2292
|
+
g_runtime.pending_permissions.erase(it);
|
|
2293
|
+
}
|
|
2294
|
+
|
|
2295
|
+
if (!request) {
|
|
2296
|
+
return;
|
|
2297
|
+
}
|
|
2298
|
+
|
|
2299
|
+
const bool allow = state != 0;
|
|
2300
|
+
if (request->kind == PermissionRequestKind::Prompt && request->prompt_callback) {
|
|
2301
|
+
request->prompt_callback->Continue(
|
|
2302
|
+
allow ? CEF_PERMISSION_RESULT_ACCEPT : CEF_PERMISSION_RESULT_DENY
|
|
2303
|
+
);
|
|
2304
|
+
return;
|
|
2305
|
+
}
|
|
2306
|
+
|
|
2307
|
+
if (request->kind == PermissionRequestKind::MediaAccess && request->media_callback) {
|
|
2308
|
+
if (allow) {
|
|
2309
|
+
request->media_callback->Continue(request->permissions);
|
|
2310
|
+
} else {
|
|
2311
|
+
request->media_callback->Cancel();
|
|
2312
|
+
}
|
|
2313
|
+
}
|
|
2314
|
+
});
|
|
2315
|
+
}
|
|
2316
|
+
|
|
2317
|
+
extern "C" BUNITE_EXPORT int32_t bunite_show_message_box(
|
|
2318
|
+
const char* type,
|
|
2319
|
+
const char* title,
|
|
2320
|
+
const char* message,
|
|
2321
|
+
const char* detail,
|
|
2322
|
+
const char* buttons,
|
|
2323
|
+
int32_t default_id,
|
|
2324
|
+
int32_t cancel_id
|
|
2325
|
+
) {
|
|
2326
|
+
return runOnUiThreadSync<int32_t>([=]() -> int32_t {
|
|
2327
|
+
std::string composed_message = message ? message : "";
|
|
2328
|
+
if (detail && std::strlen(detail) > 0) {
|
|
2329
|
+
if (!composed_message.empty()) {
|
|
2330
|
+
composed_message += "\n\n";
|
|
2331
|
+
}
|
|
2332
|
+
composed_message += detail;
|
|
2333
|
+
}
|
|
2334
|
+
|
|
2335
|
+
UINT flags = MB_OK;
|
|
2336
|
+
const std::string type_name = type ? type : "info";
|
|
2337
|
+
if (type_name == "none") {
|
|
2338
|
+
// Intentionally no icon flag.
|
|
2339
|
+
} else if (type_name == "warning") {
|
|
2340
|
+
flags |= MB_ICONWARNING;
|
|
2341
|
+
} else if (type_name == "error") {
|
|
2342
|
+
flags |= MB_ICONERROR;
|
|
2343
|
+
} else if (type_name == "question") {
|
|
2344
|
+
flags |= MB_ICONQUESTION;
|
|
2345
|
+
} else {
|
|
2346
|
+
flags |= MB_ICONINFORMATION;
|
|
2347
|
+
}
|
|
2348
|
+
|
|
2349
|
+
const std::vector<std::string> labels = splitButtonLabels(buttons ? buttons : "");
|
|
2350
|
+
std::vector<std::string> normalized_labels;
|
|
2351
|
+
normalized_labels.reserve(labels.size());
|
|
2352
|
+
for (const std::string& label : labels) {
|
|
2353
|
+
normalized_labels.push_back(toLowerAscii(trimAsciiWhitespace(label)));
|
|
2354
|
+
}
|
|
2355
|
+
|
|
2356
|
+
if (normalized_labels.size() == 2) {
|
|
2357
|
+
if (normalized_labels[0] == "yes" && normalized_labels[1] == "no") {
|
|
2358
|
+
flags = (flags & ~MB_OK) | MB_YESNO;
|
|
2359
|
+
} else {
|
|
2360
|
+
flags = (flags & ~MB_OK) | MB_OKCANCEL;
|
|
2361
|
+
}
|
|
2362
|
+
} else if (
|
|
2363
|
+
normalized_labels.size() >= 3 &&
|
|
2364
|
+
normalized_labels[0] == "yes" &&
|
|
2365
|
+
normalized_labels[1] == "no" &&
|
|
2366
|
+
normalized_labels[2] == "cancel"
|
|
2367
|
+
) {
|
|
2368
|
+
flags = (flags & ~MB_OK) | MB_YESNOCANCEL;
|
|
2369
|
+
}
|
|
2370
|
+
|
|
2371
|
+
if (default_id == 1) {
|
|
2372
|
+
flags |= MB_DEFBUTTON2;
|
|
2373
|
+
} else if (default_id >= 2) {
|
|
2374
|
+
flags |= MB_DEFBUTTON3;
|
|
2375
|
+
}
|
|
2376
|
+
|
|
2377
|
+
const std::wstring window_title = utf8ToWide(title ? title : "");
|
|
2378
|
+
const std::wstring window_message = utf8ToWide(composed_message);
|
|
2379
|
+
const int result = MessageBoxW(GetActiveWindow(), window_message.c_str(), window_title.c_str(), flags);
|
|
2380
|
+
|
|
2381
|
+
switch (result) {
|
|
2382
|
+
case IDOK:
|
|
2383
|
+
case IDYES:
|
|
2384
|
+
return 0;
|
|
2385
|
+
case IDNO:
|
|
2386
|
+
return 1;
|
|
2387
|
+
case IDCANCEL:
|
|
2388
|
+
// `-1` means the JS side did not provide an explicit cancel target.
|
|
2389
|
+
return cancel_id >= 0 ? cancel_id : (labels.size() > 2 ? 2 : 1);
|
|
2390
|
+
default:
|
|
2391
|
+
return cancel_id >= 0 ? cancel_id : -1;
|
|
2392
|
+
}
|
|
2393
|
+
});
|
|
2394
|
+
}
|
|
2395
|
+
|
|
2396
|
+
extern "C" BUNITE_EXPORT uint32_t bunite_show_browser_message_box(
|
|
2397
|
+
const char* type,
|
|
2398
|
+
const char* title,
|
|
2399
|
+
const char* message,
|
|
2400
|
+
const char* detail,
|
|
2401
|
+
const char* buttons,
|
|
2402
|
+
int32_t default_id,
|
|
2403
|
+
int32_t cancel_id
|
|
2404
|
+
) {
|
|
2405
|
+
return runOnUiThreadSync<uint32_t>([=]() -> uint32_t {
|
|
2406
|
+
ViewHost* view = getPreferredMessageBoxView();
|
|
2407
|
+
if (!view || !view->browser || !view->browser->GetMainFrame()) {
|
|
2408
|
+
return 0;
|
|
2409
|
+
}
|
|
2410
|
+
|
|
2411
|
+
const uint32_t request_id = [&]() {
|
|
2412
|
+
std::lock_guard<std::mutex> lock(g_runtime.message_box_mutex);
|
|
2413
|
+
uint32_t id = g_runtime.next_message_box_request_id++;
|
|
2414
|
+
if (id == 0) {
|
|
2415
|
+
id = g_runtime.next_message_box_request_id++;
|
|
2416
|
+
}
|
|
2417
|
+
g_runtime.pending_message_boxes.emplace(
|
|
2418
|
+
id,
|
|
2419
|
+
PendingMessageBoxRequest{
|
|
2420
|
+
view->id,
|
|
2421
|
+
cancel_id
|
|
2422
|
+
}
|
|
2423
|
+
);
|
|
2424
|
+
return id;
|
|
2425
|
+
}();
|
|
2426
|
+
|
|
2427
|
+
const std::vector<std::string> labels = splitButtonLabels(buttons ? buttons : "");
|
|
2428
|
+
const std::vector<std::string> browser_labels = labels.empty()
|
|
2429
|
+
? std::vector<std::string>{ "OK" }
|
|
2430
|
+
: labels;
|
|
2431
|
+
|
|
2432
|
+
view->browser->GetMainFrame()->ExecuteJavaScript(
|
|
2433
|
+
buildBrowserMessageBoxScript(
|
|
2434
|
+
request_id,
|
|
2435
|
+
type ? type : "info",
|
|
2436
|
+
title ? title : "",
|
|
2437
|
+
message ? message : "",
|
|
2438
|
+
detail ? detail : "",
|
|
2439
|
+
browser_labels,
|
|
2440
|
+
default_id,
|
|
2441
|
+
cancel_id
|
|
2442
|
+
),
|
|
2443
|
+
view->browser->GetMainFrame()->GetURL(),
|
|
2444
|
+
0
|
|
2445
|
+
);
|
|
2446
|
+
|
|
2447
|
+
return request_id;
|
|
2448
|
+
});
|
|
2449
|
+
}
|
|
2450
|
+
|
|
2451
|
+
extern "C" BUNITE_EXPORT void bunite_cancel_browser_message_box(uint32_t request_id) {
|
|
2452
|
+
runOnUiThreadSync<void>([=]() { cancelPendingMessageBoxRequest(request_id); });
|
|
2453
|
+
}
|