@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,16 @@
|
|
|
1
|
+
#pragma once
|
|
2
|
+
|
|
3
|
+
#include <glib.h>
|
|
4
|
+
|
|
5
|
+
#include <functional>
|
|
6
|
+
|
|
7
|
+
// Runs fn on the GTK main loop: at once when the caller is on it, otherwise as soon as the loop gets to it.
|
|
8
|
+
inline void onMainThread(std::function<void()> fn) {
|
|
9
|
+
g_main_context_invoke_full(
|
|
10
|
+
nullptr, G_PRIORITY_DEFAULT,
|
|
11
|
+
[](gpointer data) -> gboolean {
|
|
12
|
+
(*static_cast<std::function<void()> *>(data))();
|
|
13
|
+
return G_SOURCE_REMOVE;
|
|
14
|
+
},
|
|
15
|
+
new std::function<void()>(std::move(fn)), [](gpointer data) { delete static_cast<std::function<void()> *>(data); });
|
|
16
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
#include "platform.h"
|
|
2
|
+
|
|
3
|
+
#include <webkit2/webkit2.h>
|
|
4
|
+
|
|
5
|
+
#include <algorithm>
|
|
6
|
+
|
|
7
|
+
// A page from another origin in any frame would reach the module's channel and its crossbind:// routes, so the webview
|
|
8
|
+
// never loads one. A link the user follows to such a page opens in the default browser instead.
|
|
9
|
+
namespace {
|
|
10
|
+
|
|
11
|
+
struct Lock {
|
|
12
|
+
std::vector<std::string> origins;
|
|
13
|
+
std::function<void()> newPage;
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
std::string originOf(const std::string &uri) {
|
|
17
|
+
const auto scheme = uri.find("://");
|
|
18
|
+
if (scheme == std::string::npos) return uri;
|
|
19
|
+
return uri.substr(0, uri.find_first_of("/?#", scheme + 3));
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
bool allows(const Lock &lock, const std::string &uri) {
|
|
23
|
+
if (uri.rfind("about:", 0) == 0) return true;
|
|
24
|
+
// A blob URL carries the origin of the page that made it.
|
|
25
|
+
if (uri.rfind("blob:", 0) == 0) return allows(lock, uri.substr(5));
|
|
26
|
+
return std::find(lock.origins.begin(), lock.origins.end(), originOf(uri)) != lock.origins.end();
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
gboolean onDecidePolicy(WebKitWebView *, WebKitPolicyDecision *decision, WebKitPolicyDecisionType type, gpointer data) {
|
|
30
|
+
if (type != WEBKIT_POLICY_DECISION_TYPE_NAVIGATION_ACTION && type != WEBKIT_POLICY_DECISION_TYPE_NEW_WINDOW_ACTION) return FALSE;
|
|
31
|
+
WebKitNavigationAction *action = webkit_navigation_policy_decision_get_navigation_action(WEBKIT_NAVIGATION_POLICY_DECISION(decision));
|
|
32
|
+
const char *requested = webkit_uri_request_get_uri(webkit_navigation_action_get_request(action));
|
|
33
|
+
const std::string uri = requested ? requested : "";
|
|
34
|
+
if (allows(*static_cast<Lock *>(data), uri)) return FALSE;
|
|
35
|
+
const bool followed = type == WEBKIT_POLICY_DECISION_TYPE_NEW_WINDOW_ACTION
|
|
36
|
+
|| webkit_navigation_action_get_navigation_type(action) == WEBKIT_NAVIGATION_TYPE_LINK_CLICKED;
|
|
37
|
+
const std::string scheme = uri.substr(0, uri.find(':'));
|
|
38
|
+
if (followed && (scheme == "http" || scheme == "https" || scheme == "mailto")) {
|
|
39
|
+
g_app_info_launch_default_for_uri(uri.c_str(), nullptr, nullptr);
|
|
40
|
+
}
|
|
41
|
+
webkit_policy_decision_ignore(decision);
|
|
42
|
+
return TRUE;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
void onLoadChanged(WebKitWebView *, WebKitLoadEvent event, gpointer data) {
|
|
46
|
+
if (event == WEBKIT_LOAD_COMMITTED) static_cast<Lock *>(data)->newPage();
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
} // namespace
|
|
50
|
+
|
|
51
|
+
void restrictNavigation(void *webView, std::vector<std::string> origins, std::function<void()> newPage) {
|
|
52
|
+
auto *lock = new Lock{std::move(origins), std::move(newPage)};
|
|
53
|
+
g_signal_connect(WEBKIT_WEB_VIEW(webView), "decide-policy", G_CALLBACK(onDecidePolicy), lock);
|
|
54
|
+
g_signal_connect(WEBKIT_WEB_VIEW(webView), "load-changed", G_CALLBACK(onLoadChanged), lock);
|
|
55
|
+
}
|
package/cpp/src/main.cpp
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
// A desktop app: a window with the system webview whose page reaches a crossbind module running in Hermes. The page
|
|
2
|
+
// comes from crossbind://app/, or in the debug host the dev server opens, from the URL CROSSBIND_APP_URL names. Its
|
|
3
|
+
// calls arrive as webview messages, their large bytes as crossbind:// request and response bodies.
|
|
4
|
+
#include "app.h"
|
|
5
|
+
#include "platform.h"
|
|
6
|
+
#include "webview/webview.h"
|
|
7
|
+
|
|
8
|
+
namespace {
|
|
9
|
+
|
|
10
|
+
namespace app = crossbind::app;
|
|
11
|
+
|
|
12
|
+
constexpr int WIDTH = 1024;
|
|
13
|
+
constexpr int HEIGHT = 768;
|
|
14
|
+
|
|
15
|
+
SchemeHandler schemeHandler;
|
|
16
|
+
|
|
17
|
+
#ifdef __APPLE__
|
|
18
|
+
// WKWebView takes its scheme handlers from the configuration it is made from.
|
|
19
|
+
void configureWebView(void *configuration) { registerSchemeHandler(configuration, "crossbind", schemeHandler); }
|
|
20
|
+
#elif defined(_WIN32)
|
|
21
|
+
// WebView2 takes its schemes from the environment the webview is made in; a dev server's page requests the scheme too.
|
|
22
|
+
// webview asks again on every attempt to make the environment, which keeps what it needs of them.
|
|
23
|
+
std::vector<std::string> schemeOrigins;
|
|
24
|
+
ICoreWebView2EnvironmentOptions *environmentOptions() {
|
|
25
|
+
static auto *options = static_cast<ICoreWebView2EnvironmentOptions *>(schemeEnvironmentOptions("crossbind", schemeOrigins));
|
|
26
|
+
return options;
|
|
27
|
+
}
|
|
28
|
+
#endif
|
|
29
|
+
|
|
30
|
+
} // namespace
|
|
31
|
+
|
|
32
|
+
int main() {
|
|
33
|
+
const char *devUrl = app::devUrl();
|
|
34
|
+
app::Core &core = app::start(devUrl);
|
|
35
|
+
schemeHandler = [&core](SchemeRequest request, SchemeRespond respond) { core.serve(std::move(request), std::move(respond)); };
|
|
36
|
+
|
|
37
|
+
#ifdef __APPLE__
|
|
38
|
+
webview::detail::cocoa_webkit::cocoa_configure_hook = configureWebView;
|
|
39
|
+
#elif defined(_WIN32)
|
|
40
|
+
if (devUrl) schemeOrigins.push_back(app::originOf(devUrl));
|
|
41
|
+
webview::detail::win32_environment_options_hook = environmentOptions;
|
|
42
|
+
#endif
|
|
43
|
+
webview::webview view(devUrl != nullptr, nullptr);
|
|
44
|
+
view.set_title(appName());
|
|
45
|
+
view.set_size(WIDTH, HEIGHT, WEBVIEW_HINT_NONE);
|
|
46
|
+
void *webView = view.browser_controller().value();
|
|
47
|
+
#ifndef __APPLE__
|
|
48
|
+
registerSchemeHandler(webView, "crossbind", schemeHandler);
|
|
49
|
+
#endif
|
|
50
|
+
const std::string url = app::attach(core, webView, devUrl);
|
|
51
|
+
// Quitting from the Dock or the system ends the app as closing its window does.
|
|
52
|
+
onQuit([&core] { app::quit(core, 0); });
|
|
53
|
+
|
|
54
|
+
view.navigate(url);
|
|
55
|
+
view.run();
|
|
56
|
+
app::quit(core, 0);
|
|
57
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
#pragma once
|
|
2
|
+
|
|
3
|
+
// What a platform gives the host: the webview's channels, its URL scheme, its navigation rule and where the app's
|
|
4
|
+
// files are. darwin/ implements it with WKWebView on macOS and iOS (ios/ has the iOS app's own parts), linux/ with
|
|
5
|
+
// WebKitGTK, windows/ with WebView2.
|
|
6
|
+
#include <cstdint>
|
|
7
|
+
#include <functional>
|
|
8
|
+
#include <string>
|
|
9
|
+
#include <utility>
|
|
10
|
+
#include <vector>
|
|
11
|
+
|
|
12
|
+
using ReplyFn = std::function<void(const std::string &)>;
|
|
13
|
+
// A page message with the origin of the frame that sent it, empty where the webview does not tell it (WebKitGTK).
|
|
14
|
+
// reply() may be called from any thread.
|
|
15
|
+
using MessageHandler = std::function<void(std::string origin, std::string message, ReplyFn reply)>;
|
|
16
|
+
|
|
17
|
+
struct SchemeRequest {
|
|
18
|
+
std::string method;
|
|
19
|
+
std::string path;
|
|
20
|
+
std::string query;
|
|
21
|
+
std::vector<uint8_t> body;
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
struct SchemeResponse {
|
|
25
|
+
int status = 200;
|
|
26
|
+
std::string mime = "application/octet-stream";
|
|
27
|
+
std::vector<uint8_t> body;
|
|
28
|
+
std::vector<std::pair<std::string, std::string>> headers;
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
using SchemeRespond = std::function<void(SchemeResponse)>;
|
|
32
|
+
using SchemeHandler = std::function<void(SchemeRequest, SchemeRespond)>;
|
|
33
|
+
|
|
34
|
+
// A message channel whose reply resolves the page's own postMessage() promise.
|
|
35
|
+
void installReplyChannel(void *webView, const char *name, MessageHandler handler);
|
|
36
|
+
// The page's console and uncaught errors, written to this process's output.
|
|
37
|
+
void forwardConsole(void *webView);
|
|
38
|
+
// Runs a script in the top frame of every page, before the page's own.
|
|
39
|
+
void addTopFrameScript(void *webView, const std::string &source);
|
|
40
|
+
// Serves <scheme>:// requests: on macOS of the webview made from this configuration, on Linux of this web view.
|
|
41
|
+
// respond() may be called from any thread.
|
|
42
|
+
void registerSchemeHandler(void *target, const char *scheme, SchemeHandler handler);
|
|
43
|
+
// The webview loads only these origins, in any frame; a link to another opens in the default browser. newPage runs
|
|
44
|
+
// when the main frame commits another document.
|
|
45
|
+
void restrictNavigation(void *webView, std::vector<std::string> origins, std::function<void()> newPage);
|
|
46
|
+
// Runs when the app is quit other than by closing its window (the Dock on macOS, the system on iOS), before it would
|
|
47
|
+
// exit.
|
|
48
|
+
void onQuit(std::function<void()> quit);
|
|
49
|
+
// Where the app's files are, and its name.
|
|
50
|
+
std::string resourceDir();
|
|
51
|
+
std::string appName();
|
|
52
|
+
|
|
53
|
+
#ifdef _WIN32
|
|
54
|
+
// The ICoreWebView2EnvironmentOptions WebView2's environment is made with: they register the scheme, which pages of
|
|
55
|
+
// these other origins may request as well.
|
|
56
|
+
void *schemeEnvironmentOptions(const char *scheme, const std::vector<std::string> &origins);
|
|
57
|
+
#endif
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
#include "platform.h"
|
|
2
|
+
#include "windows/com.h"
|
|
3
|
+
|
|
4
|
+
using namespace crossbind::windows;
|
|
5
|
+
|
|
6
|
+
namespace {
|
|
7
|
+
|
|
8
|
+
std::string executablePath() {
|
|
9
|
+
std::wstring path(MAX_PATH, L'\0');
|
|
10
|
+
for (;;) {
|
|
11
|
+
const DWORD length = GetModuleFileNameW(nullptr, path.data(), static_cast<DWORD>(path.size()));
|
|
12
|
+
if (length < path.size()) {
|
|
13
|
+
path.resize(length);
|
|
14
|
+
return utf8(path.c_str());
|
|
15
|
+
}
|
|
16
|
+
path.resize(path.size() * 2);
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
} // namespace
|
|
21
|
+
|
|
22
|
+
// A Windows app is a directory: the executable, and the app's files in resources\ beside it.
|
|
23
|
+
std::string resourceDir() {
|
|
24
|
+
const std::string executable = executablePath();
|
|
25
|
+
return executable.substr(0, executable.find_last_of("\\/")) + "\\resources";
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
std::string appName() {
|
|
29
|
+
const std::string executable = executablePath();
|
|
30
|
+
std::string name = executable.substr(executable.find_last_of("\\/") + 1);
|
|
31
|
+
if (name.size() > 4 && name.compare(name.size() - 4, 4, ".exe") == 0) name.resize(name.size() - 4);
|
|
32
|
+
return name;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// The app ends with its window; anything else ends the process as it ends any other.
|
|
36
|
+
void onQuit(std::function<void()>) {}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
|
2
|
+
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
|
|
3
|
+
<application xmlns="urn:schemas-microsoft-com:asm.v3">
|
|
4
|
+
<windowsSettings>
|
|
5
|
+
<activeCodePage xmlns="http://schemas.microsoft.com/SMI/2019/WindowsSettings">UTF-8</activeCodePage>
|
|
6
|
+
</windowsSettings>
|
|
7
|
+
</application>
|
|
8
|
+
</assembly>
|
|
@@ -0,0 +1,336 @@
|
|
|
1
|
+
#include "platform.h"
|
|
2
|
+
#include "windows/com.h"
|
|
3
|
+
|
|
4
|
+
// crossbind's windows image writes it beside the SDK's headers.
|
|
5
|
+
#include "crossbind_webview2.h"
|
|
6
|
+
|
|
7
|
+
#include <shlwapi.h>
|
|
8
|
+
|
|
9
|
+
#include <cstdio>
|
|
10
|
+
#include <map>
|
|
11
|
+
#include <memory>
|
|
12
|
+
|
|
13
|
+
using namespace crossbind::windows;
|
|
14
|
+
|
|
15
|
+
namespace {
|
|
16
|
+
|
|
17
|
+
// The runtime the app declares itself compatible with, as the SDK's own options do: the one of the SDK it builds with.
|
|
18
|
+
constexpr const wchar_t *TARGET_VERSION = CROSSBIND_WEBVIEW2_TARGET_VERSION;
|
|
19
|
+
|
|
20
|
+
// WebView2's page messages answer nothing, so the page side of WebKit's reply channels is this script: a message
|
|
21
|
+
// carries its channel's name and an id, and the reply comes back with the id. The host hears the top frame alone. An id
|
|
22
|
+
// names its document too, so a reply meant for the page before a reload answers nothing in the next one.
|
|
23
|
+
constexpr const char *CHANNELS_SCRIPT = R"js((() => {
|
|
24
|
+
const pending = new Map();
|
|
25
|
+
const session = crypto.getRandomValues(new Uint32Array(2)).join('-');
|
|
26
|
+
let next = 0;
|
|
27
|
+
const handlers = {};
|
|
28
|
+
Object.defineProperty(globalThis, 'webkit', { value: { messageHandlers: handlers } });
|
|
29
|
+
Object.defineProperty(globalThis, '__crossbind_channel', { value: (name) => {
|
|
30
|
+
handlers[name] = {
|
|
31
|
+
postMessage: (text) => new Promise((resolve) => {
|
|
32
|
+
const id = `${session}.${++next}`;
|
|
33
|
+
pending.set(id, resolve);
|
|
34
|
+
chrome.webview.postMessage(`${name}\n${id}\n${text}`);
|
|
35
|
+
}),
|
|
36
|
+
};
|
|
37
|
+
} });
|
|
38
|
+
chrome.webview.addEventListener('message', (event) => {
|
|
39
|
+
const text = String(event.data);
|
|
40
|
+
const cut = text.indexOf('\n');
|
|
41
|
+
const id = text.slice(0, cut);
|
|
42
|
+
pending.get(id)?.(text.slice(cut + 1));
|
|
43
|
+
pending.delete(id);
|
|
44
|
+
});
|
|
45
|
+
})();)js";
|
|
46
|
+
|
|
47
|
+
// Wraps the page's console and reports uncaught errors.
|
|
48
|
+
constexpr const char *CONSOLE_SCRIPT = R"js((() => {
|
|
49
|
+
const text = (args) => args.map((arg) => {
|
|
50
|
+
if (arg instanceof Error) return `${arg.message}\n${arg.stack}`;
|
|
51
|
+
if (typeof arg === 'string') return arg;
|
|
52
|
+
try { return JSON.stringify(arg); } catch { return String(arg); }
|
|
53
|
+
}).join(' ');
|
|
54
|
+
const post = (level, args) => chrome.webview.postMessage(`crossbind_console\n0\n${level}\n${text(args)}`);
|
|
55
|
+
for (const level of ['log', 'info', 'warn', 'error', 'debug']) {
|
|
56
|
+
const original = console[level].bind(console);
|
|
57
|
+
console[level] = (...args) => { post(level, args); original(...args); };
|
|
58
|
+
}
|
|
59
|
+
addEventListener('error', (event) => post('error', [event.error ?? event.message]));
|
|
60
|
+
addEventListener('unhandledrejection', (event) => post('error', [event.reason]));
|
|
61
|
+
})();)js";
|
|
62
|
+
|
|
63
|
+
std::map<std::string, MessageHandler> &handlers() {
|
|
64
|
+
static auto *all = new std::map<std::string, MessageHandler>();
|
|
65
|
+
return *all;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
std::string originOf(const std::string &url) {
|
|
69
|
+
const auto scheme = url.find("://");
|
|
70
|
+
if (scheme == std::string::npos) return url;
|
|
71
|
+
return url.substr(0, url.find_first_of("/?#", scheme + 3));
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// WebView2 adds a script later; the UI thread waits for it, as webview's own scripts do, so the first page gets it.
|
|
75
|
+
void addScript(ICoreWebView2 *core, const std::string &source) {
|
|
76
|
+
auto added = std::make_shared<bool>(false);
|
|
77
|
+
auto *done = new Handler<ICoreWebView2AddScriptToExecuteOnDocumentCreatedCompletedHandler, HRESULT, LPCWSTR>(
|
|
78
|
+
IID_ICoreWebView2AddScriptToExecuteOnDocumentCreatedCompletedHandler, [added](HRESULT, LPCWSTR) -> HRESULT {
|
|
79
|
+
*added = true;
|
|
80
|
+
return S_OK;
|
|
81
|
+
});
|
|
82
|
+
if (SUCCEEDED(core->AddScriptToExecuteOnDocumentCreated(wide("if (window === window.top) {\n" + source + "\n}").c_str(), done))) {
|
|
83
|
+
MSG message;
|
|
84
|
+
while (!*added) {
|
|
85
|
+
const BOOL got = GetMessageW(&message, nullptr, 0, 0);
|
|
86
|
+
// The window closed meanwhile: the app's own loop gets the quit as well.
|
|
87
|
+
if (got == 0) PostQuitMessage(static_cast<int>(message.wParam));
|
|
88
|
+
if (got <= 0) break;
|
|
89
|
+
TranslateMessage(&message);
|
|
90
|
+
DispatchMessageW(&message);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
done->Release();
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
void listen(ICoreWebView2 *core) {
|
|
97
|
+
static bool listening = false;
|
|
98
|
+
if (listening) return;
|
|
99
|
+
listening = true;
|
|
100
|
+
addScript(core, CHANNELS_SCRIPT);
|
|
101
|
+
auto *received = new Handler<ICoreWebView2WebMessageReceivedEventHandler, ICoreWebView2 *, ICoreWebView2WebMessageReceivedEventArgs *>(
|
|
102
|
+
IID_ICoreWebView2WebMessageReceivedEventHandler, [core](ICoreWebView2 *, ICoreWebView2WebMessageReceivedEventArgs *args) -> HRESULT {
|
|
103
|
+
LPWSTR raw = nullptr;
|
|
104
|
+
if (FAILED(args->TryGetWebMessageAsString(&raw))) return S_OK;
|
|
105
|
+
const std::string text = take(raw);
|
|
106
|
+
const size_t first = text.find('\n');
|
|
107
|
+
const size_t second = first == std::string::npos ? first : text.find('\n', first + 1);
|
|
108
|
+
if (second == std::string::npos) return S_OK;
|
|
109
|
+
const auto handler = handlers().find(text.substr(0, first));
|
|
110
|
+
if (handler == handlers().end()) return S_OK;
|
|
111
|
+
LPWSTR source = nullptr;
|
|
112
|
+
// A sender WebView2 cannot name is no origin a page may call from.
|
|
113
|
+
const std::string origin = SUCCEEDED(args->get_Source(&source)) ? originOf(take(source)) : "null";
|
|
114
|
+
const std::string id = text.substr(first + 1, second - first - 1);
|
|
115
|
+
handler->second(origin, text.substr(second + 1), [core, id](const std::string &reply) {
|
|
116
|
+
onMainThread([core, message = wide(id + "\n" + reply)] { core->PostWebMessageAsString(message.c_str()); });
|
|
117
|
+
});
|
|
118
|
+
return S_OK;
|
|
119
|
+
});
|
|
120
|
+
EventRegistrationToken token;
|
|
121
|
+
core->add_WebMessageReceived(received, &token);
|
|
122
|
+
received->Release();
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
std::vector<uint8_t> readAll(IStream *stream) {
|
|
126
|
+
std::vector<uint8_t> body;
|
|
127
|
+
uint8_t chunk[65536];
|
|
128
|
+
for (ULONG read = 0; SUCCEEDED(stream->Read(chunk, sizeof chunk, &read)) && read > 0;) body.insert(body.end(), chunk, chunk + read);
|
|
129
|
+
return body;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
const wchar_t *reasonOf(int status) {
|
|
133
|
+
switch (status) {
|
|
134
|
+
case 200: return L"OK";
|
|
135
|
+
case 204: return L"No Content";
|
|
136
|
+
case 403: return L"Forbidden";
|
|
137
|
+
case 404: return L"Not Found";
|
|
138
|
+
default: return status < 400 ? L"OK" : L"Error";
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
class SchemeRegistration final : public ICoreWebView2CustomSchemeRegistration {
|
|
143
|
+
public:
|
|
144
|
+
SchemeRegistration(std::wstring scheme, std::vector<std::wstring> origins) : scheme_(std::move(scheme)), origins_(std::move(origins)) {}
|
|
145
|
+
|
|
146
|
+
ULONG STDMETHODCALLTYPE AddRef() override { return ++refs_; }
|
|
147
|
+
ULONG STDMETHODCALLTYPE Release() override {
|
|
148
|
+
const ULONG left = --refs_;
|
|
149
|
+
if (left == 0) delete this;
|
|
150
|
+
return left;
|
|
151
|
+
}
|
|
152
|
+
HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, void **out) override {
|
|
153
|
+
if (!out) return E_POINTER;
|
|
154
|
+
if (!IsEqualIID(riid, IID_IUnknown) && !IsEqualIID(riid, IID_ICoreWebView2CustomSchemeRegistration)) {
|
|
155
|
+
*out = nullptr;
|
|
156
|
+
return E_NOINTERFACE;
|
|
157
|
+
}
|
|
158
|
+
*out = static_cast<ICoreWebView2CustomSchemeRegistration *>(this);
|
|
159
|
+
AddRef();
|
|
160
|
+
return S_OK;
|
|
161
|
+
}
|
|
162
|
+
HRESULT STDMETHODCALLTYPE get_SchemeName(LPWSTR *name) override {
|
|
163
|
+
*name = give(scheme_);
|
|
164
|
+
return S_OK;
|
|
165
|
+
}
|
|
166
|
+
// Its pages are secure contexts, as https pages are.
|
|
167
|
+
HRESULT STDMETHODCALLTYPE get_TreatAsSecure(BOOL *value) override {
|
|
168
|
+
*value = TRUE;
|
|
169
|
+
return S_OK;
|
|
170
|
+
}
|
|
171
|
+
HRESULT STDMETHODCALLTYPE put_TreatAsSecure(BOOL) override { return E_NOTIMPL; }
|
|
172
|
+
HRESULT STDMETHODCALLTYPE GetAllowedOrigins(UINT32 *count, LPWSTR **origins) override {
|
|
173
|
+
*count = static_cast<UINT32>(origins_.size());
|
|
174
|
+
*origins = static_cast<LPWSTR *>(CoTaskMemAlloc(sizeof(LPWSTR) * (origins_.size() + 1)));
|
|
175
|
+
for (size_t i = 0; i < origins_.size(); ++i) (*origins)[i] = give(origins_[i]);
|
|
176
|
+
return S_OK;
|
|
177
|
+
}
|
|
178
|
+
HRESULT STDMETHODCALLTYPE SetAllowedOrigins(UINT32, LPCWSTR *) override { return E_NOTIMPL; }
|
|
179
|
+
// crossbind://app is an origin of its own.
|
|
180
|
+
HRESULT STDMETHODCALLTYPE get_HasAuthorityComponent(BOOL *value) override {
|
|
181
|
+
*value = TRUE;
|
|
182
|
+
return S_OK;
|
|
183
|
+
}
|
|
184
|
+
HRESULT STDMETHODCALLTYPE put_HasAuthorityComponent(BOOL) override { return E_NOTIMPL; }
|
|
185
|
+
|
|
186
|
+
private:
|
|
187
|
+
std::atomic<ULONG> refs_{1};
|
|
188
|
+
std::wstring scheme_;
|
|
189
|
+
std::vector<std::wstring> origins_;
|
|
190
|
+
};
|
|
191
|
+
|
|
192
|
+
class EnvironmentOptions final : public ICoreWebView2EnvironmentOptions, public ICoreWebView2EnvironmentOptions4 {
|
|
193
|
+
public:
|
|
194
|
+
explicit EnvironmentOptions(ICoreWebView2CustomSchemeRegistration *scheme) : scheme_(scheme) {}
|
|
195
|
+
~EnvironmentOptions() { scheme_->Release(); }
|
|
196
|
+
|
|
197
|
+
ULONG STDMETHODCALLTYPE AddRef() override { return ++refs_; }
|
|
198
|
+
ULONG STDMETHODCALLTYPE Release() override {
|
|
199
|
+
const ULONG left = --refs_;
|
|
200
|
+
if (left == 0) delete this;
|
|
201
|
+
return left;
|
|
202
|
+
}
|
|
203
|
+
HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, void **out) override {
|
|
204
|
+
if (!out) return E_POINTER;
|
|
205
|
+
if (IsEqualIID(riid, IID_IUnknown) || IsEqualIID(riid, IID_ICoreWebView2EnvironmentOptions)) {
|
|
206
|
+
*out = static_cast<ICoreWebView2EnvironmentOptions *>(this);
|
|
207
|
+
} else if (IsEqualIID(riid, IID_ICoreWebView2EnvironmentOptions4)) {
|
|
208
|
+
*out = static_cast<ICoreWebView2EnvironmentOptions4 *>(this);
|
|
209
|
+
} else {
|
|
210
|
+
*out = nullptr;
|
|
211
|
+
return E_NOINTERFACE;
|
|
212
|
+
}
|
|
213
|
+
AddRef();
|
|
214
|
+
return S_OK;
|
|
215
|
+
}
|
|
216
|
+
HRESULT STDMETHODCALLTYPE get_AdditionalBrowserArguments(LPWSTR *value) override {
|
|
217
|
+
*value = nullptr;
|
|
218
|
+
return S_OK;
|
|
219
|
+
}
|
|
220
|
+
HRESULT STDMETHODCALLTYPE put_AdditionalBrowserArguments(LPCWSTR) override { return E_NOTIMPL; }
|
|
221
|
+
HRESULT STDMETHODCALLTYPE get_Language(LPWSTR *value) override {
|
|
222
|
+
*value = nullptr;
|
|
223
|
+
return S_OK;
|
|
224
|
+
}
|
|
225
|
+
HRESULT STDMETHODCALLTYPE put_Language(LPCWSTR) override { return E_NOTIMPL; }
|
|
226
|
+
HRESULT STDMETHODCALLTYPE get_TargetCompatibleBrowserVersion(LPWSTR *value) override {
|
|
227
|
+
*value = give(TARGET_VERSION);
|
|
228
|
+
return S_OK;
|
|
229
|
+
}
|
|
230
|
+
HRESULT STDMETHODCALLTYPE put_TargetCompatibleBrowserVersion(LPCWSTR) override { return E_NOTIMPL; }
|
|
231
|
+
HRESULT STDMETHODCALLTYPE get_AllowSingleSignOnUsingOSPrimaryAccount(BOOL *allow) override {
|
|
232
|
+
*allow = FALSE;
|
|
233
|
+
return S_OK;
|
|
234
|
+
}
|
|
235
|
+
HRESULT STDMETHODCALLTYPE put_AllowSingleSignOnUsingOSPrimaryAccount(BOOL) override { return E_NOTIMPL; }
|
|
236
|
+
HRESULT STDMETHODCALLTYPE GetCustomSchemeRegistrations(UINT32 *count, ICoreWebView2CustomSchemeRegistration ***registrations) override {
|
|
237
|
+
*count = 1;
|
|
238
|
+
*registrations = static_cast<ICoreWebView2CustomSchemeRegistration **>(CoTaskMemAlloc(sizeof(ICoreWebView2CustomSchemeRegistration *)));
|
|
239
|
+
(*registrations)[0] = scheme_;
|
|
240
|
+
scheme_->AddRef();
|
|
241
|
+
return S_OK;
|
|
242
|
+
}
|
|
243
|
+
HRESULT STDMETHODCALLTYPE SetCustomSchemeRegistrations(UINT32, ICoreWebView2CustomSchemeRegistration **) override { return E_NOTIMPL; }
|
|
244
|
+
|
|
245
|
+
private:
|
|
246
|
+
std::atomic<ULONG> refs_{1};
|
|
247
|
+
ICoreWebView2CustomSchemeRegistration *scheme_;
|
|
248
|
+
};
|
|
249
|
+
|
|
250
|
+
} // namespace
|
|
251
|
+
|
|
252
|
+
void *schemeEnvironmentOptions(const char *scheme, const std::vector<std::string> &origins) {
|
|
253
|
+
std::vector<std::wstring> allowed;
|
|
254
|
+
for (const auto &origin : origins) allowed.push_back(wide(origin));
|
|
255
|
+
return static_cast<ICoreWebView2EnvironmentOptions *>(new EnvironmentOptions(new SchemeRegistration(wide(scheme), std::move(allowed))));
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
void installReplyChannel(void *webView, const char *name, MessageHandler handler) {
|
|
259
|
+
ICoreWebView2 *core = coreWebView(webView);
|
|
260
|
+
listen(core);
|
|
261
|
+
handlers()[name] = std::move(handler);
|
|
262
|
+
addScript(core, std::string("__crossbind_channel('") + name + "');");
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
void forwardConsole(void *webView) {
|
|
266
|
+
ICoreWebView2 *core = coreWebView(webView);
|
|
267
|
+
listen(core);
|
|
268
|
+
handlers()["crossbind_console"] = [](std::string, std::string message, ReplyFn) {
|
|
269
|
+
const size_t cut = message.find('\n');
|
|
270
|
+
const std::string level = message.substr(0, cut);
|
|
271
|
+
std::FILE *out = level == "warn" || level == "error" ? stderr : stdout;
|
|
272
|
+
std::fprintf(out, "%s\n", cut == std::string::npos ? "" : message.c_str() + cut + 1);
|
|
273
|
+
std::fflush(out);
|
|
274
|
+
};
|
|
275
|
+
addScript(core, CONSOLE_SCRIPT);
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
void addTopFrameScript(void *webView, const std::string &source) { addScript(coreWebView(webView), source); }
|
|
279
|
+
|
|
280
|
+
// The scheme itself is registered with the environment (schemeEnvironmentOptions); this serves its requests.
|
|
281
|
+
void registerSchemeHandler(void *target, const char *scheme, SchemeHandler handler) {
|
|
282
|
+
ICoreWebView2 *core = coreWebView(target);
|
|
283
|
+
ICoreWebView2_2 *core2 = nullptr;
|
|
284
|
+
core->QueryInterface(IID_ICoreWebView2_2, reinterpret_cast<void **>(&core2));
|
|
285
|
+
ICoreWebView2Environment *environment = nullptr;
|
|
286
|
+
core2->get_Environment(&environment);
|
|
287
|
+
core2->Release();
|
|
288
|
+
core->AddWebResourceRequestedFilter(wide(std::string(scheme) + "://*").c_str(), COREWEBVIEW2_WEB_RESOURCE_CONTEXT_ALL);
|
|
289
|
+
auto *serve = new SchemeHandler(std::move(handler));
|
|
290
|
+
auto *requested = new Handler<ICoreWebView2WebResourceRequestedEventHandler, ICoreWebView2 *, ICoreWebView2WebResourceRequestedEventArgs *>(
|
|
291
|
+
IID_ICoreWebView2WebResourceRequestedEventHandler,
|
|
292
|
+
[serve, environment](ICoreWebView2 *, ICoreWebView2WebResourceRequestedEventArgs *args) -> HRESULT {
|
|
293
|
+
ICoreWebView2WebResourceRequest *request = nullptr;
|
|
294
|
+
if (FAILED(args->get_Request(&request))) return S_OK;
|
|
295
|
+
SchemeRequest incoming;
|
|
296
|
+
LPWSTR method = nullptr;
|
|
297
|
+
request->get_Method(&method);
|
|
298
|
+
incoming.method = take(method);
|
|
299
|
+
LPWSTR uri = nullptr;
|
|
300
|
+
request->get_Uri(&uri);
|
|
301
|
+
const std::string url = take(uri);
|
|
302
|
+
const size_t authority = url.find("://");
|
|
303
|
+
const size_t path = url.find('/', authority == std::string::npos ? 0 : authority + 3);
|
|
304
|
+
const size_t query = url.find('?', path == std::string::npos ? 0 : path);
|
|
305
|
+
incoming.path = path == std::string::npos ? "/" : url.substr(path, query == std::string::npos ? std::string::npos : query - path);
|
|
306
|
+
incoming.query = query == std::string::npos ? "" : url.substr(query + 1, url.find('#', query) - query - 1);
|
|
307
|
+
IStream *content = nullptr;
|
|
308
|
+
if (SUCCEEDED(request->get_Content(&content)) && content) {
|
|
309
|
+
incoming.body = readAll(content);
|
|
310
|
+
content->Release();
|
|
311
|
+
}
|
|
312
|
+
request->Release();
|
|
313
|
+
ICoreWebView2Deferral *deferral = nullptr;
|
|
314
|
+
args->GetDeferral(&deferral);
|
|
315
|
+
args->AddRef();
|
|
316
|
+
(*serve)(std::move(incoming), [args, deferral, environment](SchemeResponse response) {
|
|
317
|
+
onMainThread([args, deferral, environment, response = std::move(response)] {
|
|
318
|
+
IStream *body = SHCreateMemStream(response.body.data(), static_cast<UINT>(response.body.size()));
|
|
319
|
+
std::string headers = "Content-Type: " + response.mime;
|
|
320
|
+
for (const auto &[name, value] : response.headers) headers += "\r\n" + name + ": " + value;
|
|
321
|
+
ICoreWebView2WebResourceResponse *answer = nullptr;
|
|
322
|
+
environment->CreateWebResourceResponse(body, response.status, reasonOf(response.status), wide(headers).c_str(), &answer);
|
|
323
|
+
args->put_Response(answer);
|
|
324
|
+
deferral->Complete();
|
|
325
|
+
if (answer) answer->Release();
|
|
326
|
+
if (body) body->Release();
|
|
327
|
+
deferral->Release();
|
|
328
|
+
args->Release();
|
|
329
|
+
});
|
|
330
|
+
});
|
|
331
|
+
return S_OK;
|
|
332
|
+
});
|
|
333
|
+
EventRegistrationToken token;
|
|
334
|
+
core->add_WebResourceRequested(requested, &token);
|
|
335
|
+
requested->Release();
|
|
336
|
+
}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
#include "windows/com.h"
|
|
2
|
+
|
|
3
|
+
#include <cstring>
|
|
4
|
+
#include <deque>
|
|
5
|
+
#include <mutex>
|
|
6
|
+
|
|
7
|
+
namespace crossbind::windows {
|
|
8
|
+
|
|
9
|
+
namespace {
|
|
10
|
+
|
|
11
|
+
constexpr UINT RUN = WM_APP + 1;
|
|
12
|
+
DWORD uiThread = 0;
|
|
13
|
+
HWND dispatcher = nullptr;
|
|
14
|
+
std::mutex queueMutex;
|
|
15
|
+
std::deque<std::function<void()>> queue;
|
|
16
|
+
|
|
17
|
+
LRESULT CALLBACK dispatch(HWND window, UINT message, WPARAM wParam, LPARAM lParam) {
|
|
18
|
+
if (message != RUN) return DefWindowProcW(window, message, wParam, lParam);
|
|
19
|
+
std::deque<std::function<void()>> tasks;
|
|
20
|
+
{
|
|
21
|
+
std::lock_guard<std::mutex> lock(queueMutex);
|
|
22
|
+
tasks.swap(queue);
|
|
23
|
+
}
|
|
24
|
+
for (auto &task : tasks) task();
|
|
25
|
+
return 0;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// A message-only window of the UI thread, whose message loop runs what other threads post to it.
|
|
29
|
+
void startDispatcher() {
|
|
30
|
+
uiThread = GetCurrentThreadId();
|
|
31
|
+
WNDCLASSW type{};
|
|
32
|
+
type.lpfnWndProc = dispatch;
|
|
33
|
+
type.hInstance = GetModuleHandleW(nullptr);
|
|
34
|
+
type.lpszClassName = L"crossbind_dispatcher";
|
|
35
|
+
RegisterClassW(&type);
|
|
36
|
+
dispatcher = CreateWindowExW(0, type.lpszClassName, L"", 0, 0, 0, 0, 0, HWND_MESSAGE, nullptr, type.hInstance, nullptr);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
} // namespace
|
|
40
|
+
|
|
41
|
+
std::wstring wide(const std::string &text) {
|
|
42
|
+
if (text.empty()) return {};
|
|
43
|
+
const int size = MultiByteToWideChar(CP_UTF8, 0, text.data(), static_cast<int>(text.size()), nullptr, 0);
|
|
44
|
+
std::wstring result(static_cast<size_t>(size), L'\0');
|
|
45
|
+
MultiByteToWideChar(CP_UTF8, 0, text.data(), static_cast<int>(text.size()), result.data(), size);
|
|
46
|
+
return result;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
std::string utf8(const wchar_t *text) {
|
|
50
|
+
if (!text || !*text) return {};
|
|
51
|
+
const int size = WideCharToMultiByte(CP_UTF8, 0, text, -1, nullptr, 0, nullptr, nullptr);
|
|
52
|
+
std::string result(static_cast<size_t>(size - 1), '\0');
|
|
53
|
+
WideCharToMultiByte(CP_UTF8, 0, text, -1, result.data(), size, nullptr, nullptr);
|
|
54
|
+
return result;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
std::string take(LPWSTR text) {
|
|
58
|
+
std::string result = utf8(text);
|
|
59
|
+
CoTaskMemFree(text);
|
|
60
|
+
return result;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
LPWSTR give(const std::wstring &text) {
|
|
64
|
+
auto *copy = static_cast<LPWSTR>(CoTaskMemAlloc((text.size() + 1) * sizeof(wchar_t)));
|
|
65
|
+
if (copy) std::memcpy(copy, text.c_str(), (text.size() + 1) * sizeof(wchar_t));
|
|
66
|
+
return copy;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// The platform's calls all start here, on the UI thread, before any other thread posts to it.
|
|
70
|
+
ICoreWebView2 *coreWebView(void *controller) {
|
|
71
|
+
if (!dispatcher) startDispatcher();
|
|
72
|
+
ICoreWebView2 *core = nullptr;
|
|
73
|
+
static_cast<ICoreWebView2Controller *>(controller)->get_CoreWebView2(&core);
|
|
74
|
+
return core;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// What other threads post waits in one queue, which one message drains: Windows caps the messages a thread has posted.
|
|
78
|
+
void onMainThread(std::function<void()> fn) {
|
|
79
|
+
if (GetCurrentThreadId() == uiThread) {
|
|
80
|
+
fn();
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
bool isIdle;
|
|
84
|
+
{
|
|
85
|
+
std::lock_guard<std::mutex> lock(queueMutex);
|
|
86
|
+
isIdle = queue.empty();
|
|
87
|
+
queue.push_back(std::move(fn));
|
|
88
|
+
}
|
|
89
|
+
if (isIdle) PostMessageW(dispatcher, RUN, 0, 0);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
} // namespace crossbind::windows
|