@crossbind/core-app-host 0.0.0-bootstrap.0 → 2.0.0-beta.65
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/README.md +55 -3
- package/android/app/src/main/AndroidManifest.xml +15 -0
- package/android/app/src/main/java/dev/crossbind/app/CrossbindActivity.java +366 -0
- package/android/app/src/main/res/xml/network_security_config.xml +8 -0
- package/android/build.gradle +14 -0
- package/android/gradle/wrapper/gradle-wrapper.jar +0 -0
- package/android/gradle/wrapper/gradle-wrapper.properties +10 -0
- package/android/gradle.properties +2 -0
- package/android/gradlew +248 -0
- package/android/gradlew.bat +112 -0
- package/android/settings.gradle +14 -0
- package/cpp/CMakeLists.txt +110 -0
- package/cpp/src/android/jni.cpp +167 -0
- package/cpp/src/app.cpp +40 -0
- package/cpp/src/app.h +21 -0
- package/cpp/src/attach.cpp +40 -0
- package/cpp/src/darwin/bundle.mm +19 -0
- package/cpp/src/darwin/channels.mm +196 -0
- package/cpp/src/darwin/navigation.mm +69 -0
- package/cpp/src/host_core.cpp +335 -0
- package/cpp/src/host_core.h +84 -0
- package/cpp/src/ios/bundle.mm +15 -0
- package/cpp/src/ios/main.mm +71 -0
- package/cpp/src/linux/app.cpp +29 -0
- package/cpp/src/linux/channels.cpp +143 -0
- package/cpp/src/linux/main_thread.h +16 -0
- package/cpp/src/linux/navigation.cpp +55 -0
- package/cpp/src/main.cpp +57 -0
- package/cpp/src/platform.h +57 -0
- package/cpp/src/windows/app.cpp +36 -0
- package/cpp/src/windows/app.manifest +8 -0
- package/cpp/src/windows/app.rc +3 -0
- package/cpp/src/windows/channels.cpp +336 -0
- package/cpp/src/windows/com.cpp +92 -0
- package/cpp/src/windows/com.h +57 -0
- package/cpp/src/windows/navigation.cpp +90 -0
- package/js/codec.js +123 -0
- package/js/host.js +8 -0
- package/js/lean-client.js +147 -0
- package/js/lean-host.js +237 -0
- package/js/page.js +79 -0
- package/js/polyfills.js +32 -0
- package/package.json +30 -6
- package/third_party/jsi/LICENSE +21 -0
- package/third_party/jsi/README.md +18 -0
- package/third_party/jsi/include/jsi/hermes-interfaces.h +189 -0
- package/third_party/jsi/include/jsi/instrumentation.h +132 -0
- package/third_party/jsi/include/jsi/jsi-inl.h +434 -0
- package/third_party/jsi/include/jsi/jsi.h +2266 -0
- package/third_party/jsi/jsi.cpp +1110 -0
- package/third_party/webview/LICENSE +22 -0
- package/third_party/webview/README.md +24 -0
- package/third_party/webview/core/include/webview/api.h +251 -0
- package/third_party/webview/core/include/webview/backends.hh +40 -0
- package/third_party/webview/core/include/webview/c_api_impl.hh +258 -0
- package/third_party/webview/core/include/webview/detail/backends/cocoa_webkit.hh +633 -0
- package/third_party/webview/core/include/webview/detail/backends/gtk_webkitgtk.hh +355 -0
- package/third_party/webview/core/include/webview/detail/backends/win32_edge.hh +915 -0
- package/third_party/webview/core/include/webview/detail/basic_result.hh +119 -0
- package/third_party/webview/core/include/webview/detail/engine_base.hh +382 -0
- package/third_party/webview/core/include/webview/detail/exceptions.hh +42 -0
- package/third_party/webview/core/include/webview/detail/json.hh +337 -0
- package/third_party/webview/core/include/webview/detail/native_library.hh +176 -0
- package/third_party/webview/core/include/webview/detail/optional.hh +118 -0
- package/third_party/webview/core/include/webview/detail/platform/darwin/cocoa/NSApplication.hh +107 -0
- package/third_party/webview/core/include/webview/detail/platform/darwin/cocoa/NSBundle.hh +56 -0
- package/third_party/webview/core/include/webview/detail/platform/darwin/cocoa/NSEvent.hh +71 -0
- package/third_party/webview/core/include/webview/detail/platform/darwin/cocoa/NSInvocation.hh +66 -0
- package/third_party/webview/core/include/webview/detail/platform/darwin/cocoa/NSMethodSignature.hh +52 -0
- package/third_party/webview/core/include/webview/detail/platform/darwin/cocoa/NSNotification.hh +51 -0
- package/third_party/webview/core/include/webview/detail/platform/darwin/cocoa/NSNumber.hh +53 -0
- package/third_party/webview/core/include/webview/detail/platform/darwin/cocoa/NSObject.hh +51 -0
- package/third_party/webview/core/include/webview/detail/platform/darwin/cocoa/NSOpenPanel.hh +71 -0
- package/third_party/webview/core/include/webview/detail/platform/darwin/cocoa/NSPoint.hh +53 -0
- package/third_party/webview/core/include/webview/detail/platform/darwin/cocoa/NSRect.hh +53 -0
- package/third_party/webview/core/include/webview/detail/platform/darwin/cocoa/NSSavePanel.hh +52 -0
- package/third_party/webview/core/include/webview/detail/platform/darwin/cocoa/NSSize.hh +53 -0
- package/third_party/webview/core/include/webview/detail/platform/darwin/cocoa/NSString.hh +87 -0
- package/third_party/webview/core/include/webview/detail/platform/darwin/cocoa/NSURL.hh +63 -0
- package/third_party/webview/core/include/webview/detail/platform/darwin/cocoa/NSURLRequest.hh +52 -0
- package/third_party/webview/core/include/webview/detail/platform/darwin/cocoa/NSValue.hh +56 -0
- package/third_party/webview/core/include/webview/detail/platform/darwin/cocoa/NSView.hh +91 -0
- package/third_party/webview/core/include/webview/detail/platform/darwin/cocoa/NSWindow.hh +137 -0
- package/third_party/webview/core/include/webview/detail/platform/darwin/cocoa/cocoa.hh +59 -0
- package/third_party/webview/core/include/webview/detail/platform/darwin/cocoa/types.hh +49 -0
- package/third_party/webview/core/include/webview/detail/platform/darwin/objc/Class.hh +55 -0
- package/third_party/webview/core/include/webview/detail/platform/darwin/objc/autoreleasepool.hh +70 -0
- package/third_party/webview/core/include/webview/detail/platform/darwin/objc/invoke.hh +75 -0
- package/third_party/webview/core/include/webview/detail/platform/darwin/objc/memory.hh +57 -0
- package/third_party/webview/core/include/webview/detail/platform/darwin/objc/objc.hh +47 -0
- package/third_party/webview/core/include/webview/detail/platform/darwin/webkit/WKOpenPanelParameters.hh +57 -0
- package/third_party/webview/core/include/webview/detail/platform/darwin/webkit/WKScriptMessage.hh +51 -0
- package/third_party/webview/core/include/webview/detail/platform/darwin/webkit/WKUserContentController.hh +61 -0
- package/third_party/webview/core/include/webview/detail/platform/darwin/webkit/WKUserScript.hh +71 -0
- package/third_party/webview/core/include/webview/detail/platform/darwin/webkit/WKWebView.hh +108 -0
- package/third_party/webview/core/include/webview/detail/platform/darwin/webkit/WKWebViewConfiguration.hh +60 -0
- package/third_party/webview/core/include/webview/detail/platform/darwin/webkit/webkit.hh +46 -0
- package/third_party/webview/core/include/webview/detail/platform/linux/gtk/compat.hh +137 -0
- package/third_party/webview/core/include/webview/detail/platform/linux/webkitgtk/compat.hh +142 -0
- package/third_party/webview/core/include/webview/detail/platform/linux/webkitgtk/dmabuf.hh +167 -0
- package/third_party/webview/core/include/webview/detail/platform/windows/com_init_wrapper.hh +126 -0
- package/third_party/webview/core/include/webview/detail/platform/windows/dpi.hh +161 -0
- package/third_party/webview/core/include/webview/detail/platform/windows/dwmapi.hh +67 -0
- package/third_party/webview/core/include/webview/detail/platform/windows/iid.hh +78 -0
- package/third_party/webview/core/include/webview/detail/platform/windows/ntdll.hh +58 -0
- package/third_party/webview/core/include/webview/detail/platform/windows/reg_key.hh +135 -0
- package/third_party/webview/core/include/webview/detail/platform/windows/shcore.hh +59 -0
- package/third_party/webview/core/include/webview/detail/platform/windows/theme.hh +76 -0
- package/third_party/webview/core/include/webview/detail/platform/windows/user32.hh +90 -0
- package/third_party/webview/core/include/webview/detail/platform/windows/version.hh +151 -0
- package/third_party/webview/core/include/webview/detail/platform/windows/webview2/loader.hh +391 -0
- package/third_party/webview/core/include/webview/detail/user_script.hh +76 -0
- package/third_party/webview/core/include/webview/detail/utility/string.hh +99 -0
- package/third_party/webview/core/include/webview/errors.h +67 -0
- package/third_party/webview/core/include/webview/errors.hh +85 -0
- package/third_party/webview/core/include/webview/json_deprecated.hh +61 -0
- package/third_party/webview/core/include/webview/macros.h +123 -0
- package/third_party/webview/core/include/webview/types.h +81 -0
- package/third_party/webview/core/include/webview/types.hh +48 -0
- package/third_party/webview/core/include/webview/version.h +67 -0
- package/third_party/webview/core/include/webview/webview.h +32 -0
- package/third_party/webview/core/include/webview.h +15 -0
- package/third_party/webview2/LICENSE +27 -0
- package/third_party/webview2/README.md +13 -0
package/cpp/src/app.cpp
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
#include "app.h"
|
|
2
|
+
|
|
3
|
+
#include <cstdio>
|
|
4
|
+
#include <cstdlib>
|
|
5
|
+
|
|
6
|
+
namespace crossbind::app {
|
|
7
|
+
|
|
8
|
+
const char *devUrl() {
|
|
9
|
+
#ifdef CROSSBIND_APP_DEV
|
|
10
|
+
return std::getenv("CROSSBIND_APP_URL");
|
|
11
|
+
#else
|
|
12
|
+
// A release build loads its own page, whatever starts it.
|
|
13
|
+
return nullptr;
|
|
14
|
+
#endif
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
Core &start(const char *devUrl) {
|
|
18
|
+
const std::string resources = resourceDir();
|
|
19
|
+
Core &core = *new Core(resources + "/page");
|
|
20
|
+
core.cors = devUrl != nullptr;
|
|
21
|
+
std::string bootError;
|
|
22
|
+
try {
|
|
23
|
+
bootError = core.boot(readFile(resources + "/host.hbc"), "host.hbc", resources + "/data");
|
|
24
|
+
} catch (const std::exception &error) {
|
|
25
|
+
bootError = error.what();
|
|
26
|
+
}
|
|
27
|
+
if (!bootError.empty()) {
|
|
28
|
+
std::fprintf(stderr, "crossbind: the app's module failed to start: %s\n", bootError.c_str());
|
|
29
|
+
quit(core, 1);
|
|
30
|
+
}
|
|
31
|
+
return core;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
void quit(Core &core, int status) {
|
|
35
|
+
core.stop();
|
|
36
|
+
std::fflush(nullptr);
|
|
37
|
+
std::_Exit(status);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
} // namespace crossbind::app
|
package/cpp/src/app.h
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
#pragma once
|
|
2
|
+
|
|
3
|
+
// What an app does around its webview, whatever makes its window: the module starts from the app's files, and the
|
|
4
|
+
// webview gets the page's token, the module's channel, the page's console and the navigation lock.
|
|
5
|
+
#include "host_core.h"
|
|
6
|
+
|
|
7
|
+
namespace crossbind::app {
|
|
8
|
+
|
|
9
|
+
// The page a debug app opens instead of its own: the dev server's, which CROSSBIND_APP_URL names. Null in a release app.
|
|
10
|
+
const char *devUrl();
|
|
11
|
+
// Starts the module from the app's files, or ends the app with the reason.
|
|
12
|
+
Core &start(const char *devUrl);
|
|
13
|
+
// Ends the app without running destructors: bindings keep JSI values in globals, which must not be destroyed after the
|
|
14
|
+
// runtime they belong to.
|
|
15
|
+
[[noreturn]] void quit(Core &core, int status);
|
|
16
|
+
// Readies a webview for the page, and returns the URL it opens first. An Android app's activity does this in Java.
|
|
17
|
+
std::string attach(Core &core, void *webView, const char *devUrl);
|
|
18
|
+
// The scheme and authority of a URL: the origin a webview names a page by.
|
|
19
|
+
std::string originOf(const std::string &url);
|
|
20
|
+
|
|
21
|
+
} // namespace crossbind::app
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
#include "app.h"
|
|
2
|
+
|
|
3
|
+
#include <algorithm>
|
|
4
|
+
#include <cstdlib>
|
|
5
|
+
|
|
6
|
+
namespace crossbind::app {
|
|
7
|
+
|
|
8
|
+
namespace {
|
|
9
|
+
|
|
10
|
+
constexpr const char *APP_ORIGIN = "crossbind://app";
|
|
11
|
+
constexpr const char *APP_URL = "crossbind://app/";
|
|
12
|
+
constexpr const char *REFUSED = "[1,{\"name\":\"Error\",\"message\":\"crossbind: a page from this origin may not call the app's module\"}]";
|
|
13
|
+
|
|
14
|
+
} // namespace
|
|
15
|
+
|
|
16
|
+
std::string originOf(const std::string &url) {
|
|
17
|
+
const auto scheme = url.find("://");
|
|
18
|
+
if (scheme == std::string::npos) return url;
|
|
19
|
+
return url.substr(0, url.find_first_of("/?#", scheme + 3));
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
std::string attach(Core &core, void *webView, const char *devUrl) {
|
|
23
|
+
std::vector<std::string> origins{APP_ORIGIN};
|
|
24
|
+
if (devUrl) origins.push_back(originOf(devUrl));
|
|
25
|
+
// Only the top frame gets the token every message and crossbind:// call carries: a frame of another origin, or a
|
|
26
|
+
// sandboxed one, cannot read it.
|
|
27
|
+
addTopFrameScript(webView, "Object.defineProperty(globalThis, '__crossbind_token', { value: '" + core.token() + "' });");
|
|
28
|
+
installReplyChannel(webView, "crossbind", [&core, origins](std::string origin, std::string message, ReplyFn reply) {
|
|
29
|
+
if (!origin.empty() && std::find(origins.begin(), origins.end(), origin) == origins.end()) {
|
|
30
|
+
reply(REFUSED);
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
core.receive(std::move(message), std::move(reply));
|
|
34
|
+
});
|
|
35
|
+
if (devUrl || std::getenv("CROSSBIND_APP_CONSOLE")) forwardConsole(webView);
|
|
36
|
+
restrictNavigation(webView, origins, [&core] { core.reset(); });
|
|
37
|
+
return devUrl ? devUrl : APP_URL;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
} // namespace crossbind::app
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
#import <Cocoa/Cocoa.h>
|
|
2
|
+
|
|
3
|
+
#include "platform.h"
|
|
4
|
+
|
|
5
|
+
std::string resourceDir() { return NSBundle.mainBundle.resourcePath.UTF8String; }
|
|
6
|
+
|
|
7
|
+
std::string appName() {
|
|
8
|
+
NSString *name = [NSBundle.mainBundle objectForInfoDictionaryKey:@"CFBundleName"] ?: NSProcessInfo.processInfo.processName;
|
|
9
|
+
return name.UTF8String;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
void onQuit(std::function<void()> quit) {
|
|
13
|
+
[NSNotificationCenter.defaultCenter addObserverForName:NSApplicationWillTerminateNotification
|
|
14
|
+
object:nil
|
|
15
|
+
queue:nil
|
|
16
|
+
usingBlock:^(NSNotification *) {
|
|
17
|
+
quit();
|
|
18
|
+
}];
|
|
19
|
+
}
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
#import <WebKit/WebKit.h>
|
|
2
|
+
|
|
3
|
+
#include "platform.h"
|
|
4
|
+
|
|
5
|
+
#include <cstdio>
|
|
6
|
+
#include <memory>
|
|
7
|
+
|
|
8
|
+
namespace {
|
|
9
|
+
|
|
10
|
+
// Wraps the page's console and reports uncaught errors, in the main frame alone.
|
|
11
|
+
NSString *const CONSOLE_SCRIPT = @"(() => {"
|
|
12
|
+
"const text = (args) => args.map((arg) => {"
|
|
13
|
+
" if (arg instanceof Error) return `${arg.message}\\n${arg.stack}`;"
|
|
14
|
+
" if (typeof arg === 'string') return arg;"
|
|
15
|
+
" try { return JSON.stringify(arg); } catch { return String(arg); }"
|
|
16
|
+
"}).join(' ');"
|
|
17
|
+
"const post = (level, args) => window.webkit.messageHandlers.crossbind_console.postMessage([level, text(args)]);"
|
|
18
|
+
"for (const level of ['log', 'info', 'warn', 'error', 'debug']) {"
|
|
19
|
+
" const original = console[level].bind(console);"
|
|
20
|
+
" console[level] = (...args) => { post(level, args); original(...args); };"
|
|
21
|
+
"}"
|
|
22
|
+
"addEventListener('error', (event) => post('error', [event.error ?? event.message]));"
|
|
23
|
+
"addEventListener('unhandledrejection', (event) => post('error', [event.reason]));"
|
|
24
|
+
"})();";
|
|
25
|
+
|
|
26
|
+
void onMainThread(void (^block)(void)) {
|
|
27
|
+
if ([NSThread isMainThread]) {
|
|
28
|
+
block();
|
|
29
|
+
} else {
|
|
30
|
+
dispatch_async(dispatch_get_main_queue(), block);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
} // namespace
|
|
35
|
+
|
|
36
|
+
@interface CBReplyChannel : NSObject <WKScriptMessageHandlerWithReply>
|
|
37
|
+
- (instancetype)initWithHandler:(MessageHandler)handler;
|
|
38
|
+
@end
|
|
39
|
+
|
|
40
|
+
@implementation CBReplyChannel {
|
|
41
|
+
MessageHandler _handler;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
- (instancetype)initWithHandler:(MessageHandler)handler {
|
|
45
|
+
if ((self = [super init])) _handler = std::move(handler);
|
|
46
|
+
return self;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
- (void)userContentController:(WKUserContentController *)controller
|
|
50
|
+
didReceiveScriptMessage:(WKScriptMessage *)message
|
|
51
|
+
replyHandler:(void (^)(id, NSString *))replyHandler {
|
|
52
|
+
NSString *body = [message.body isKindOfClass:[NSString class]] ? message.body : @"";
|
|
53
|
+
WKSecurityOrigin *sender = message.frameInfo.securityOrigin;
|
|
54
|
+
NSString *origin = [NSString stringWithFormat:@"%@://%@%@", sender.protocol, sender.host,
|
|
55
|
+
sender.port ? [NSString stringWithFormat:@":%ld", static_cast<long>(sender.port)] : @""];
|
|
56
|
+
void (^reply)(id, NSString *) = [replyHandler copy];
|
|
57
|
+
_handler(std::string(origin.UTF8String ?: ""), std::string(body.UTF8String ?: ""), [reply](const std::string &text) {
|
|
58
|
+
// The host answers from its JS thread, which has no autorelease pool of its own.
|
|
59
|
+
@autoreleasepool {
|
|
60
|
+
NSString *answer = [[NSString alloc] initWithBytes:text.data() length:text.size() encoding:NSUTF8StringEncoding];
|
|
61
|
+
onMainThread(^{
|
|
62
|
+
reply(answer, nil);
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
@end
|
|
69
|
+
|
|
70
|
+
@interface CBConsole : NSObject <WKScriptMessageHandler>
|
|
71
|
+
@end
|
|
72
|
+
|
|
73
|
+
@implementation CBConsole
|
|
74
|
+
|
|
75
|
+
- (void)userContentController:(WKUserContentController *)controller didReceiveScriptMessage:(WKScriptMessage *)message {
|
|
76
|
+
NSArray *entry = [message.body isKindOfClass:[NSArray class]] ? message.body : nil;
|
|
77
|
+
if (entry.count != 2) return;
|
|
78
|
+
NSString *level = [entry[0] description];
|
|
79
|
+
std::FILE *out = [level isEqualToString:@"warn"] || [level isEqualToString:@"error"] ? stderr : stdout;
|
|
80
|
+
std::fprintf(out, "%s\n", [entry[1] description].UTF8String);
|
|
81
|
+
std::fflush(out);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
@end
|
|
85
|
+
|
|
86
|
+
@interface CBSchemeHandler : NSObject <WKURLSchemeHandler>
|
|
87
|
+
- (instancetype)initWithHandler:(SchemeHandler)handler;
|
|
88
|
+
@end
|
|
89
|
+
|
|
90
|
+
@implementation CBSchemeHandler {
|
|
91
|
+
SchemeHandler _handler;
|
|
92
|
+
NSMutableSet *_stopped;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
- (instancetype)initWithHandler:(SchemeHandler)handler {
|
|
96
|
+
if ((self = [super init])) {
|
|
97
|
+
_handler = std::move(handler);
|
|
98
|
+
_stopped = [NSMutableSet set];
|
|
99
|
+
}
|
|
100
|
+
return self;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
static std::vector<uint8_t> readBody(NSURLRequest *request) {
|
|
104
|
+
std::vector<uint8_t> body;
|
|
105
|
+
if (NSData *data = request.HTTPBody) {
|
|
106
|
+
body.assign(static_cast<const uint8_t *>(data.bytes), static_cast<const uint8_t *>(data.bytes) + data.length);
|
|
107
|
+
return body;
|
|
108
|
+
}
|
|
109
|
+
NSInputStream *stream = request.HTTPBodyStream;
|
|
110
|
+
if (!stream) return body;
|
|
111
|
+
[stream open];
|
|
112
|
+
uint8_t chunk[65536];
|
|
113
|
+
for (NSInteger read; (read = [stream read:chunk maxLength:sizeof chunk]) > 0;) {
|
|
114
|
+
body.insert(body.end(), chunk, chunk + read);
|
|
115
|
+
}
|
|
116
|
+
[stream close];
|
|
117
|
+
return body;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
- (void)webView:(WKWebView *)webView startURLSchemeTask:(id<WKURLSchemeTask>)task {
|
|
121
|
+
NSURLRequest *request = task.request;
|
|
122
|
+
SchemeRequest incoming;
|
|
123
|
+
incoming.method = request.HTTPMethod.UTF8String ?: "GET";
|
|
124
|
+
incoming.path = request.URL.path.UTF8String ?: "";
|
|
125
|
+
incoming.query = request.URL.query.UTF8String ?: "";
|
|
126
|
+
incoming.body = readBody(request);
|
|
127
|
+
|
|
128
|
+
id<WKURLSchemeTask> held = task;
|
|
129
|
+
NSURL *url = request.URL;
|
|
130
|
+
NSMutableSet *stopped = _stopped;
|
|
131
|
+
_handler(std::move(incoming), [held, url, stopped](SchemeResponse response) {
|
|
132
|
+
// A call's answer comes from the host's JS thread, which has no autorelease pool of its own.
|
|
133
|
+
@autoreleasepool {
|
|
134
|
+
auto *bytes = new std::vector<uint8_t>(std::move(response.body));
|
|
135
|
+
NSData *data = [[NSData alloc] initWithBytesNoCopy:bytes->data()
|
|
136
|
+
length:bytes->size()
|
|
137
|
+
deallocator:^(void *, NSUInteger) { delete bytes; }];
|
|
138
|
+
NSMutableDictionary *headers = [@{
|
|
139
|
+
@"Content-Type": [NSString stringWithUTF8String:response.mime.c_str()],
|
|
140
|
+
@"Content-Length": [NSString stringWithFormat:@"%lu", static_cast<unsigned long>(data.length)],
|
|
141
|
+
} mutableCopy];
|
|
142
|
+
for (const auto &[name, value] : response.headers) {
|
|
143
|
+
headers[[NSString stringWithUTF8String:name.c_str()]] = [NSString stringWithUTF8String:value.c_str()];
|
|
144
|
+
}
|
|
145
|
+
NSHTTPURLResponse *http = [[NSHTTPURLResponse alloc] initWithURL:url
|
|
146
|
+
statusCode:response.status
|
|
147
|
+
HTTPVersion:@"HTTP/1.1"
|
|
148
|
+
headerFields:headers];
|
|
149
|
+
onMainThread(^{
|
|
150
|
+
// The page may have dropped the request meanwhile; WebKit refuses answers to a stopped task.
|
|
151
|
+
if ([stopped containsObject:held]) {
|
|
152
|
+
[stopped removeObject:held];
|
|
153
|
+
return;
|
|
154
|
+
}
|
|
155
|
+
[held didReceiveResponse:http];
|
|
156
|
+
[held didReceiveData:data];
|
|
157
|
+
[held didFinish];
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
- (void)webView:(WKWebView *)webView stopURLSchemeTask:(id<WKURLSchemeTask>)task {
|
|
164
|
+
[_stopped addObject:task];
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
@end
|
|
168
|
+
|
|
169
|
+
void installReplyChannel(void *webView, const char *name, MessageHandler handler) {
|
|
170
|
+
WKWebView *view = (__bridge WKWebView *)webView;
|
|
171
|
+
CBReplyChannel *channel = [[CBReplyChannel alloc] initWithHandler:std::move(handler)];
|
|
172
|
+
[view.configuration.userContentController addScriptMessageHandlerWithReply:channel
|
|
173
|
+
contentWorld:WKContentWorld.pageWorld
|
|
174
|
+
name:[NSString stringWithUTF8String:name]];
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
void forwardConsole(void *webView) {
|
|
178
|
+
WKUserContentController *controller = ((__bridge WKWebView *)webView).configuration.userContentController;
|
|
179
|
+
[controller addScriptMessageHandler:[CBConsole new] name:@"crossbind_console"];
|
|
180
|
+
[controller addUserScript:[[WKUserScript alloc] initWithSource:CONSOLE_SCRIPT
|
|
181
|
+
injectionTime:WKUserScriptInjectionTimeAtDocumentStart
|
|
182
|
+
forMainFrameOnly:YES]];
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
void addTopFrameScript(void *webView, const std::string &source) {
|
|
186
|
+
WKUserContentController *controller = ((__bridge WKWebView *)webView).configuration.userContentController;
|
|
187
|
+
[controller addUserScript:[[WKUserScript alloc] initWithSource:[NSString stringWithUTF8String:source.c_str()]
|
|
188
|
+
injectionTime:WKUserScriptInjectionTimeAtDocumentStart
|
|
189
|
+
forMainFrameOnly:YES]];
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
void registerSchemeHandler(void *target, const char *scheme, SchemeHandler handler) {
|
|
193
|
+
WKWebViewConfiguration *config = (__bridge WKWebViewConfiguration *)target;
|
|
194
|
+
CBSchemeHandler *schemeHandler = [[CBSchemeHandler alloc] initWithHandler:std::move(handler)];
|
|
195
|
+
[config setURLSchemeHandler:schemeHandler forURLScheme:[NSString stringWithUTF8String:scheme]];
|
|
196
|
+
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
#import <TargetConditionals.h>
|
|
2
|
+
#if TARGET_OS_IPHONE
|
|
3
|
+
#import <UIKit/UIKit.h>
|
|
4
|
+
#else
|
|
5
|
+
#import <Cocoa/Cocoa.h>
|
|
6
|
+
#endif
|
|
7
|
+
#import <WebKit/WebKit.h>
|
|
8
|
+
#import <objc/runtime.h>
|
|
9
|
+
|
|
10
|
+
#include "platform.h"
|
|
11
|
+
|
|
12
|
+
#include <algorithm>
|
|
13
|
+
|
|
14
|
+
// A page from another origin in any frame would reach the module's channel and its crossbind:// routes, so the webview
|
|
15
|
+
// never loads one. A link the user follows to such a page opens in the default browser instead.
|
|
16
|
+
@interface CBNavigationLock : NSObject <WKNavigationDelegate> {
|
|
17
|
+
@public
|
|
18
|
+
std::vector<std::string> origins;
|
|
19
|
+
std::function<void()> newPage;
|
|
20
|
+
}
|
|
21
|
+
@end
|
|
22
|
+
|
|
23
|
+
@implementation CBNavigationLock
|
|
24
|
+
|
|
25
|
+
- (BOOL)allows:(NSURL *)url {
|
|
26
|
+
NSString *scheme = url.scheme.lowercaseString;
|
|
27
|
+
if ([scheme isEqualToString:@"about"]) return YES;
|
|
28
|
+
// A blob URL carries the origin of the page that made it.
|
|
29
|
+
if ([scheme isEqualToString:@"blob"]) return [self allows:[NSURL URLWithString:url.resourceSpecifier]];
|
|
30
|
+
std::string origin = std::string(scheme.UTF8String ?: "") + "://" + (url.host.UTF8String ?: "");
|
|
31
|
+
if (url.port) origin += ":" + std::to_string(url.port.intValue);
|
|
32
|
+
return std::find(origins.begin(), origins.end(), origin) != origins.end();
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
- (void)webView:(WKWebView *)webView
|
|
36
|
+
decidePolicyForNavigationAction:(WKNavigationAction *)action
|
|
37
|
+
decisionHandler:(void (^)(WKNavigationActionPolicy))decide {
|
|
38
|
+
NSURL *url = action.request.URL;
|
|
39
|
+
if ([self allows:url]) {
|
|
40
|
+
decide(WKNavigationActionPolicyAllow);
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
const BOOL followed = action.navigationType == WKNavigationTypeLinkActivated || action.targetFrame == nil;
|
|
44
|
+
if (followed && [@[ @"http", @"https", @"mailto" ] containsObject:url.scheme.lowercaseString]) {
|
|
45
|
+
#if TARGET_OS_IPHONE
|
|
46
|
+
[UIApplication.sharedApplication openURL:url options:@{} completionHandler:nil];
|
|
47
|
+
#else
|
|
48
|
+
[NSWorkspace.sharedWorkspace openURL:url];
|
|
49
|
+
#endif
|
|
50
|
+
}
|
|
51
|
+
decide(WKNavigationActionPolicyCancel);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
- (void)webView:(WKWebView *)webView didCommitNavigation:(WKNavigation *)navigation {
|
|
55
|
+
newPage();
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
@end
|
|
59
|
+
|
|
60
|
+
void restrictNavigation(void *webView, std::vector<std::string> origins, std::function<void()> newPage) {
|
|
61
|
+
static char key;
|
|
62
|
+
WKWebView *view = (__bridge WKWebView *)webView;
|
|
63
|
+
CBNavigationLock *lock = [CBNavigationLock new];
|
|
64
|
+
lock->origins = std::move(origins);
|
|
65
|
+
lock->newPage = std::move(newPage);
|
|
66
|
+
// The webview holds its navigation delegate weakly.
|
|
67
|
+
objc_setAssociatedObject(view, &key, lock, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
|
|
68
|
+
view.navigationDelegate = lock;
|
|
69
|
+
}
|