bunite-core 0.12.1 → 0.16.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +4 -4
- package/src/host/core/App.ts +19 -2
- package/src/host/core/BrowserView.ts +515 -38
- package/src/host/core/SurfaceBrowserIPC.ts +53 -3
- package/src/host/core/SurfaceManager.ts +603 -30
- package/src/host/core/SurfaceRegistry.ts +9 -1
- package/src/host/core/inputDispatch.ts +147 -0
- package/src/host/events/webviewEvents.ts +25 -1
- package/src/host/log.ts +6 -1
- package/src/host/native.ts +263 -1
- package/src/host/preloadBundle.ts +7 -2
- package/src/native/linux/bunite_linux_ffi.cpp +427 -6
- package/src/native/linux/bunite_linux_internal.h +18 -0
- package/src/native/linux/bunite_linux_runtime.cpp +6 -1
- package/src/native/linux/bunite_linux_utils.cpp +2 -2
- package/src/native/linux/bunite_linux_view.cpp +296 -5
- package/src/native/mac/bunite_mac_ffi.mm +630 -8
- package/src/native/mac/bunite_mac_internal.h +19 -0
- package/src/native/mac/bunite_mac_utils.mm +2 -2
- package/src/native/mac/bunite_mac_view.mm +371 -9
- package/src/native/shared/ffi_exports.h +200 -2
- package/src/native/win/native_host_cef.cpp +186 -11
- package/src/native/win/native_host_ffi.cpp +1194 -1
- package/src/native/win/native_host_internal.h +35 -0
- package/src/native/win/native_host_utils.cpp +2 -1
- package/src/native/win/process_helper_win.cpp +54 -27
- package/src/native/win-webview2/bunite_webview2_ffi.cpp +1023 -12
- package/src/native/win-webview2/webview2_internal.h +25 -0
- package/src/native/win-webview2/webview2_runtime.cpp +403 -34
- package/src/native/win-webview2/webview2_utils.cpp +30 -12
- package/src/preload/runtime.built.js +1 -1
- package/src/preload/runtime.ts +97 -0
- package/src/rpc/framework.ts +340 -8
- package/src/rpc/index.ts +32 -0
- package/src/webview/native.ts +253 -51
- package/src/webview/polyfill.ts +283 -22
|
@@ -61,6 +61,16 @@ struct ViewState {
|
|
|
61
61
|
// HTML stashed by load_html; appres handler serves at internal/index.html.
|
|
62
62
|
std::string stored_html;
|
|
63
63
|
std::vector<std::string> navigation_rules;
|
|
64
|
+
|
|
65
|
+
// Page-initiated dialogs awaiting host response (alert/confirm/prompt).
|
|
66
|
+
// WKUIDelegate completion handlers are held in `__strong` blocks until
|
|
67
|
+
// respondToDialog invokes them; the page execution is paused meanwhile.
|
|
68
|
+
std::unordered_map<uint32_t, void(^)(bool /*accept*/, const std::string& /*text*/)> pending_dialogs;
|
|
69
|
+
uint32_t next_dialog_request_id = 1;
|
|
70
|
+
|
|
71
|
+
// Download policy: 0=auto, 1=ask (treated as block), 2=block (default).
|
|
72
|
+
std::atomic<int32_t> download_policy{2};
|
|
73
|
+
std::string download_dir;
|
|
64
74
|
};
|
|
65
75
|
|
|
66
76
|
// ---------------------------------------------------------------------------
|
|
@@ -86,6 +96,11 @@ struct RuntimeState {
|
|
|
86
96
|
|
|
87
97
|
BuniteWebviewEventHandler webview_event_handler = nullptr;
|
|
88
98
|
BuniteWindowEventHandler window_event_handler = nullptr;
|
|
99
|
+
|
|
100
|
+
// Hidden NSWindow — popup-minted WKWebViews park here until adoption.
|
|
101
|
+
__strong NSWindow* popup_parent = nil;
|
|
102
|
+
// Parked popup webviews awaiting acceptPopup/dismissPopup. Key = popup view_id (>= 0x80000000).
|
|
103
|
+
__strong NSMutableDictionary<NSNumber*, WKWebView*>* parked_popups = nil;
|
|
89
104
|
};
|
|
90
105
|
|
|
91
106
|
extern RuntimeState g_runtime;
|
|
@@ -133,6 +148,10 @@ void destroyWindow(uint32_t window_id);
|
|
|
133
148
|
// Defined in bunite_mac_view.mm.
|
|
134
149
|
ViewState* findView(uint32_t view_id);
|
|
135
150
|
uint32_t viewIdForWebView(WKWebView* wv); // returns 0 if not tracked
|
|
151
|
+
void registerWebViewId(WKWebView* wv, uint32_t view_id);
|
|
152
|
+
void unregisterWebViewId(WKWebView* wv);
|
|
153
|
+
bool acceptParkedPopup(uint32_t new_view_id, uint32_t host_window_id, double x, double y, double w, double h);
|
|
154
|
+
void dismissParkedPopup(uint32_t new_view_id);
|
|
136
155
|
bool createView(uint32_t view_id, uint32_t window_id,
|
|
137
156
|
NSString* url, NSString* html, NSString* preload, NSString* appres_root,
|
|
138
157
|
NSString* navigation_rules_json, NSString* preload_origins_json,
|
|
@@ -100,8 +100,8 @@ std::vector<std::string> parseNavigationRulesJson(NSString* json) {
|
|
|
100
100
|
}
|
|
101
101
|
|
|
102
102
|
bool shouldAlwaysAllowNavigationUrl(const std::string& url) {
|
|
103
|
-
|
|
104
|
-
|
|
103
|
+
// Exact-match — prefix would let `../../evil` style paths bypass scrutiny.
|
|
104
|
+
return url == "about:blank" || url == "appres://app.internal/internal/index.html";
|
|
105
105
|
}
|
|
106
106
|
|
|
107
107
|
bool shouldAllowNavigation(const ViewState* view, const std::string& url) {
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
#import "bunite_mac_internal.h"
|
|
4
4
|
|
|
5
5
|
#include "webview_storage.h"
|
|
6
|
+
#include <objc/runtime.h>
|
|
6
7
|
|
|
7
8
|
@implementation BunitePendingPermission
|
|
8
9
|
@end
|
|
@@ -27,6 +28,125 @@
|
|
|
27
28
|
using bunite_mac::g_runtime;
|
|
28
29
|
using bunite_mac::utf8ToNSString;
|
|
29
30
|
|
|
31
|
+
// WKDownload delegate — decideDestination gates host policy + emits
|
|
32
|
+
// started/blocked, KVO on download.progress emits progress, didFinish/didFail
|
|
33
|
+
// emit terminal events.
|
|
34
|
+
@interface BuniteDownloadDelegate : NSObject <WKDownloadDelegate>
|
|
35
|
+
@property (nonatomic) uint32_t viewId;
|
|
36
|
+
@property (nonatomic) uint64_t downloadId;
|
|
37
|
+
@property (nonatomic, strong) NSString* destinationPath;
|
|
38
|
+
@property (nonatomic, weak) NSProgress* observedProgress;
|
|
39
|
+
@property (nonatomic) int64_t lastReportedBytes;
|
|
40
|
+
@property (atomic) BOOL terminalReached;
|
|
41
|
+
@end
|
|
42
|
+
|
|
43
|
+
static char kBuniteProgressCtx;
|
|
44
|
+
|
|
45
|
+
@implementation BuniteDownloadDelegate
|
|
46
|
+
|
|
47
|
+
- (void)download:(WKDownload*)download
|
|
48
|
+
decideDestinationUsingResponse:(NSURLResponse*)response
|
|
49
|
+
suggestedFilename:(NSString*)suggested
|
|
50
|
+
completionHandler:(void (^)(NSURL*))completionHandler
|
|
51
|
+
{
|
|
52
|
+
auto* st = bunite_mac::findView(self.viewId);
|
|
53
|
+
if (!st) { completionHandler(nil); return; }
|
|
54
|
+
const int32_t policy = st->download_policy.load();
|
|
55
|
+
NSString* url = download.originalRequest.URL.absoluteString ?: @"";
|
|
56
|
+
std::string url_s = url.UTF8String ?: "";
|
|
57
|
+
if (policy != 0) {
|
|
58
|
+
const char* reason = (policy == 1) ? "ask-not-implemented" : "host-policy";
|
|
59
|
+
std::string payload = "{\"kind\":\"blocked\",\"id\":\"mac-" + std::to_string(self.downloadId) +
|
|
60
|
+
"\",\"url\":\"" + bunite_mac::escapeJsonString(url_s) +
|
|
61
|
+
"\",\"reason\":\"" + reason + "\"}";
|
|
62
|
+
bunite_mac::emitWebviewEvent(self.viewId, "download-event", payload);
|
|
63
|
+
completionHandler(nil);
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
NSString* dirStr = st->download_dir.empty() ? nil : [NSString stringWithUTF8String:st->download_dir.c_str()];
|
|
67
|
+
if (!dirStr || dirStr.length == 0) {
|
|
68
|
+
NSArray<NSURL*>* paths = [NSFileManager.defaultManager URLsForDirectory:NSDownloadsDirectory inDomains:NSUserDomainMask];
|
|
69
|
+
dirStr = paths.firstObject.path ?: NSTemporaryDirectory();
|
|
70
|
+
}
|
|
71
|
+
NSString* fname = suggested.length > 0 ? suggested : @"download";
|
|
72
|
+
NSString* path = [dirStr stringByAppendingPathComponent:fname];
|
|
73
|
+
self.destinationPath = path;
|
|
74
|
+
std::string mime = (response.MIMEType ?: @"").UTF8String ?: "";
|
|
75
|
+
long long total = response.expectedContentLength > 0 ? (long long)response.expectedContentLength : 0;
|
|
76
|
+
std::string started = "{\"kind\":\"started\",\"id\":\"mac-" + std::to_string(self.downloadId) +
|
|
77
|
+
"\",\"url\":\"" + bunite_mac::escapeJsonString(url_s) +
|
|
78
|
+
"\",\"suggestedFilename\":\"" + bunite_mac::escapeJsonString(fname.UTF8String ?: "") +
|
|
79
|
+
"\",\"mimeType\":\"" + bunite_mac::escapeJsonString(mime) + "\"";
|
|
80
|
+
if (total > 0) started += ",\"sizeBytes\":" + std::to_string(total);
|
|
81
|
+
started += "}";
|
|
82
|
+
bunite_mac::emitWebviewEvent(self.viewId, "download-event", started);
|
|
83
|
+
NSProgress* prog = download.progress;
|
|
84
|
+
if (prog) {
|
|
85
|
+
self.observedProgress = prog;
|
|
86
|
+
self.lastReportedBytes = 0;
|
|
87
|
+
[prog addObserver:self
|
|
88
|
+
forKeyPath:@"completedUnitCount"
|
|
89
|
+
options:NSKeyValueObservingOptionNew
|
|
90
|
+
context:&kBuniteProgressCtx];
|
|
91
|
+
}
|
|
92
|
+
completionHandler([NSURL fileURLWithPath:path]);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
- (void)observeValueForKeyPath:(NSString*)keyPath
|
|
96
|
+
ofObject:(id)object
|
|
97
|
+
change:(NSDictionary*)change
|
|
98
|
+
context:(void*)context
|
|
99
|
+
{
|
|
100
|
+
if (context != &kBuniteProgressCtx) {
|
|
101
|
+
[super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
if (self.terminalReached) return;
|
|
105
|
+
NSProgress* prog = (NSProgress*)object;
|
|
106
|
+
int64_t received = prog.completedUnitCount;
|
|
107
|
+
int64_t total = prog.totalUnitCount;
|
|
108
|
+
if (received <= self.lastReportedBytes) return;
|
|
109
|
+
self.lastReportedBytes = received;
|
|
110
|
+
std::string payload = "{\"kind\":\"progress\",\"id\":\"mac-" + std::to_string(self.downloadId) +
|
|
111
|
+
"\",\"receivedBytes\":" + std::to_string(received);
|
|
112
|
+
if (total > 0) payload += ",\"totalBytes\":" + std::to_string(total);
|
|
113
|
+
payload += "}";
|
|
114
|
+
bunite_mac::emitWebviewEvent(self.viewId, "download-event", payload);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
- (void)_detachProgressObserver {
|
|
118
|
+
NSProgress* prog = self.observedProgress;
|
|
119
|
+
if (!prog) return;
|
|
120
|
+
@try {
|
|
121
|
+
[prog removeObserver:self forKeyPath:@"completedUnitCount" context:&kBuniteProgressCtx];
|
|
122
|
+
} @catch (NSException* ex) {} // tolerate over-remove on edge lifecycles
|
|
123
|
+
self.observedProgress = nil;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
- (void)downloadDidFinish:(WKDownload*)download {
|
|
127
|
+
self.terminalReached = YES;
|
|
128
|
+
[self _detachProgressObserver];
|
|
129
|
+
std::string dest = self.destinationPath ? std::string(self.destinationPath.UTF8String ?: "") : "";
|
|
130
|
+
std::string payload = "{\"kind\":\"completed\",\"id\":\"mac-" + std::to_string(self.downloadId) +
|
|
131
|
+
"\",\"localPath\":\"" + bunite_mac::escapeJsonString(dest) + "\"}";
|
|
132
|
+
bunite_mac::emitWebviewEvent(self.viewId, "download-event", payload);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
- (void)download:(WKDownload*)download didFailWithError:(NSError*)error resumeData:(NSData*)resumeData {
|
|
136
|
+
self.terminalReached = YES;
|
|
137
|
+
[self _detachProgressObserver];
|
|
138
|
+
std::string reason = error ? std::string(error.localizedDescription.UTF8String ?: "unknown") : "unknown";
|
|
139
|
+
std::string payload = "{\"kind\":\"failed\",\"id\":\"mac-" + std::to_string(self.downloadId) +
|
|
140
|
+
"\",\"reason\":\"" + bunite_mac::escapeJsonString(reason) + "\"}";
|
|
141
|
+
bunite_mac::emitWebviewEvent(self.viewId, "download-event", payload);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
- (void)dealloc {
|
|
145
|
+
[self _detachProgressObserver];
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
@end
|
|
149
|
+
|
|
30
150
|
@interface BuniteNavigationDelegate : NSObject <WKNavigationDelegate>
|
|
31
151
|
@end
|
|
32
152
|
|
|
@@ -49,14 +169,95 @@ decidePolicyForNavigationAction:(WKNavigationAction*)action
|
|
|
49
169
|
decisionHandler(allow ? WKNavigationActionPolicyAllow : WKNavigationActionPolicyCancel);
|
|
50
170
|
}
|
|
51
171
|
|
|
52
|
-
- (void)webView:(WKWebView*)wv
|
|
172
|
+
- (void)webView:(WKWebView*)wv didStartProvisionalNavigation:(WKNavigation*)nav {
|
|
53
173
|
(void)nav;
|
|
54
174
|
uint32_t view_id = bunite_mac::viewIdForWebView(wv);
|
|
55
175
|
NSString* url = wv.URL.absoluteString ?: @"";
|
|
176
|
+
bunite_mac::emitWebviewEvent(view_id, "load-start", url.UTF8String);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
- (void)webView:(WKWebView*)wv didCommitNavigation:(WKNavigation*)nav {
|
|
180
|
+
(void)nav;
|
|
181
|
+
uint32_t view_id = bunite_mac::viewIdForWebView(wv);
|
|
182
|
+
NSString* url = wv.URL.absoluteString ?: @"";
|
|
183
|
+
// URL commit point — surfaceEvents `navigate` arm. WKWebView fires this
|
|
184
|
+
// when the document begins loading after server response (post-redirect).
|
|
56
185
|
bunite_mac::emitWebviewEvent(view_id, "did-navigate", url.UTF8String);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
- (void)webView:(WKWebView*)wv didFinishNavigation:(WKNavigation*)nav {
|
|
189
|
+
(void)nav;
|
|
190
|
+
uint32_t view_id = bunite_mac::viewIdForWebView(wv);
|
|
191
|
+
NSString* url = wv.URL.absoluteString ?: @"";
|
|
192
|
+
bunite_mac::emitWebviewEvent(view_id, "load-finish", url.UTF8String);
|
|
57
193
|
bunite_mac::emitWebviewEvent(view_id, "dom-ready", url.UTF8String);
|
|
58
194
|
}
|
|
59
195
|
|
|
196
|
+
- (void)webView:(WKWebView*)wv didFailNavigation:(WKNavigation*)nav withError:(NSError*)error {
|
|
197
|
+
(void)nav;
|
|
198
|
+
uint32_t view_id = bunite_mac::viewIdForWebView(wv);
|
|
199
|
+
NSString* url = wv.URL.absoluteString ?: @"";
|
|
200
|
+
std::string payload = "{\"url\":\"" + bunite_mac::escapeJsonString(url.UTF8String ?: "") +
|
|
201
|
+
"\",\"reason\":\"" + bunite_mac::escapeJsonString(error.localizedDescription.UTF8String ?: "") + "\"}";
|
|
202
|
+
bunite_mac::emitWebviewEvent(view_id, "load-fail", payload);
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
- (void)webView:(WKWebView*)wv didFailProvisionalNavigation:(WKNavigation*)nav withError:(NSError*)error {
|
|
206
|
+
(void)nav;
|
|
207
|
+
uint32_t view_id = bunite_mac::viewIdForWebView(wv);
|
|
208
|
+
NSString* failingUrl = ((NSURL*)error.userInfo[NSURLErrorFailingURLErrorKey]).absoluteString
|
|
209
|
+
?: (wv.URL.absoluteString ?: @"");
|
|
210
|
+
std::string payload = "{\"url\":\"" + bunite_mac::escapeJsonString(failingUrl.UTF8String ?: "") +
|
|
211
|
+
"\",\"reason\":\"" + bunite_mac::escapeJsonString(error.localizedDescription.UTF8String ?: "") + "\"}";
|
|
212
|
+
bunite_mac::emitWebviewEvent(view_id, "load-fail", payload);
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
- (void)webView:(WKWebView*)wv navigationAction:(WKNavigationAction*)action didBecomeDownload:(WKDownload*)download {
|
|
216
|
+
static std::atomic<uint64_t> g_seq{1};
|
|
217
|
+
BuniteDownloadDelegate* d = [BuniteDownloadDelegate new];
|
|
218
|
+
d.viewId = bunite_mac::viewIdForWebView(wv);
|
|
219
|
+
d.downloadId = g_seq.fetch_add(1);
|
|
220
|
+
download.delegate = d;
|
|
221
|
+
objc_setAssociatedObject(download, "bunite.dl.delegate", d, OBJC_ASSOCIATION_RETAIN);
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
- (void)webView:(WKWebView*)wv navigationResponse:(WKNavigationResponse*)response didBecomeDownload:(WKDownload*)download {
|
|
225
|
+
static std::atomic<uint64_t> g_seq{0x80000000ULL};
|
|
226
|
+
BuniteDownloadDelegate* d = [BuniteDownloadDelegate new];
|
|
227
|
+
d.viewId = bunite_mac::viewIdForWebView(wv);
|
|
228
|
+
d.downloadId = g_seq.fetch_add(1);
|
|
229
|
+
download.delegate = d;
|
|
230
|
+
objc_setAssociatedObject(download, "bunite.dl.delegate", d, OBJC_ASSOCIATION_RETAIN);
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
@end
|
|
234
|
+
|
|
235
|
+
@interface BuniteTitleObserver : NSObject
|
|
236
|
+
+ (instancetype)shared;
|
|
237
|
+
@end
|
|
238
|
+
|
|
239
|
+
@implementation BuniteTitleObserver
|
|
240
|
+
+ (instancetype)shared {
|
|
241
|
+
static BuniteTitleObserver* o = nil;
|
|
242
|
+
static dispatch_once_t once;
|
|
243
|
+
dispatch_once(&once, ^{ o = [[BuniteTitleObserver alloc] init]; });
|
|
244
|
+
return o;
|
|
245
|
+
}
|
|
246
|
+
- (void)observeValueForKeyPath:(NSString*)keyPath
|
|
247
|
+
ofObject:(id)object
|
|
248
|
+
change:(NSDictionary<NSKeyValueChangeKey, id>*)change
|
|
249
|
+
context:(void*)context
|
|
250
|
+
{
|
|
251
|
+
(void)change; (void)context;
|
|
252
|
+
if (![keyPath isEqualToString:@"title"]) return;
|
|
253
|
+
WKWebView* wv = (WKWebView*)object;
|
|
254
|
+
uint32_t view_id = bunite_mac::viewIdForWebView(wv);
|
|
255
|
+
if (!view_id) return;
|
|
256
|
+
NSString* title = wv.title ?: @"";
|
|
257
|
+
std::string payload = "{\"title\":\"" +
|
|
258
|
+
bunite_mac::escapeJsonString(title.UTF8String ?: "") + "\"}";
|
|
259
|
+
bunite_mac::emitWebviewEvent(view_id, "title-changed", payload);
|
|
260
|
+
}
|
|
60
261
|
@end
|
|
61
262
|
|
|
62
263
|
@interface BuniteUIDelegate : NSObject <WKUIDelegate>
|
|
@@ -69,14 +270,118 @@ createWebViewWithConfiguration:(WKWebViewConfiguration*)config
|
|
|
69
270
|
forNavigationAction:(WKNavigationAction*)action
|
|
70
271
|
windowFeatures:(WKWindowFeatures*)features
|
|
71
272
|
{
|
|
72
|
-
(void)
|
|
73
|
-
uint32_t
|
|
273
|
+
(void)features;
|
|
274
|
+
uint32_t opener_view_id = bunite_mac::viewIdForWebView(wv);
|
|
74
275
|
NSString* url = action.request.URL.absoluteString ?: @"";
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
bunite_mac::
|
|
78
|
-
|
|
79
|
-
|
|
276
|
+
if (bunite_mac::g_runtime.popup_blocking) {
|
|
277
|
+
std::string payload = "{\"url\":\"" + bunite_mac::escapeJsonString(url.UTF8String ?: "") + "\"}";
|
|
278
|
+
bunite_mac::emitWebviewEvent(opener_view_id, "new-window-open", payload);
|
|
279
|
+
return nil;
|
|
280
|
+
}
|
|
281
|
+
static std::atomic<uint32_t> g_popup_seq{0x80000000u};
|
|
282
|
+
const uint32_t new_view_id = g_popup_seq.fetch_add(1);
|
|
283
|
+
WKWebView* popup = [[WKWebView alloc] initWithFrame:NSMakeRect(0, 0, 1, 1) configuration:config];
|
|
284
|
+
popup.navigationDelegate = wv.navigationDelegate;
|
|
285
|
+
popup.UIDelegate = wv.UIDelegate;
|
|
286
|
+
if (!bunite_mac::g_runtime.parked_popups) {
|
|
287
|
+
bunite_mac::g_runtime.parked_popups = [NSMutableDictionary dictionary];
|
|
288
|
+
}
|
|
289
|
+
if (!bunite_mac::g_runtime.popup_parent) {
|
|
290
|
+
bunite_mac::g_runtime.popup_parent =
|
|
291
|
+
[[NSWindow alloc] initWithContentRect:NSMakeRect(0, 0, 1, 1)
|
|
292
|
+
styleMask:NSWindowStyleMaskBorderless
|
|
293
|
+
backing:NSBackingStoreBuffered
|
|
294
|
+
defer:YES];
|
|
295
|
+
}
|
|
296
|
+
bunite_mac::g_runtime.parked_popups[@(new_view_id)] = popup;
|
|
297
|
+
[bunite_mac::g_runtime.popup_parent.contentView addSubview:popup];
|
|
298
|
+
bunite_mac::registerWebViewId(popup, new_view_id);
|
|
299
|
+
{
|
|
300
|
+
std::lock_guard<std::mutex> lock(bunite_mac::g_runtime.object_mutex);
|
|
301
|
+
auto& st = bunite_mac::g_runtime.views[new_view_id];
|
|
302
|
+
st.webview = popup;
|
|
303
|
+
st.container = nil; // bound on adoption
|
|
304
|
+
st.window_id = 0;
|
|
305
|
+
}
|
|
306
|
+
std::string payload = "{\"newSurfaceId\":" + std::to_string(new_view_id) +
|
|
307
|
+
",\"url\":\"" + bunite_mac::escapeJsonString(url.UTF8String ?: "") +
|
|
308
|
+
"\",\"disposition\":\"popup\"}";
|
|
309
|
+
bunite_mac::emitWebviewEvent(opener_view_id, "popup-requested", payload);
|
|
310
|
+
return popup;
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
// --- Dialog handlers (alert / confirm / prompt). beforeunload is not
|
|
314
|
+
// surfaced by WKUIDelegate on macOS — the cancel/proceed decision is made
|
|
315
|
+
// through the navigation-policy delegate's `decidePolicyForNavigationAction:`
|
|
316
|
+
// which we already drive via the `will-navigate` allow-list. Hence no
|
|
317
|
+
// beforeunload arm on this backend.
|
|
318
|
+
|
|
319
|
+
- (void)webView:(WKWebView*)wv
|
|
320
|
+
runJavaScriptAlertPanelWithMessage:(NSString*)message
|
|
321
|
+
initiatedByFrame:(WKFrameInfo*)frame
|
|
322
|
+
completionHandler:(void (^)(void))completionHandler
|
|
323
|
+
{
|
|
324
|
+
(void)frame;
|
|
325
|
+
uint32_t view_id = bunite_mac::viewIdForWebView(wv);
|
|
326
|
+
auto* v = bunite_mac::findView(view_id);
|
|
327
|
+
if (!v) { completionHandler(); return; }
|
|
328
|
+
uint32_t rid = v->next_dialog_request_id++;
|
|
329
|
+
v->pending_dialogs[rid] = ^(bool /*accept*/, const std::string& /*text*/) {
|
|
330
|
+
completionHandler();
|
|
331
|
+
};
|
|
332
|
+
std::string payload = "{\"requestId\":" + std::to_string(rid) +
|
|
333
|
+
",\"kind\":\"alert\",\"message\":\"" +
|
|
334
|
+
bunite_mac::escapeJsonString(message.UTF8String ?: "") + "\"}";
|
|
335
|
+
// Post emit to next runloop turn so a heavy host-side listener cannot stall
|
|
336
|
+
// the cooperative pump while the page is awaiting the completion handler.
|
|
337
|
+
dispatch_async(dispatch_get_main_queue(), ^{
|
|
338
|
+
bunite_mac::emitWebviewEvent(view_id, "dialog", payload);
|
|
339
|
+
});
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
- (void)webView:(WKWebView*)wv
|
|
343
|
+
runJavaScriptConfirmPanelWithMessage:(NSString*)message
|
|
344
|
+
initiatedByFrame:(WKFrameInfo*)frame
|
|
345
|
+
completionHandler:(void (^)(BOOL))completionHandler
|
|
346
|
+
{
|
|
347
|
+
(void)frame;
|
|
348
|
+
uint32_t view_id = bunite_mac::viewIdForWebView(wv);
|
|
349
|
+
auto* v = bunite_mac::findView(view_id);
|
|
350
|
+
if (!v) { completionHandler(NO); return; }
|
|
351
|
+
uint32_t rid = v->next_dialog_request_id++;
|
|
352
|
+
v->pending_dialogs[rid] = ^(bool accept, const std::string& /*text*/) {
|
|
353
|
+
completionHandler(accept ? YES : NO);
|
|
354
|
+
};
|
|
355
|
+
std::string payload = "{\"requestId\":" + std::to_string(rid) +
|
|
356
|
+
",\"kind\":\"confirm\",\"message\":\"" +
|
|
357
|
+
bunite_mac::escapeJsonString(message.UTF8String ?: "") + "\"}";
|
|
358
|
+
dispatch_async(dispatch_get_main_queue(), ^{
|
|
359
|
+
bunite_mac::emitWebviewEvent(view_id, "dialog", payload);
|
|
360
|
+
});
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
- (void)webView:(WKWebView*)wv
|
|
364
|
+
runJavaScriptTextInputPanelWithPrompt:(NSString*)prompt
|
|
365
|
+
defaultText:(NSString*)defaultText
|
|
366
|
+
initiatedByFrame:(WKFrameInfo*)frame
|
|
367
|
+
completionHandler:(void (^)(NSString*))completionHandler
|
|
368
|
+
{
|
|
369
|
+
(void)frame;
|
|
370
|
+
uint32_t view_id = bunite_mac::viewIdForWebView(wv);
|
|
371
|
+
auto* v = bunite_mac::findView(view_id);
|
|
372
|
+
if (!v) { completionHandler(nil); return; }
|
|
373
|
+
uint32_t rid = v->next_dialog_request_id++;
|
|
374
|
+
v->pending_dialogs[rid] = ^(bool accept, const std::string& text) {
|
|
375
|
+
completionHandler(accept ? [NSString stringWithUTF8String:text.c_str()] : nil);
|
|
376
|
+
};
|
|
377
|
+
std::string payload = "{\"requestId\":" + std::to_string(rid) +
|
|
378
|
+
",\"kind\":\"prompt\",\"message\":\"" +
|
|
379
|
+
bunite_mac::escapeJsonString(prompt.UTF8String ?: "") +
|
|
380
|
+
"\",\"defaultPrompt\":\"" +
|
|
381
|
+
bunite_mac::escapeJsonString(defaultText.UTF8String ?: "") + "\"}";
|
|
382
|
+
dispatch_async(dispatch_get_main_queue(), ^{
|
|
383
|
+
bunite_mac::emitWebviewEvent(view_id, "dialog", payload);
|
|
384
|
+
});
|
|
80
385
|
}
|
|
81
386
|
|
|
82
387
|
- (void)webView:(WKWebView*)wv
|
|
@@ -142,6 +447,14 @@ uint32_t viewIdForWebView(WKWebView* wv) {
|
|
|
142
447
|
return id ? id.unsignedIntValue : 0;
|
|
143
448
|
}
|
|
144
449
|
|
|
450
|
+
void registerWebViewId(WKWebView* wv, uint32_t view_id) {
|
|
451
|
+
[webviewIdTable() setObject:@(view_id) forKey:wv];
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
void unregisterWebViewId(WKWebView* wv) {
|
|
455
|
+
[webviewIdTable() removeObjectForKey:wv];
|
|
456
|
+
}
|
|
457
|
+
|
|
145
458
|
bool createView(uint32_t view_id, uint32_t window_id,
|
|
146
459
|
NSString* url, NSString* html, NSString* preload, NSString* appres_root,
|
|
147
460
|
NSString* navigation_rules_json, NSString* preload_origins_json,
|
|
@@ -196,6 +509,7 @@ bool createView(uint32_t view_id, uint32_t window_id,
|
|
|
196
509
|
static __strong BuniteUIDelegate* uiDelegate = [[BuniteUIDelegate alloc] init];
|
|
197
510
|
wv.navigationDelegate = navDelegate;
|
|
198
511
|
wv.UIDelegate = uiDelegate;
|
|
512
|
+
[wv addObserver:[BuniteTitleObserver shared] forKeyPath:@"title" options:NSKeyValueObservingOptionNew context:NULL];
|
|
199
513
|
|
|
200
514
|
[container addSubview:wv];
|
|
201
515
|
[window_state->window.contentView addSubview:container];
|
|
@@ -226,6 +540,50 @@ bool createView(uint32_t view_id, uint32_t window_id,
|
|
|
226
540
|
return true;
|
|
227
541
|
}
|
|
228
542
|
|
|
543
|
+
bool acceptParkedPopup(uint32_t new_view_id, uint32_t host_window_id, double x, double y, double w, double h) {
|
|
544
|
+
WKWebView* popup = nil;
|
|
545
|
+
if (g_runtime.parked_popups) {
|
|
546
|
+
popup = g_runtime.parked_popups[@(new_view_id)];
|
|
547
|
+
if (popup) [g_runtime.parked_popups removeObjectForKey:@(new_view_id)];
|
|
548
|
+
}
|
|
549
|
+
if (!popup) return false;
|
|
550
|
+
WindowState* host = findWindow(host_window_id);
|
|
551
|
+
if (!host || !host->window) return false;
|
|
552
|
+
[popup removeFromSuperview];
|
|
553
|
+
BunitePassthroughContainer* container = [[BunitePassthroughContainer alloc] init];
|
|
554
|
+
container.frame = NSMakeRect(x, y, w, h);
|
|
555
|
+
popup.frame = NSMakeRect(0, 0, w, h);
|
|
556
|
+
popup.autoresizingMask = NSViewWidthSizable | NSViewHeightSizable;
|
|
557
|
+
[container addSubview:popup];
|
|
558
|
+
[host->window.contentView addSubview:container];
|
|
559
|
+
{
|
|
560
|
+
std::lock_guard<std::mutex> lock(g_runtime.object_mutex);
|
|
561
|
+
auto& st = g_runtime.views[new_view_id];
|
|
562
|
+
st.container = container;
|
|
563
|
+
st.webview = popup;
|
|
564
|
+
st.window_id = host_window_id;
|
|
565
|
+
}
|
|
566
|
+
// Re-emit view-ready so TS BrowserView.adopt resolves — the initial
|
|
567
|
+
// did-navigate fired before the adopter registered.
|
|
568
|
+
emitWebviewEvent(new_view_id, "view-ready", "");
|
|
569
|
+
return true;
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
void dismissParkedPopup(uint32_t new_view_id) {
|
|
573
|
+
WKWebView* popup = nil;
|
|
574
|
+
if (g_runtime.parked_popups) {
|
|
575
|
+
popup = g_runtime.parked_popups[@(new_view_id)];
|
|
576
|
+
if (popup) [g_runtime.parked_popups removeObjectForKey:@(new_view_id)];
|
|
577
|
+
}
|
|
578
|
+
if (!popup) return;
|
|
579
|
+
[popup removeFromSuperview];
|
|
580
|
+
unregisterWebViewId(popup);
|
|
581
|
+
{
|
|
582
|
+
std::lock_guard<std::mutex> lock(g_runtime.object_mutex);
|
|
583
|
+
g_runtime.views.erase(new_view_id);
|
|
584
|
+
}
|
|
585
|
+
}
|
|
586
|
+
|
|
229
587
|
void removeView(uint32_t view_id) {
|
|
230
588
|
__strong WKWebView* wv = nil;
|
|
231
589
|
__strong NSView* container = nil;
|
|
@@ -272,7 +630,11 @@ void removeView(uint32_t view_id) {
|
|
|
272
630
|
for (BunitePendingPermission* p in to_deny) p.handler(WKPermissionDecisionDeny);
|
|
273
631
|
}
|
|
274
632
|
|
|
275
|
-
if (wv)
|
|
633
|
+
if (wv) {
|
|
634
|
+
@try { [wv removeObserver:[BuniteTitleObserver shared] forKeyPath:@"title"]; }
|
|
635
|
+
@catch (NSException*) { /* never registered (race) */ }
|
|
636
|
+
[webviewIdTable() removeObjectForKey:wv];
|
|
637
|
+
}
|
|
276
638
|
if (container) [container removeFromSuperview];
|
|
277
639
|
}
|
|
278
640
|
|