@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
|
@@ -0,0 +1,335 @@
|
|
|
1
|
+
#include "host_core.h"
|
|
2
|
+
|
|
3
|
+
#include <emscripten/bind.h>
|
|
4
|
+
|
|
5
|
+
#include <array>
|
|
6
|
+
#include <cstdio>
|
|
7
|
+
#include <filesystem>
|
|
8
|
+
#include <fstream>
|
|
9
|
+
#include <future>
|
|
10
|
+
#include <random>
|
|
11
|
+
#include <sstream>
|
|
12
|
+
|
|
13
|
+
namespace crossbind::app {
|
|
14
|
+
|
|
15
|
+
namespace {
|
|
16
|
+
|
|
17
|
+
class VectorBuffer : public jsi::MutableBuffer {
|
|
18
|
+
public:
|
|
19
|
+
explicit VectorBuffer(std::vector<uint8_t> bytes) : bytes_(std::move(bytes)) {}
|
|
20
|
+
size_t size() const override { return bytes_.size(); }
|
|
21
|
+
uint8_t *data() override { return bytes_.data(); }
|
|
22
|
+
|
|
23
|
+
private:
|
|
24
|
+
std::vector<uint8_t> bytes_;
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
constexpr char B64[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
|
|
28
|
+
|
|
29
|
+
std::string toBase64(const uint8_t *bytes, size_t length) {
|
|
30
|
+
std::string out;
|
|
31
|
+
out.reserve((length + 2) / 3 * 4);
|
|
32
|
+
size_t i = 0;
|
|
33
|
+
for (; i + 2 < length; i += 3) {
|
|
34
|
+
const uint32_t n = (bytes[i] << 16) | (bytes[i + 1] << 8) | bytes[i + 2];
|
|
35
|
+
out += B64[n >> 18];
|
|
36
|
+
out += B64[(n >> 12) & 63];
|
|
37
|
+
out += B64[(n >> 6) & 63];
|
|
38
|
+
out += B64[n & 63];
|
|
39
|
+
}
|
|
40
|
+
if (i < length) {
|
|
41
|
+
const uint32_t n = (bytes[i] << 16) | ((i + 1 < length ? bytes[i + 1] : 0) << 8);
|
|
42
|
+
out += B64[n >> 18];
|
|
43
|
+
out += B64[(n >> 12) & 63];
|
|
44
|
+
out += i + 1 < length ? B64[(n >> 6) & 63] : '=';
|
|
45
|
+
out += '=';
|
|
46
|
+
}
|
|
47
|
+
return out;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
std::vector<uint8_t> fromBase64(const std::string &text) {
|
|
51
|
+
static const auto index = [] {
|
|
52
|
+
std::array<uint8_t, 256> table{};
|
|
53
|
+
for (uint8_t i = 0; i < 64; i += 1) table[static_cast<uint8_t>(B64[i])] = i;
|
|
54
|
+
return table;
|
|
55
|
+
}();
|
|
56
|
+
const size_t padding = text.size() >= 2 && text[text.size() - 1] == '=' ? (text[text.size() - 2] == '=' ? 2 : 1) : 0;
|
|
57
|
+
std::vector<uint8_t> bytes(text.size() / 4 * 3 - padding);
|
|
58
|
+
size_t o = 0;
|
|
59
|
+
for (size_t i = 0; i + 3 < text.size(); i += 4) {
|
|
60
|
+
const uint32_t n = (index[static_cast<uint8_t>(text[i])] << 18) | (index[static_cast<uint8_t>(text[i + 1])] << 12)
|
|
61
|
+
| (index[static_cast<uint8_t>(text[i + 2])] << 6) | index[static_cast<uint8_t>(text[i + 3])];
|
|
62
|
+
if (o < bytes.size()) bytes[o++] = n >> 16;
|
|
63
|
+
if (o < bytes.size()) bytes[o++] = (n >> 8) & 255;
|
|
64
|
+
if (o < bytes.size()) bytes[o++] = n & 255;
|
|
65
|
+
}
|
|
66
|
+
return bytes;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
std::vector<uint8_t> bytesOf(const std::string &text) { return std::vector<uint8_t>(text.begin(), text.end()); }
|
|
70
|
+
|
|
71
|
+
// 128 random bits, as hex.
|
|
72
|
+
std::string randomToken() {
|
|
73
|
+
std::random_device random;
|
|
74
|
+
char token[33];
|
|
75
|
+
std::snprintf(token, sizeof token, "%08x%08x%08x%08x", random(), random(), random(), random());
|
|
76
|
+
return token;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// A reply the page decodes as a failed call.
|
|
80
|
+
std::string errorReply(const std::string &message) {
|
|
81
|
+
std::string escaped;
|
|
82
|
+
for (const char c : message) {
|
|
83
|
+
if (static_cast<unsigned char>(c) < 0x20) {
|
|
84
|
+
escaped += ' ';
|
|
85
|
+
continue;
|
|
86
|
+
}
|
|
87
|
+
if (c == '"' || c == '\\') escaped += '\\';
|
|
88
|
+
escaped += c;
|
|
89
|
+
}
|
|
90
|
+
return "[1,{\"name\":\"Error\",\"message\":\"" + escaped + "\"}]";
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
void setFunction(jsi::Runtime &rt, const char *name, unsigned params, jsi::HostFunctionType fn) {
|
|
94
|
+
rt.global().setProperty(rt, name, jsi::Function::createFromHostFunction(rt, jsi::PropNameID::forAscii(rt, name), params, std::move(fn)));
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// The page's requests for bytes, each answered by a function of the host bundle: a message with its attachments in the
|
|
98
|
+
// request body, and a large result read after its reply.
|
|
99
|
+
const std::map<std::string, std::string> ROUTES{
|
|
100
|
+
{"/b/frame", "__crossbind_frame"},
|
|
101
|
+
{"/b/result", "__crossbind_result"},
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
const std::map<std::string, std::string> MIME_TYPES{
|
|
105
|
+
{"html", "text/html; charset=utf-8"},
|
|
106
|
+
{"js", "text/javascript; charset=utf-8"},
|
|
107
|
+
{"mjs", "text/javascript; charset=utf-8"},
|
|
108
|
+
{"css", "text/css; charset=utf-8"},
|
|
109
|
+
{"json", "application/json"},
|
|
110
|
+
{"map", "application/json"},
|
|
111
|
+
{"txt", "text/plain; charset=utf-8"},
|
|
112
|
+
{"svg", "image/svg+xml"},
|
|
113
|
+
{"png", "image/png"},
|
|
114
|
+
{"jpg", "image/jpeg"},
|
|
115
|
+
{"jpeg", "image/jpeg"},
|
|
116
|
+
{"gif", "image/gif"},
|
|
117
|
+
{"webp", "image/webp"},
|
|
118
|
+
{"ico", "image/x-icon"},
|
|
119
|
+
{"wasm", "application/wasm"},
|
|
120
|
+
{"woff", "font/woff"},
|
|
121
|
+
{"woff2", "font/woff2"},
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
SchemeResponse binaryCall(jsi::Runtime &rt, const std::string &function, SchemeRequest &request) {
|
|
125
|
+
jsi::ArrayBuffer body(rt, std::make_shared<VectorBuffer>(std::move(request.body)));
|
|
126
|
+
auto reply = rt.global().getPropertyAsFunction(rt, function.c_str()).call(rt, jsi::String::createFromUtf8(rt, request.query), std::move(body));
|
|
127
|
+
if (reply.isString()) return SchemeResponse{200, "application/json", bytesOf(reply.asString(rt).utf8(rt))};
|
|
128
|
+
auto view = reply.asObject(rt);
|
|
129
|
+
auto buffer = view.getProperty(rt, "buffer").asObject(rt).getArrayBuffer(rt);
|
|
130
|
+
const auto offset = static_cast<size_t>(view.getProperty(rt, "byteOffset").asNumber());
|
|
131
|
+
const auto length = static_cast<size_t>(view.getProperty(rt, "byteLength").asNumber());
|
|
132
|
+
const uint8_t *data = buffer.data(rt) + offset;
|
|
133
|
+
return SchemeResponse{200, "application/octet-stream", std::vector<uint8_t>(data, data + length)};
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// A file of the page: a directory names its index.html, and no path leaves the page's directory.
|
|
137
|
+
SchemeResponse pageFile(const std::string &pageDir, const std::string &path) {
|
|
138
|
+
if (path.find("..") != std::string::npos) return SchemeResponse{404, "text/plain", {}};
|
|
139
|
+
std::filesystem::path file = pageDir + path;
|
|
140
|
+
std::error_code error;
|
|
141
|
+
if (std::filesystem::is_directory(file, error)) file /= "index.html";
|
|
142
|
+
// A device, such as Windows' CON, or a pipe would hold the read up.
|
|
143
|
+
if (!std::filesystem::is_regular_file(file, error)) return SchemeResponse{404, "text/plain", {}};
|
|
144
|
+
std::ifstream stream(file, std::ios::binary);
|
|
145
|
+
if (!stream) return SchemeResponse{404, "text/plain", {}};
|
|
146
|
+
std::ostringstream content;
|
|
147
|
+
content << stream.rdbuf();
|
|
148
|
+
const std::string extension = file.extension().string();
|
|
149
|
+
const auto mime = extension.empty() ? MIME_TYPES.end() : MIME_TYPES.find(extension.substr(1));
|
|
150
|
+
return SchemeResponse{200, mime == MIME_TYPES.end() ? "application/octet-stream" : mime->second, bytesOf(content.str())};
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
} // namespace
|
|
154
|
+
|
|
155
|
+
std::string readFile(const std::string &path) {
|
|
156
|
+
std::ifstream file(path, std::ios::binary);
|
|
157
|
+
if (!file) throw std::runtime_error("cannot read " + path);
|
|
158
|
+
std::ostringstream text;
|
|
159
|
+
text << file.rdbuf();
|
|
160
|
+
return text.str();
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
void JsThread::start(Task boot) {
|
|
164
|
+
thread_ = std::thread([this, boot = std::move(boot)] { loop(boot); });
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
void JsThread::post(Task task) {
|
|
168
|
+
{
|
|
169
|
+
std::lock_guard<std::mutex> lock(mutex_);
|
|
170
|
+
tasks_.push_back(std::move(task));
|
|
171
|
+
}
|
|
172
|
+
wake_.notify_one();
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
void JsThread::postAfter(double ms, Task task) {
|
|
176
|
+
{
|
|
177
|
+
std::lock_guard<std::mutex> lock(mutex_);
|
|
178
|
+
timers_.emplace(Clock::now() + std::chrono::microseconds(static_cast<long long>(ms * 1000)), std::move(task));
|
|
179
|
+
}
|
|
180
|
+
wake_.notify_one();
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
void JsThread::stop() {
|
|
184
|
+
{
|
|
185
|
+
std::lock_guard<std::mutex> lock(mutex_);
|
|
186
|
+
stopping_ = true;
|
|
187
|
+
}
|
|
188
|
+
wake_.notify_one();
|
|
189
|
+
if (thread_.joinable()) thread_.join();
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
JsThread::Task JsThread::next() {
|
|
193
|
+
std::unique_lock<std::mutex> lock(mutex_);
|
|
194
|
+
for (;;) {
|
|
195
|
+
if (!tasks_.empty()) {
|
|
196
|
+
Task task = std::move(tasks_.front());
|
|
197
|
+
tasks_.pop_front();
|
|
198
|
+
return task;
|
|
199
|
+
}
|
|
200
|
+
if (!timers_.empty() && timers_.begin()->first <= Clock::now()) {
|
|
201
|
+
Task task = std::move(timers_.begin()->second);
|
|
202
|
+
timers_.erase(timers_.begin());
|
|
203
|
+
return task;
|
|
204
|
+
}
|
|
205
|
+
if (stopping_) return nullptr;
|
|
206
|
+
if (timers_.empty()) {
|
|
207
|
+
wake_.wait(lock);
|
|
208
|
+
} else {
|
|
209
|
+
wake_.wait_until(lock, timers_.begin()->first);
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
void JsThread::loop(const Task &boot) {
|
|
215
|
+
auto config = ::hermes::vm::RuntimeConfig::Builder().withMicrotaskQueue(true).withES6BlockScoping(true).build();
|
|
216
|
+
runtime_ = facebook::hermes::makeHermesRuntime(config);
|
|
217
|
+
run(boot);
|
|
218
|
+
while (Task task = next()) run(task);
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
void JsThread::run(const Task &task) {
|
|
222
|
+
try {
|
|
223
|
+
task(*runtime_);
|
|
224
|
+
while (!runtime_->drainMicrotasks()) {
|
|
225
|
+
}
|
|
226
|
+
} catch (const jsi::JSError &error) {
|
|
227
|
+
std::fprintf(stderr, "crossbind: %s\n%s\n", error.getMessage().c_str(), error.getStack().c_str());
|
|
228
|
+
} catch (const std::exception &error) {
|
|
229
|
+
std::fprintf(stderr, "crossbind: %s\n", error.what());
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
void Core::installHostFunctions(jsi::Runtime &rt) {
|
|
234
|
+
setFunction(rt, "__host_log", 2, [](jsi::Runtime &rt, const jsi::Value &, const jsi::Value *args, size_t) {
|
|
235
|
+
const std::string level = args[0].asString(rt).utf8(rt);
|
|
236
|
+
std::FILE *out = level == "warn" || level == "error" ? stderr : stdout;
|
|
237
|
+
std::fprintf(out, "%s\n", args[1].asString(rt).utf8(rt).c_str());
|
|
238
|
+
std::fflush(out);
|
|
239
|
+
return jsi::Value::undefined();
|
|
240
|
+
});
|
|
241
|
+
setFunction(rt, "__host_timer", 2, [this](jsi::Runtime &rt, const jsi::Value &, const jsi::Value *args, size_t) {
|
|
242
|
+
const double id = args[0].asNumber();
|
|
243
|
+
js_.postAfter(args[1].asNumber(), [id](jsi::Runtime &rt) {
|
|
244
|
+
rt.global().getPropertyAsFunction(rt, "__host_timer_fire").call(rt, id);
|
|
245
|
+
});
|
|
246
|
+
return jsi::Value::undefined();
|
|
247
|
+
});
|
|
248
|
+
setFunction(rt, "__host_b64encode", 3, [](jsi::Runtime &rt, const jsi::Value &, const jsi::Value *args, size_t) {
|
|
249
|
+
const auto buffer = args[0].asObject(rt).getArrayBuffer(rt);
|
|
250
|
+
const auto offset = static_cast<size_t>(args[1].asNumber());
|
|
251
|
+
const auto length = static_cast<size_t>(args[2].asNumber());
|
|
252
|
+
return jsi::Value(rt, jsi::String::createFromAscii(rt, toBase64(buffer.data(rt) + offset, length)));
|
|
253
|
+
});
|
|
254
|
+
setFunction(rt, "__host_b64decode", 1, [](jsi::Runtime &rt, const jsi::Value &, const jsi::Value *args, size_t) {
|
|
255
|
+
auto bytes = fromBase64(args[0].asString(rt).utf8(rt));
|
|
256
|
+
return jsi::Value(rt, jsi::ArrayBuffer(rt, std::make_shared<VectorBuffer>(std::move(bytes))));
|
|
257
|
+
});
|
|
258
|
+
setFunction(rt, "__host_reply", 2, [this](jsi::Runtime &rt, const jsi::Value &, const jsi::Value *args, size_t) {
|
|
259
|
+
const auto pending = replies_.find(args[0].asString(rt).utf8(rt));
|
|
260
|
+
if (pending != replies_.end()) {
|
|
261
|
+
const ReplyFn reply = std::move(pending->second);
|
|
262
|
+
replies_.erase(pending);
|
|
263
|
+
reply(args[1].asString(rt).utf8(rt));
|
|
264
|
+
}
|
|
265
|
+
return jsi::Value::undefined();
|
|
266
|
+
});
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
std::string Core::boot(std::string bundle, const std::string &sourceUrl, const std::string &dataPath) {
|
|
270
|
+
token_ = randomToken();
|
|
271
|
+
std::promise<std::string> booted;
|
|
272
|
+
js_.start([&](jsi::Runtime &rt) {
|
|
273
|
+
try {
|
|
274
|
+
installHostFunctions(rt);
|
|
275
|
+
rt.evaluateJavaScript(std::make_shared<jsi::StringBuffer>(std::move(bundle)), sourceUrl);
|
|
276
|
+
emscripten::internal::_embind_initialize_bindings(rt, dataPath);
|
|
277
|
+
rt.global().getPropertyAsFunction(rt, "__crossbind_start").call(rt);
|
|
278
|
+
booted.set_value("");
|
|
279
|
+
} catch (const jsi::JSError &error) {
|
|
280
|
+
booted.set_value(error.getMessage() + "\n" + error.getStack());
|
|
281
|
+
} catch (const std::exception &error) {
|
|
282
|
+
booted.set_value(error.what());
|
|
283
|
+
}
|
|
284
|
+
});
|
|
285
|
+
return booted.get_future().get();
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
void Core::receive(std::string message, ReplyFn reply) {
|
|
289
|
+
if (message.compare(0, token_.size(), token_) != 0) {
|
|
290
|
+
reply(errorReply("crossbind: this message does not carry the page's token"));
|
|
291
|
+
return;
|
|
292
|
+
}
|
|
293
|
+
message.erase(0, token_.size());
|
|
294
|
+
js_.post([this, message = std::move(message), reply = std::move(reply)](jsi::Runtime &rt) {
|
|
295
|
+
const std::string call = std::to_string(nextReply_++);
|
|
296
|
+
replies_.emplace(call, reply);
|
|
297
|
+
try {
|
|
298
|
+
rt.global().getPropertyAsFunction(rt, "__crossbind_receive")
|
|
299
|
+
.call(rt, jsi::String::createFromUtf8(rt, call), jsi::String::createFromUtf8(rt, message));
|
|
300
|
+
} catch (const std::exception &error) {
|
|
301
|
+
replies_.erase(call);
|
|
302
|
+
reply(errorReply(error.what()));
|
|
303
|
+
}
|
|
304
|
+
});
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
void Core::serve(SchemeRequest request, SchemeRespond answer) {
|
|
308
|
+
SchemeRespond respond = cors ? SchemeRespond([answer = std::move(answer)](SchemeResponse response) {
|
|
309
|
+
response.headers.emplace_back("Access-Control-Allow-Origin", "*");
|
|
310
|
+
answer(std::move(response));
|
|
311
|
+
})
|
|
312
|
+
: std::move(answer);
|
|
313
|
+
const auto route = ROUTES.find(request.path);
|
|
314
|
+
if (route == ROUTES.end()) {
|
|
315
|
+
respond(pageFile(pageDir_, request.path));
|
|
316
|
+
return;
|
|
317
|
+
}
|
|
318
|
+
if (("&" + request.query + "&").find("&t=" + token_ + "&") == std::string::npos) {
|
|
319
|
+
respond(SchemeResponse{403, "text/plain", bytesOf("crossbind: this request does not carry the page's token")});
|
|
320
|
+
return;
|
|
321
|
+
}
|
|
322
|
+
js_.post([function = route->second, request = std::move(request), respond = std::move(respond)](jsi::Runtime &rt) mutable {
|
|
323
|
+
try {
|
|
324
|
+
respond(binaryCall(rt, function, request));
|
|
325
|
+
} catch (const std::exception &error) {
|
|
326
|
+
respond(SchemeResponse{500, "text/plain", bytesOf(error.what())});
|
|
327
|
+
}
|
|
328
|
+
});
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
void Core::reset() {
|
|
332
|
+
js_.post([](jsi::Runtime &rt) { rt.global().getPropertyAsFunction(rt, "__crossbind_reset").call(rt); });
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
} // namespace crossbind::app
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
#pragma once
|
|
2
|
+
|
|
3
|
+
// What every app host shares: the module in Hermes on a thread of its own, the page's messages and its crossbind://
|
|
4
|
+
// requests. A platform adds the window, the webview and their channels (platform.h).
|
|
5
|
+
#include <hermes/hermes.h>
|
|
6
|
+
#include <jsi/jsi.h>
|
|
7
|
+
|
|
8
|
+
#include "platform.h"
|
|
9
|
+
|
|
10
|
+
#include <chrono>
|
|
11
|
+
#include <condition_variable>
|
|
12
|
+
#include <deque>
|
|
13
|
+
#include <functional>
|
|
14
|
+
#include <map>
|
|
15
|
+
#include <memory>
|
|
16
|
+
#include <mutex>
|
|
17
|
+
#include <string>
|
|
18
|
+
#include <thread>
|
|
19
|
+
#include <unordered_map>
|
|
20
|
+
|
|
21
|
+
namespace crossbind::app {
|
|
22
|
+
|
|
23
|
+
namespace jsi = facebook::jsi;
|
|
24
|
+
using Clock = std::chrono::steady_clock;
|
|
25
|
+
|
|
26
|
+
std::string readFile(const std::string &path);
|
|
27
|
+
|
|
28
|
+
// Runs every JS task on one thread, as JSI requires, and drains Hermes' microtasks after each, so a reply that waits
|
|
29
|
+
// on a promise goes out before the next message.
|
|
30
|
+
class JsThread {
|
|
31
|
+
public:
|
|
32
|
+
using Task = std::function<void(jsi::Runtime &)>;
|
|
33
|
+
|
|
34
|
+
void start(Task boot);
|
|
35
|
+
void post(Task task);
|
|
36
|
+
void postAfter(double ms, Task task);
|
|
37
|
+
// The runtime stays alive: bindings keep JSI values in globals that would outlive its teardown.
|
|
38
|
+
void stop();
|
|
39
|
+
|
|
40
|
+
private:
|
|
41
|
+
Task next();
|
|
42
|
+
void loop(const Task &boot);
|
|
43
|
+
void run(const Task &task);
|
|
44
|
+
|
|
45
|
+
std::unique_ptr<facebook::hermes::HermesRuntime> runtime_;
|
|
46
|
+
std::mutex mutex_;
|
|
47
|
+
std::condition_variable wake_;
|
|
48
|
+
std::deque<Task> tasks_;
|
|
49
|
+
std::multimap<Clock::time_point, Task> timers_;
|
|
50
|
+
bool stopping_ = false;
|
|
51
|
+
std::thread thread_;
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
class Core {
|
|
55
|
+
public:
|
|
56
|
+
explicit Core(std::string pageDir) : pageDir_(std::move(pageDir)) {}
|
|
57
|
+
|
|
58
|
+
// Loads the host bundle, Hermes bytecode or source, and starts the module. Returns an error, or nothing.
|
|
59
|
+
std::string boot(std::string bundle, const std::string &sourceUrl, const std::string &dataPath);
|
|
60
|
+
// A page message, which the host bundle answers through __host_reply.
|
|
61
|
+
void receive(std::string message, ReplyFn reply);
|
|
62
|
+
// The page's files, and the calls whose bytes are the request or response body.
|
|
63
|
+
void serve(SchemeRequest request, SchemeRespond respond);
|
|
64
|
+
// A new page replaced the one that held the module's objects.
|
|
65
|
+
void reset();
|
|
66
|
+
void stop() { js_.stop(); }
|
|
67
|
+
const std::string &token() const { return token_; }
|
|
68
|
+
|
|
69
|
+
// Every response lets any origin read it, as a page from a dev server needs.
|
|
70
|
+
bool cors = false;
|
|
71
|
+
|
|
72
|
+
private:
|
|
73
|
+
void installHostFunctions(jsi::Runtime &rt);
|
|
74
|
+
|
|
75
|
+
JsThread js_;
|
|
76
|
+
std::string pageDir_;
|
|
77
|
+
// Made at boot for the page's top frame. Every message and every call made as a crossbind:// request carries it, so
|
|
78
|
+
// markup and frames that cannot read the top frame's globals cannot make one.
|
|
79
|
+
std::string token_;
|
|
80
|
+
std::unordered_map<std::string, ReplyFn> replies_;
|
|
81
|
+
long long nextReply_ = 0;
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
} // namespace crossbind::app
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
#import <UIKit/UIKit.h>
|
|
2
|
+
|
|
3
|
+
#include "platform.h"
|
|
4
|
+
|
|
5
|
+
// The app's Xcode project copies the app's files into the bundle's app folder.
|
|
6
|
+
std::string resourceDir() { return std::string(NSBundle.mainBundle.resourcePath.UTF8String) + "/app"; }
|
|
7
|
+
|
|
8
|
+
void onQuit(std::function<void()> quit) {
|
|
9
|
+
[NSNotificationCenter.defaultCenter addObserverForName:UIApplicationWillTerminateNotification
|
|
10
|
+
object:nil
|
|
11
|
+
queue:nil
|
|
12
|
+
usingBlock:^(NSNotification *) {
|
|
13
|
+
quit();
|
|
14
|
+
}];
|
|
15
|
+
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
// An iOS app: one scene whose WKWebView page reaches a crossbind module running in Hermes, as on macOS. The page comes
|
|
2
|
+
// from crossbind://app/, or in a debug app the dev server opens, from the URL CROSSBIND_APP_URL names.
|
|
3
|
+
#import <UIKit/UIKit.h>
|
|
4
|
+
#import <WebKit/WebKit.h>
|
|
5
|
+
|
|
6
|
+
#include "app.h"
|
|
7
|
+
|
|
8
|
+
namespace {
|
|
9
|
+
|
|
10
|
+
namespace app = crossbind::app;
|
|
11
|
+
|
|
12
|
+
app::Core *core;
|
|
13
|
+
const char *devUrl;
|
|
14
|
+
|
|
15
|
+
} // namespace
|
|
16
|
+
|
|
17
|
+
@interface CBSceneDelegate : UIResponder <UIWindowSceneDelegate>
|
|
18
|
+
@property(nonatomic, strong) UIWindow *window;
|
|
19
|
+
@end
|
|
20
|
+
|
|
21
|
+
@implementation CBSceneDelegate
|
|
22
|
+
|
|
23
|
+
- (void)scene:(UIScene *)scene willConnectToSession:(UISceneSession *)session options:(UISceneConnectionOptions *)options {
|
|
24
|
+
WKWebViewConfiguration *configuration = [WKWebViewConfiguration new];
|
|
25
|
+
registerSchemeHandler((__bridge void *)configuration, "crossbind", [](SchemeRequest request, SchemeRespond respond) {
|
|
26
|
+
core->serve(std::move(request), std::move(respond));
|
|
27
|
+
});
|
|
28
|
+
WKWebView *webView = [[WKWebView alloc] initWithFrame:CGRectZero configuration:configuration];
|
|
29
|
+
#ifdef CROSSBIND_APP_DEV
|
|
30
|
+
if (@available(iOS 16.4, *)) webView.inspectable = YES;
|
|
31
|
+
#endif
|
|
32
|
+
const std::string url = app::attach(*core, (__bridge void *)webView, devUrl);
|
|
33
|
+
|
|
34
|
+
UIViewController *controller = [UIViewController new];
|
|
35
|
+
controller.view = webView;
|
|
36
|
+
self.window = [[UIWindow alloc] initWithWindowScene:(UIWindowScene *)scene];
|
|
37
|
+
self.window.rootViewController = controller;
|
|
38
|
+
[self.window makeKeyAndVisible];
|
|
39
|
+
[webView loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:@(url.c_str())]]];
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
@end
|
|
43
|
+
|
|
44
|
+
@interface CBAppDelegate : UIResponder <UIApplicationDelegate>
|
|
45
|
+
@end
|
|
46
|
+
|
|
47
|
+
@implementation CBAppDelegate
|
|
48
|
+
|
|
49
|
+
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)options {
|
|
50
|
+
devUrl = app::devUrl();
|
|
51
|
+
core = &app::start(devUrl);
|
|
52
|
+
onQuit([] { app::quit(*core, 0); });
|
|
53
|
+
return YES;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
- (UISceneConfiguration *)application:(UIApplication *)application
|
|
57
|
+
configurationForConnectingSceneSession:(UISceneSession *)session
|
|
58
|
+
options:(UISceneConnectionOptions *)options {
|
|
59
|
+
UISceneConfiguration *configuration = [[UISceneConfiguration alloc] initWithName:@"Default" sessionRole:session.role];
|
|
60
|
+
configuration.delegateClass = CBSceneDelegate.class;
|
|
61
|
+
return configuration;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
@end
|
|
65
|
+
|
|
66
|
+
// The app's Xcode project only calls this from its main(): the whole app comes prebuilt.
|
|
67
|
+
extern "C" int crossbind_app_main(int argc, char *argv[]) {
|
|
68
|
+
@autoreleasepool {
|
|
69
|
+
return UIApplicationMain(argc, argv, nil, NSStringFromClass(CBAppDelegate.class));
|
|
70
|
+
}
|
|
71
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
#include "platform.h"
|
|
2
|
+
|
|
3
|
+
#include <unistd.h>
|
|
4
|
+
|
|
5
|
+
#include <climits>
|
|
6
|
+
|
|
7
|
+
namespace {
|
|
8
|
+
|
|
9
|
+
std::string executablePath() {
|
|
10
|
+
char path[PATH_MAX];
|
|
11
|
+
const ssize_t length = readlink("/proc/self/exe", path, sizeof path);
|
|
12
|
+
return length > 0 ? std::string(path, static_cast<size_t>(length)) : std::string();
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
} // namespace
|
|
16
|
+
|
|
17
|
+
// A Linux app is a directory: the executable, and the app's files in resources/ beside it.
|
|
18
|
+
std::string resourceDir() {
|
|
19
|
+
const std::string executable = executablePath();
|
|
20
|
+
return executable.substr(0, executable.find_last_of('/')) + "/resources";
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
std::string appName() {
|
|
24
|
+
const std::string executable = executablePath();
|
|
25
|
+
return executable.substr(executable.find_last_of('/') + 1);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// The app ends with its window; anything else ends the process as it ends any other.
|
|
29
|
+
void onQuit(std::function<void()>) {}
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
#include "main_thread.h"
|
|
2
|
+
#include "platform.h"
|
|
3
|
+
|
|
4
|
+
#include <webkit2/webkit2.h>
|
|
5
|
+
|
|
6
|
+
#include <cstdio>
|
|
7
|
+
#include <cstring>
|
|
8
|
+
|
|
9
|
+
namespace {
|
|
10
|
+
|
|
11
|
+
// Wraps the page's console and reports uncaught errors, in the top frame alone.
|
|
12
|
+
constexpr const char *CONSOLE_SCRIPT = "(() => {"
|
|
13
|
+
"const text = (args) => args.map((arg) => {"
|
|
14
|
+
" if (arg instanceof Error) return `${arg.message}\\n${arg.stack}`;"
|
|
15
|
+
" if (typeof arg === 'string') return arg;"
|
|
16
|
+
" try { return JSON.stringify(arg); } catch { return String(arg); }"
|
|
17
|
+
"}).join(' ');"
|
|
18
|
+
"const post = (level, args) => window.webkit.messageHandlers.crossbind_console.postMessage([level, text(args)]);"
|
|
19
|
+
"for (const level of ['log', 'info', 'warn', 'error', 'debug']) {"
|
|
20
|
+
" const original = console[level].bind(console);"
|
|
21
|
+
" console[level] = (...args) => { post(level, args); original(...args); };"
|
|
22
|
+
"}"
|
|
23
|
+
"addEventListener('error', (event) => post('error', [event.error ?? event.message]));"
|
|
24
|
+
"addEventListener('unhandledrejection', (event) => post('error', [event.reason]));"
|
|
25
|
+
"})();";
|
|
26
|
+
|
|
27
|
+
std::string stringOf(JSCValue *value) {
|
|
28
|
+
gchar *text = jsc_value_to_string(value);
|
|
29
|
+
std::string result(text ? text : "");
|
|
30
|
+
g_free(text);
|
|
31
|
+
return result;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// WebKitGTK does not say which frame sent a message; the host checks the token the message carries instead.
|
|
35
|
+
gboolean onMessage(WebKitUserContentManager *, JSCValue *value, WebKitScriptMessageReply *reply, gpointer data) {
|
|
36
|
+
const std::string message = jsc_value_is_string(value) ? stringOf(value) : "";
|
|
37
|
+
JSCContext *context = JSC_CONTEXT(g_object_ref(jsc_value_get_context(value)));
|
|
38
|
+
webkit_script_message_reply_ref(reply);
|
|
39
|
+
(*static_cast<MessageHandler *>(data))("", message, [reply, context](const std::string &text) {
|
|
40
|
+
onMainThread([reply, context, text] {
|
|
41
|
+
JSCValue *answer = jsc_value_new_string(context, text.c_str());
|
|
42
|
+
webkit_script_message_reply_return_value(reply, answer);
|
|
43
|
+
g_object_unref(answer);
|
|
44
|
+
webkit_script_message_reply_unref(reply);
|
|
45
|
+
g_object_unref(context);
|
|
46
|
+
});
|
|
47
|
+
});
|
|
48
|
+
return TRUE;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
void onConsole(WebKitUserContentManager *, WebKitJavascriptResult *result, gpointer) {
|
|
52
|
+
JSCValue *entry = webkit_javascript_result_get_js_value(result);
|
|
53
|
+
JSCValue *level = jsc_value_object_get_property_at_index(entry, 0);
|
|
54
|
+
JSCValue *text = jsc_value_object_get_property_at_index(entry, 1);
|
|
55
|
+
const std::string name = stringOf(level);
|
|
56
|
+
std::FILE *out = name == "warn" || name == "error" ? stderr : stdout;
|
|
57
|
+
std::fprintf(out, "%s\n", stringOf(text).c_str());
|
|
58
|
+
std::fflush(out);
|
|
59
|
+
g_object_unref(level);
|
|
60
|
+
g_object_unref(text);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
std::vector<uint8_t> readBody(WebKitURISchemeRequest *request) {
|
|
64
|
+
std::vector<uint8_t> body;
|
|
65
|
+
GInputStream *stream = webkit_uri_scheme_request_get_http_body(request);
|
|
66
|
+
if (!stream) return body;
|
|
67
|
+
uint8_t chunk[65536];
|
|
68
|
+
for (gssize read; (read = g_input_stream_read(stream, chunk, sizeof chunk, nullptr, nullptr)) > 0;) {
|
|
69
|
+
body.insert(body.end(), chunk, chunk + read);
|
|
70
|
+
}
|
|
71
|
+
g_object_unref(stream);
|
|
72
|
+
return body;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
void respond(WebKitURISchemeRequest *request, SchemeResponse response) {
|
|
76
|
+
auto *bytes = new std::vector<uint8_t>(std::move(response.body));
|
|
77
|
+
GBytes *data = g_bytes_new_with_free_func(bytes->data(), bytes->size(),
|
|
78
|
+
[](gpointer owned) { delete static_cast<std::vector<uint8_t> *>(owned); }, bytes);
|
|
79
|
+
GInputStream *stream = g_memory_input_stream_new_from_bytes(data);
|
|
80
|
+
WebKitURISchemeResponse *answer = webkit_uri_scheme_response_new(stream, static_cast<gint64>(g_bytes_get_size(data)));
|
|
81
|
+
webkit_uri_scheme_response_set_status(answer, static_cast<guint>(response.status), nullptr);
|
|
82
|
+
webkit_uri_scheme_response_set_content_type(answer, response.mime.c_str());
|
|
83
|
+
if (!response.headers.empty()) {
|
|
84
|
+
SoupMessageHeaders *headers = soup_message_headers_new(SOUP_MESSAGE_HEADERS_RESPONSE);
|
|
85
|
+
for (const auto &[name, value] : response.headers) soup_message_headers_append(headers, name.c_str(), value.c_str());
|
|
86
|
+
webkit_uri_scheme_response_set_http_headers(answer, headers);
|
|
87
|
+
}
|
|
88
|
+
webkit_uri_scheme_request_finish_with_response(request, answer);
|
|
89
|
+
g_object_unref(answer);
|
|
90
|
+
g_object_unref(stream);
|
|
91
|
+
g_bytes_unref(data);
|
|
92
|
+
g_object_unref(request);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
void onSchemeRequest(WebKitURISchemeRequest *request, gpointer data) {
|
|
96
|
+
SchemeRequest incoming;
|
|
97
|
+
const char *method = webkit_uri_scheme_request_get_http_method(request);
|
|
98
|
+
incoming.method = method ? method : "GET";
|
|
99
|
+
const char *path = webkit_uri_scheme_request_get_path(request);
|
|
100
|
+
incoming.path = path ? path : "";
|
|
101
|
+
const char *uri = webkit_uri_scheme_request_get_uri(request);
|
|
102
|
+
const char *query = uri ? std::strchr(uri, '?') : nullptr;
|
|
103
|
+
incoming.query = query ? query + 1 : "";
|
|
104
|
+
incoming.body = readBody(request);
|
|
105
|
+
g_object_ref(request);
|
|
106
|
+
(*static_cast<SchemeHandler *>(data))(std::move(incoming), [request](SchemeResponse response) {
|
|
107
|
+
onMainThread([request, response = std::move(response)]() mutable { respond(request, std::move(response)); });
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
} // namespace
|
|
112
|
+
|
|
113
|
+
void installReplyChannel(void *webView, const char *name, MessageHandler handler) {
|
|
114
|
+
WebKitUserContentManager *manager = webkit_web_view_get_user_content_manager(WEBKIT_WEB_VIEW(webView));
|
|
115
|
+
const std::string signal = std::string("script-message-with-reply-received::") + name;
|
|
116
|
+
g_signal_connect(manager, signal.c_str(), G_CALLBACK(onMessage), new MessageHandler(std::move(handler)));
|
|
117
|
+
webkit_user_content_manager_register_script_message_handler_with_reply(manager, name, nullptr);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
void forwardConsole(void *webView) {
|
|
121
|
+
WebKitUserContentManager *manager = webkit_web_view_get_user_content_manager(WEBKIT_WEB_VIEW(webView));
|
|
122
|
+
g_signal_connect(manager, "script-message-received::crossbind_console", G_CALLBACK(onConsole), nullptr);
|
|
123
|
+
webkit_user_content_manager_register_script_message_handler(manager, "crossbind_console");
|
|
124
|
+
addTopFrameScript(webView, CONSOLE_SCRIPT);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
void addTopFrameScript(void *webView, const std::string &source) {
|
|
128
|
+
WebKitUserContentManager *manager = webkit_web_view_get_user_content_manager(WEBKIT_WEB_VIEW(webView));
|
|
129
|
+
WebKitUserScript *script = webkit_user_script_new(source.c_str(), WEBKIT_USER_CONTENT_INJECT_TOP_FRAME,
|
|
130
|
+
WEBKIT_USER_SCRIPT_INJECT_AT_DOCUMENT_START, nullptr, nullptr);
|
|
131
|
+
webkit_user_content_manager_add_script(manager, script);
|
|
132
|
+
webkit_user_script_unref(script);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// The page's requests are secure-context requests, and CORS applies to them as it does to https.
|
|
136
|
+
void registerSchemeHandler(void *target, const char *scheme, SchemeHandler handler) {
|
|
137
|
+
WebKitWebContext *context = webkit_web_view_get_context(WEBKIT_WEB_VIEW(target));
|
|
138
|
+
webkit_web_context_register_uri_scheme(context, scheme, onSchemeRequest, new SchemeHandler(std::move(handler)),
|
|
139
|
+
[](gpointer owned) { delete static_cast<SchemeHandler *>(owned); });
|
|
140
|
+
WebKitSecurityManager *security = webkit_web_context_get_security_manager(context);
|
|
141
|
+
webkit_security_manager_register_uri_scheme_as_secure(security, scheme);
|
|
142
|
+
webkit_security_manager_register_uri_scheme_as_cors_enabled(security, scheme);
|
|
143
|
+
}
|