diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..13566b8 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,8 @@ +# Default ignored files +/shelf/ +/workspace.xml +# Editor-based HTTP Client requests +/httpRequests/ +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 0000000..68da31b --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..35eb1dd --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/.idea/webview_go.iml b/.idea/webview_go.iml new file mode 100644 index 0000000..5e764c4 --- /dev/null +++ b/.idea/webview_go.iml @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/examples/basic/main.go b/examples/basic/main.go index 6b96c81..c3fb852 100644 --- a/examples/basic/main.go +++ b/examples/basic/main.go @@ -7,6 +7,8 @@ func main() { defer w.Destroy() w.SetTitle("Basic Example") w.SetSize(480, 320, webview.HintNone) - w.SetHtml("Thanks for using webview!") + w.SetUserAgent("Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/139.0.0.0 Safari/537.36") + w.Navigate("https://www.whatismybrowser.com/detect/what-is-my-user-agent/") + // w.SetHtml("Thanks for using webview!") w.Run() } diff --git a/examples/bind/main.go b/examples/bind/main.go index 3374f84..0b17980 100644 --- a/examples/bind/main.go +++ b/examples/bind/main.go @@ -26,6 +26,8 @@ func main() { defer w.Destroy() w.SetTitle("Bind Example") w.SetSize(480, 320, webview.HintNone) + //w.SetUserAgent("мой кастомный UA") + //w.Navigate("https://www.whatismybrowser.com/detect/what-is-my-user-agent/") // A binding that increments a value and immediately returns the new value. w.Bind("increment", func() IncrementResult { diff --git a/go.mod b/go.mod index a3c3067..5bf671a 100644 --- a/go.mod +++ b/go.mod @@ -1,3 +1,6 @@ module github.com/webview/webview_go go 1.13 + +replace github.com/webview/webview => ../webview/core +replace github.com/webview/webview_go => ../webview_go diff --git a/libs/webview/include/api.h b/libs/webview/include/api.h new file mode 120000 index 0000000..2e91e1d --- /dev/null +++ b/libs/webview/include/api.h @@ -0,0 +1 @@ +/Users/pavel/src/webview/core/include/webview/api.h \ No newline at end of file diff --git a/libs/webview/include/backends.hh b/libs/webview/include/backends.hh new file mode 120000 index 0000000..af74250 --- /dev/null +++ b/libs/webview/include/backends.hh @@ -0,0 +1 @@ +/Users/pavel/src/webview/core/include/webview/backends.hh \ No newline at end of file diff --git a/libs/webview/include/c_api_impl.hh b/libs/webview/include/c_api_impl.hh new file mode 120000 index 0000000..175f563 --- /dev/null +++ b/libs/webview/include/c_api_impl.hh @@ -0,0 +1 @@ +/Users/pavel/src/webview/core/include/webview/c_api_impl.hh \ No newline at end of file diff --git a/libs/webview/include/detail b/libs/webview/include/detail new file mode 120000 index 0000000..7bc412c --- /dev/null +++ b/libs/webview/include/detail @@ -0,0 +1 @@ +/Users/pavel/src/webview/core/include/webview/detail \ No newline at end of file diff --git a/libs/webview/include/errors.h b/libs/webview/include/errors.h new file mode 120000 index 0000000..42da266 --- /dev/null +++ b/libs/webview/include/errors.h @@ -0,0 +1 @@ +/Users/pavel/src/webview/core/include/webview/errors.h \ No newline at end of file diff --git a/libs/webview/include/errors.hh b/libs/webview/include/errors.hh new file mode 120000 index 0000000..ff5643e --- /dev/null +++ b/libs/webview/include/errors.hh @@ -0,0 +1 @@ +/Users/pavel/src/webview/core/include/webview/errors.hh \ No newline at end of file diff --git a/libs/webview/include/json_deprecated.hh b/libs/webview/include/json_deprecated.hh new file mode 120000 index 0000000..b17165d --- /dev/null +++ b/libs/webview/include/json_deprecated.hh @@ -0,0 +1 @@ +/Users/pavel/src/webview/core/include/webview/json_deprecated.hh \ No newline at end of file diff --git a/libs/webview/include/macros.h b/libs/webview/include/macros.h new file mode 120000 index 0000000..c81f963 --- /dev/null +++ b/libs/webview/include/macros.h @@ -0,0 +1 @@ +/Users/pavel/src/webview/core/include/webview/macros.h \ No newline at end of file diff --git a/libs/webview/include/types.h b/libs/webview/include/types.h new file mode 120000 index 0000000..9f02735 --- /dev/null +++ b/libs/webview/include/types.h @@ -0,0 +1 @@ +/Users/pavel/src/webview/core/include/webview/types.h \ No newline at end of file diff --git a/libs/webview/include/types.hh b/libs/webview/include/types.hh new file mode 120000 index 0000000..047e9af --- /dev/null +++ b/libs/webview/include/types.hh @@ -0,0 +1 @@ +/Users/pavel/src/webview/core/include/webview/types.hh \ No newline at end of file diff --git a/libs/webview/include/version.h b/libs/webview/include/version.h new file mode 120000 index 0000000..f7c927d --- /dev/null +++ b/libs/webview/include/version.h @@ -0,0 +1 @@ +/Users/pavel/src/webview/core/include/webview/version.h \ No newline at end of file diff --git a/libs/webview/include/webview.h b/libs/webview/include/webview.h deleted file mode 100644 index 62ac14b..0000000 --- a/libs/webview/include/webview.h +++ /dev/null @@ -1,3598 +0,0 @@ -/* - * MIT License - * - * Copyright (c) 2017 Serge Zaitsev - * Copyright (c) 2022 Steffen André Langnes - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -/// @file webview.h - -#ifndef WEBVIEW_H -#define WEBVIEW_H - -/** - * Used to specify function linkage such as extern, inline, etc. - * - * When @c WEBVIEW_API is not already defined, the defaults are as follows: - * - * - @c inline when compiling C++ code. - * - @c extern when compiling C code. - * - * The following macros can be used to automatically set an appropriate - * value for @c WEBVIEW_API: - * - * - Define @c WEBVIEW_BUILD_SHARED when building a shared library. - * - Define @c WEBVIEW_SHARED when using a shared library. - * - Define @c WEBVIEW_STATIC when building or using a static library. - */ -#ifndef WEBVIEW_API -#if defined(WEBVIEW_SHARED) || defined(WEBVIEW_BUILD_SHARED) -#if defined(_WIN32) || defined(__CYGWIN__) -#if defined(WEBVIEW_BUILD_SHARED) -#define WEBVIEW_API __declspec(dllexport) -#else -#define WEBVIEW_API __declspec(dllimport) -#endif -#else -#define WEBVIEW_API __attribute__((visibility("default"))) -#endif -#elif !defined(WEBVIEW_STATIC) && defined(__cplusplus) -#define WEBVIEW_API inline -#else -#define WEBVIEW_API extern -#endif -#endif - -/// @name Version -/// @{ - -#ifndef WEBVIEW_VERSION_MAJOR -/// The current library major version. -#define WEBVIEW_VERSION_MAJOR 0 -#endif - -#ifndef WEBVIEW_VERSION_MINOR -/// The current library minor version. -#define WEBVIEW_VERSION_MINOR 11 -#endif - -#ifndef WEBVIEW_VERSION_PATCH -/// The current library patch version. -#define WEBVIEW_VERSION_PATCH 0 -#endif - -#ifndef WEBVIEW_VERSION_PRE_RELEASE -/// SemVer 2.0.0 pre-release labels prefixed with "-". -#define WEBVIEW_VERSION_PRE_RELEASE "" -#endif - -#ifndef WEBVIEW_VERSION_BUILD_METADATA -/// SemVer 2.0.0 build metadata prefixed with "+". -#define WEBVIEW_VERSION_BUILD_METADATA "" -#endif - -/// @} - -/// @name Used internally -/// @{ - -/// Utility macro for stringifying a macro argument. -#define WEBVIEW_STRINGIFY(x) #x - -/// Utility macro for stringifying the result of a macro argument expansion. -#define WEBVIEW_EXPAND_AND_STRINGIFY(x) WEBVIEW_STRINGIFY(x) - -/// @} - -/// @name Version -/// @{ - -/// SemVer 2.0.0 version number in MAJOR.MINOR.PATCH format. -#define WEBVIEW_VERSION_NUMBER \ - WEBVIEW_EXPAND_AND_STRINGIFY(WEBVIEW_VERSION_MAJOR) \ - "." WEBVIEW_EXPAND_AND_STRINGIFY( \ - WEBVIEW_VERSION_MINOR) "." WEBVIEW_EXPAND_AND_STRINGIFY(WEBVIEW_VERSION_PATCH) - -/// @} - -/// Holds the elements of a MAJOR.MINOR.PATCH version number. -typedef struct { - /// Major version. - unsigned int major; - /// Minor version. - unsigned int minor; - /// Patch version. - unsigned int patch; -} webview_version_t; - -/// Holds the library's version information. -typedef struct { - /// The elements of the version number. - webview_version_t version; - /// SemVer 2.0.0 version number in MAJOR.MINOR.PATCH format. - char version_number[32]; - /// SemVer 2.0.0 pre-release labels prefixed with "-" if specified, otherwise - /// an empty string. - char pre_release[48]; - /// SemVer 2.0.0 build metadata prefixed with "+", otherwise an empty string. - char build_metadata[48]; -} webview_version_info_t; - -/// Pointer to a webview instance. -typedef void *webview_t; - -/// Native handle kind. The actual type depends on the backend. -typedef enum { - /// Top-level window. @c GtkWindow pointer (GTK), @c NSWindow pointer (Cocoa) - /// or @c HWND (Win32). - WEBVIEW_NATIVE_HANDLE_KIND_UI_WINDOW, - /// Browser widget. @c GtkWidget pointer (GTK), @c NSView pointer (Cocoa) or - /// @c HWND (Win32). - WEBVIEW_NATIVE_HANDLE_KIND_UI_WIDGET, - /// Browser controller. @c WebKitWebView pointer (WebKitGTK), @c WKWebView - /// pointer (Cocoa/WebKit) or @c ICoreWebView2Controller pointer - /// (Win32/WebView2). - WEBVIEW_NATIVE_HANDLE_KIND_BROWSER_CONTROLLER -} webview_native_handle_kind_t; - -/// Window size hints -typedef enum { - /// Width and height are default size. - WEBVIEW_HINT_NONE, - /// Width and height are minimum bounds. - WEBVIEW_HINT_MIN, - /// Width and height are maximum bounds. - WEBVIEW_HINT_MAX, - /// Window size can not be changed by a user. - WEBVIEW_HINT_FIXED -} webview_hint_t; - -#ifdef __cplusplus -extern "C" { -#endif - -/** - * Creates a new webview instance. - * - * @param debug Enable developer tools if supported by the backend. - * @param window Optional native window handle, i.e. @c GtkWindow pointer - * @c NSWindow pointer (Cocoa) or @c HWND (Win32). If non-null, - * the webview widget is embedded into the given window, and the - * caller is expected to assume responsibility for the window as - * well as application lifecycle. If the window handle is null, - * a new window is created and both the window and application - * lifecycle are managed by the webview instance. - * @remark Win32: The function also accepts a pointer to @c HWND (Win32) in the - * window parameter for backward compatibility. - * @remark Win32/WebView2: @c CoInitializeEx should be called with - * @c COINIT_APARTMENTTHREADED before attempting to call this function - * with an existing window. Omitting this step may cause WebView2 - * initialization to fail. - * @return @c NULL on failure. Creation can fail for various reasons such - * as when required runtime dependencies are missing or when window - * creation fails. - */ -WEBVIEW_API webview_t webview_create(int debug, void *window); - -/** - * Destroys a webview instance and closes the native window. - * - * @param w The webview instance. - */ -WEBVIEW_API void webview_destroy(webview_t w); - -/** - * Runs the main loop until it's terminated. - * - * @param w The webview instance. - */ -WEBVIEW_API void webview_run(webview_t w); - -/** - * Stops the main loop. It is safe to call this function from another other - * background thread. - * - * @param w The webview instance. - */ -WEBVIEW_API void webview_terminate(webview_t w); - -/** - * Schedules a function to be invoked on the thread with the run/event loop. - * Use this function e.g. to interact with the library or native handles. - * - * @param w The webview instance. - * @param fn The function to be invoked. - * @param arg An optional argument passed along to the callback function. - */ -WEBVIEW_API void -webview_dispatch(webview_t w, void (*fn)(webview_t w, void *arg), void *arg); - -/** - * Returns the native handle of the window associated with the webview instance. - * The handle can be a @c GtkWindow pointer (GTK), @c NSWindow pointer (Cocoa) - * or @c HWND (Win32). - * - * @param w The webview instance. - * @return The handle of the native window. - */ -WEBVIEW_API void *webview_get_window(webview_t w); - -/** - * Get a native handle of choice. - * - * @param w The webview instance. - * @param kind The kind of handle to retrieve. - * @return The native handle or @c NULL. - * @since 0.11 - */ -WEBVIEW_API void *webview_get_native_handle(webview_t w, - webview_native_handle_kind_t kind); - -/** - * Updates the title of the native window. - * - * @param w The webview instance. - * @param title The new title. - */ -WEBVIEW_API void webview_set_title(webview_t w, const char *title); - -/** - * Updates the size of the native window. - * - * @param w The webview instance. - * @param width New width. - * @param height New height. - * @param hints Size hints. - */ -WEBVIEW_API void webview_set_size(webview_t w, int width, int height, - webview_hint_t hints); - -/** - * Navigates webview to the given URL. URL may be a properly encoded data URI. - * - * Example: - * @code{.c} - * webview_navigate(w, "https://github.com/webview/webview"); - * webview_navigate(w, "data:text/html,%3Ch1%3EHello%3C%2Fh1%3E"); - * webview_navigate(w, "data:text/html;base64,PGgxPkhlbGxvPC9oMT4="); - * @endcode - * - * @param w The webview instance. - * @param url URL. - */ -WEBVIEW_API void webview_navigate(webview_t w, const char *url); - -/** - * Load HTML content into the webview. - * - * Example: - * @code{.c} - * webview_set_html(w, "

Hello

"); - * @endcode - * - * @param w The webview instance. - * @param html HTML content. - */ -WEBVIEW_API void webview_set_html(webview_t w, const char *html); - -/** - * Injects JavaScript code to be executed immediately upon loading a page. - * The code will be executed before @c window.onload. - * - * @param w The webview instance. - * @param js JS content. - */ -WEBVIEW_API void webview_init(webview_t w, const char *js); - -/** - * Evaluates arbitrary JavaScript code. - * - * Use bindings if you need to communicate the result of the evaluation. - * - * @param w The webview instance. - * @param js JS content. - */ -WEBVIEW_API void webview_eval(webview_t w, const char *js); - -/** - * Binds a function pointer to a new global JavaScript function. - * - * Internally, JS glue code is injected to create the JS function by the - * given name. The callback function is passed a sequential request - * identifier, a request string and a user-provided argument. The request - * string is a JSON array of the arguments passed to the JS function. - * - * @param w The webview instance. - * @param name Name of the JS function. - * @param fn Callback function. - * @param arg User argument. - */ -WEBVIEW_API void webview_bind(webview_t w, const char *name, - void (*fn)(const char *seq, const char *req, - void *arg), - void *arg); - -/** - * Removes a binding created with webview_bind(). - * - * @param w The webview instance. - * @param name Name of the binding. - */ -WEBVIEW_API void webview_unbind(webview_t w, const char *name); - -/** - * Responds to a binding call from the JS side. - * - * @param w The webview instance. - * @param seq The sequence number of the binding call. Pass along the value - * received in the binding handler (see webview_bind()). - * @param status A status of zero tells the JS side that the binding call was - * succesful; any other value indicates an error. - * @param result The result of the binding call to be returned to the JS side. - * This must either be a valid JSON value or an empty string for - * the primitive JS value @c undefined. - */ -WEBVIEW_API void webview_return(webview_t w, const char *seq, int status, - const char *result); - -/** - * Get the library's version information. - * - * @since 0.10 - */ -WEBVIEW_API const webview_version_info_t *webview_version(void); - -#ifdef __cplusplus -} - -#ifndef WEBVIEW_HEADER - -#if !defined(WEBVIEW_GTK) && !defined(WEBVIEW_COCOA) && !defined(WEBVIEW_EDGE) -#if defined(__APPLE__) -#define WEBVIEW_COCOA -#elif defined(__unix__) -#define WEBVIEW_GTK -#elif defined(_WIN32) -#define WEBVIEW_EDGE -#else -#error "please, specify webview backend" -#endif -#endif - -#ifndef WEBVIEW_DEPRECATED -#if __cplusplus >= 201402L -#define WEBVIEW_DEPRECATED(reason) [[deprecated(reason)]] -#elif defined(_MSC_VER) -#define WEBVIEW_DEPRECATED(reason) __declspec(deprecated(reason)) -#else -#define WEBVIEW_DEPRECATED(reason) __attribute__((deprecated(reason))) -#endif -#endif - -#ifndef WEBVIEW_DEPRECATED_PRIVATE -#define WEBVIEW_DEPRECATED_PRIVATE \ - WEBVIEW_DEPRECATED("Private API should not be used") -#endif - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include - -#if defined(_WIN32) -#define WIN32_LEAN_AND_MEAN -#include -#else -#include -#endif - -namespace webview { - -using dispatch_fn_t = std::function; - -namespace detail { - -// The library's version information. -constexpr const webview_version_info_t library_version_info{ - {WEBVIEW_VERSION_MAJOR, WEBVIEW_VERSION_MINOR, WEBVIEW_VERSION_PATCH}, - WEBVIEW_VERSION_NUMBER, - WEBVIEW_VERSION_PRE_RELEASE, - WEBVIEW_VERSION_BUILD_METADATA}; - -#if defined(_WIN32) -// Converts a narrow (UTF-8-encoded) string into a wide (UTF-16-encoded) string. -inline std::wstring widen_string(const std::string &input) { - if (input.empty()) { - return std::wstring(); - } - UINT cp = CP_UTF8; - DWORD flags = MB_ERR_INVALID_CHARS; - auto input_c = input.c_str(); - auto input_length = static_cast(input.size()); - auto required_length = - MultiByteToWideChar(cp, flags, input_c, input_length, nullptr, 0); - if (required_length > 0) { - std::wstring output(static_cast(required_length), L'\0'); - if (MultiByteToWideChar(cp, flags, input_c, input_length, &output[0], - required_length) > 0) { - return output; - } - } - // Failed to convert string from UTF-8 to UTF-16 - return std::wstring(); -} - -// Converts a wide (UTF-16-encoded) string into a narrow (UTF-8-encoded) string. -inline std::string narrow_string(const std::wstring &input) { - struct wc_flags { - enum TYPE : unsigned int { - // WC_ERR_INVALID_CHARS - err_invalid_chars = 0x00000080U - }; - }; - if (input.empty()) { - return std::string(); - } - UINT cp = CP_UTF8; - DWORD flags = wc_flags::err_invalid_chars; - auto input_c = input.c_str(); - auto input_length = static_cast(input.size()); - auto required_length = WideCharToMultiByte(cp, flags, input_c, input_length, - nullptr, 0, nullptr, nullptr); - if (required_length > 0) { - std::string output(static_cast(required_length), '\0'); - if (WideCharToMultiByte(cp, flags, input_c, input_length, &output[0], - required_length, nullptr, nullptr) > 0) { - return output; - } - } - // Failed to convert string from UTF-16 to UTF-8 - return std::string(); -} -#endif - -inline int json_parse_c(const char *s, size_t sz, const char *key, size_t keysz, - const char **value, size_t *valuesz) { - enum { - JSON_STATE_VALUE, - JSON_STATE_LITERAL, - JSON_STATE_STRING, - JSON_STATE_ESCAPE, - JSON_STATE_UTF8 - } state = JSON_STATE_VALUE; - const char *k = nullptr; - int index = 1; - int depth = 0; - int utf8_bytes = 0; - - *value = nullptr; - *valuesz = 0; - - if (key == nullptr) { - index = static_cast(keysz); - if (index < 0) { - return -1; - } - keysz = 0; - } - - for (; sz > 0; s++, sz--) { - enum { - JSON_ACTION_NONE, - JSON_ACTION_START, - JSON_ACTION_END, - JSON_ACTION_START_STRUCT, - JSON_ACTION_END_STRUCT - } action = JSON_ACTION_NONE; - auto c = static_cast(*s); - switch (state) { - case JSON_STATE_VALUE: - if (c == ' ' || c == '\t' || c == '\n' || c == '\r' || c == ',' || - c == ':') { - continue; - } else if (c == '"') { - action = JSON_ACTION_START; - state = JSON_STATE_STRING; - } else if (c == '{' || c == '[') { - action = JSON_ACTION_START_STRUCT; - } else if (c == '}' || c == ']') { - action = JSON_ACTION_END_STRUCT; - } else if (c == 't' || c == 'f' || c == 'n' || c == '-' || - (c >= '0' && c <= '9')) { - action = JSON_ACTION_START; - state = JSON_STATE_LITERAL; - } else { - return -1; - } - break; - case JSON_STATE_LITERAL: - if (c == ' ' || c == '\t' || c == '\n' || c == '\r' || c == ',' || - c == ']' || c == '}' || c == ':') { - state = JSON_STATE_VALUE; - s--; - sz++; - action = JSON_ACTION_END; - } else if (c < 32 || c > 126) { - return -1; - } // fallthrough - case JSON_STATE_STRING: - if (c < 32 || (c > 126 && c < 192)) { - return -1; - } else if (c == '"') { - action = JSON_ACTION_END; - state = JSON_STATE_VALUE; - } else if (c == '\\') { - state = JSON_STATE_ESCAPE; - } else if (c >= 192 && c < 224) { - utf8_bytes = 1; - state = JSON_STATE_UTF8; - } else if (c >= 224 && c < 240) { - utf8_bytes = 2; - state = JSON_STATE_UTF8; - } else if (c >= 240 && c < 247) { - utf8_bytes = 3; - state = JSON_STATE_UTF8; - } else if (c >= 128 && c < 192) { - return -1; - } - break; - case JSON_STATE_ESCAPE: - if (c == '"' || c == '\\' || c == '/' || c == 'b' || c == 'f' || - c == 'n' || c == 'r' || c == 't' || c == 'u') { - state = JSON_STATE_STRING; - } else { - return -1; - } - break; - case JSON_STATE_UTF8: - if (c < 128 || c > 191) { - return -1; - } - utf8_bytes--; - if (utf8_bytes == 0) { - state = JSON_STATE_STRING; - } - break; - default: - return -1; - } - - if (action == JSON_ACTION_END_STRUCT) { - depth--; - } - - if (depth == 1) { - if (action == JSON_ACTION_START || action == JSON_ACTION_START_STRUCT) { - if (index == 0) { - *value = s; - } else if (keysz > 0 && index == 1) { - k = s; - } else { - index--; - } - } else if (action == JSON_ACTION_END || - action == JSON_ACTION_END_STRUCT) { - if (*value != nullptr && index == 0) { - *valuesz = (size_t)(s + 1 - *value); - return 0; - } else if (keysz > 0 && k != nullptr) { - if (keysz == (size_t)(s - k - 1) && memcmp(key, k + 1, keysz) == 0) { - index = 0; - } else { - index = 2; - } - k = nullptr; - } - } - } - - if (action == JSON_ACTION_START_STRUCT) { - depth++; - } - } - return -1; -} - -constexpr bool is_json_special_char(char c) { - return c == '"' || c == '\\' || c == '\b' || c == '\f' || c == '\n' || - c == '\r' || c == '\t'; -} - -constexpr bool is_ascii_control_char(char c) { return c >= 0 && c <= 0x1f; } - -inline std::string json_escape(const std::string &s, bool add_quotes = true) { - // Calculate the size of the resulting string. - // Add space for the double quotes. - size_t required_length = add_quotes ? 2 : 0; - for (auto c : s) { - if (is_json_special_char(c)) { - // '\' and a single following character - required_length += 2; - continue; - } - if (is_ascii_control_char(c)) { - // '\', 'u', 4 digits - required_length += 6; - continue; - } - ++required_length; - } - // Allocate memory for resulting string only once. - std::string result; - result.reserve(required_length); - if (add_quotes) { - result += '"'; - } - // Copy string while escaping characters. - for (auto c : s) { - if (is_json_special_char(c)) { - static constexpr char special_escape_table[256] = - "\0\0\0\0\0\0\0\0btn\0fr\0\0" - "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" - "\0\0\"\0\0\0\0\0\0\0\0\0\0\0\0\0" - "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" - "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" - "\0\0\0\0\0\0\0\0\0\0\0\0\\"; - result += '\\'; - // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-constant-array-index) - result += special_escape_table[static_cast(c)]; - continue; - } - if (is_ascii_control_char(c)) { - // Escape as \u00xx - static constexpr char hex_alphabet[]{"0123456789abcdef"}; - auto uc = static_cast(c); - auto h = (uc >> 4) & 0x0f; - auto l = uc & 0x0f; - result += "\\u00"; - // NOLINTBEGIN(cppcoreguidelines-pro-bounds-constant-array-index) - result += hex_alphabet[h]; - result += hex_alphabet[l]; - // NOLINTEND(cppcoreguidelines-pro-bounds-constant-array-index) - continue; - } - result += c; - } - if (add_quotes) { - result += '"'; - } - // Should have calculated the exact amount of memory needed - assert(required_length == result.size()); - return result; -} - -inline int json_unescape(const char *s, size_t n, char *out) { - int r = 0; - if (*s++ != '"') { - return -1; - } - while (n > 2) { - char c = *s; - if (c == '\\') { - s++; - n--; - switch (*s) { - case 'b': - c = '\b'; - break; - case 'f': - c = '\f'; - break; - case 'n': - c = '\n'; - break; - case 'r': - c = '\r'; - break; - case 't': - c = '\t'; - break; - case '\\': - c = '\\'; - break; - case '/': - c = '/'; - break; - case '\"': - c = '\"'; - break; - default: // TODO: support unicode decoding - return -1; - } - } - if (out != nullptr) { - *out++ = c; - } - s++; - n--; - r++; - } - if (*s != '"') { - return -1; - } - if (out != nullptr) { - *out = '\0'; - } - return r; -} - -inline std::string json_parse(const std::string &s, const std::string &key, - const int index) { - const char *value; - size_t value_sz; - if (key.empty()) { - json_parse_c(s.c_str(), s.length(), nullptr, index, &value, &value_sz); - } else { - json_parse_c(s.c_str(), s.length(), key.c_str(), key.length(), &value, - &value_sz); - } - if (value != nullptr) { - if (value[0] != '"') { - return {value, value_sz}; - } - int n = json_unescape(value, value_sz, nullptr); - if (n > 0) { - char *decoded = new char[n + 1]; - json_unescape(value, value_sz, decoded); - std::string result(decoded, n); - delete[] decoded; - return result; - } - } - return ""; -} - -// Holds a symbol name and associated type for code clarity. -template class library_symbol { -public: - using type = T; - - constexpr explicit library_symbol(const char *name) : m_name(name) {} - constexpr const char *get_name() const { return m_name; } - -private: - const char *m_name; -}; - -// Loads a native shared library and allows one to get addresses for those -// symbols. -class native_library { -public: - native_library() = default; - - explicit native_library(const std::string &name) - : m_handle{load_library(name)} {} - -#ifdef _WIN32 - explicit native_library(const std::wstring &name) - : m_handle{load_library(name)} {} -#endif - - ~native_library() { - if (m_handle) { -#ifdef _WIN32 - FreeLibrary(m_handle); -#else - dlclose(m_handle); -#endif - m_handle = nullptr; - } - } - - native_library(const native_library &other) = delete; - native_library &operator=(const native_library &other) = delete; - native_library(native_library &&other) noexcept { *this = std::move(other); } - - native_library &operator=(native_library &&other) noexcept { - if (this == &other) { - return *this; - } - m_handle = other.m_handle; - other.m_handle = nullptr; - return *this; - } - - // Returns true if the library is currently loaded; otherwise false. - operator bool() const { return is_loaded(); } - - // Get the address for the specified symbol or nullptr if not found. - template - typename Symbol::type get(const Symbol &symbol) const { - if (is_loaded()) { - // NOLINTBEGIN(cppcoreguidelines-pro-type-reinterpret-cast) -#ifdef _WIN32 -#ifdef __GNUC__ -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wcast-function-type" -#endif - return reinterpret_cast( - GetProcAddress(m_handle, symbol.get_name())); -#ifdef __GNUC__ -#pragma GCC diagnostic pop -#endif -#else - return reinterpret_cast( - dlsym(m_handle, symbol.get_name())); -#endif - // NOLINTEND(cppcoreguidelines-pro-type-reinterpret-cast) - } - return nullptr; - } - - // Returns true if the library is currently loaded; otherwise false. - bool is_loaded() const { return !!m_handle; } - - void detach() { m_handle = nullptr; } - - // Returns true if the library by the given name is currently loaded; otherwise false. - static inline bool is_loaded(const std::string &name) { -#ifdef _WIN32 - auto handle = GetModuleHandleW(widen_string(name).c_str()); -#else - auto handle = dlopen(name.c_str(), RTLD_NOW | RTLD_NOLOAD); - if (handle) { - dlclose(handle); - } -#endif - return !!handle; - } - -private: -#ifdef _WIN32 - using mod_handle_t = HMODULE; -#else - using mod_handle_t = void *; -#endif - - static inline mod_handle_t load_library(const std::string &name) { -#ifdef _WIN32 - return load_library(widen_string(name)); -#else - return dlopen(name.c_str(), RTLD_NOW); -#endif - } - -#ifdef _WIN32 - static inline mod_handle_t load_library(const std::wstring &name) { - return LoadLibraryW(name.c_str()); - } -#endif - - mod_handle_t m_handle{}; -}; - -class engine_base { -public: - virtual ~engine_base() = default; - - void navigate(const std::string &url) { - if (url.empty()) { - navigate_impl("about:blank"); - return; - } - navigate_impl(url); - } - - using binding_t = std::function; - class binding_ctx_t { - public: - binding_ctx_t(binding_t callback, void *arg) - : callback(callback), arg(arg) {} - // This function is called upon execution of the bound JS function - binding_t callback; - // This user-supplied argument is passed to the callback - void *arg; - }; - - using sync_binding_t = std::function; - - // Synchronous bind - void bind(const std::string &name, sync_binding_t fn) { - auto wrapper = [this, fn](const std::string &seq, const std::string &req, - void * /*arg*/) { resolve(seq, 0, fn(req)); }; - bind(name, wrapper, nullptr); - } - - // Asynchronous bind - void bind(const std::string &name, binding_t fn, void *arg) { - // NOLINTNEXTLINE(readability-container-contains): contains() requires C++20 - if (bindings.count(name) > 0) { - return; - } - bindings.emplace(name, binding_ctx_t(fn, arg)); - auto js = "(function() { var name = '" + name + "';" + R""( - var RPC = window._rpc = (window._rpc || {nextSeq: 1}); - window[name] = function() { - var seq = RPC.nextSeq++; - var promise = new Promise(function(resolve, reject) { - RPC[seq] = { - resolve: resolve, - reject: reject, - }; - }); - window.external.invoke(JSON.stringify({ - id: seq, - method: name, - params: Array.prototype.slice.call(arguments), - })); - return promise; - } - })())""; - init(js); - eval(js); - } - - void unbind(const std::string &name) { - auto found = bindings.find(name); - if (found != bindings.end()) { - auto js = "delete window['" + name + "'];"; - init(js); - eval(js); - bindings.erase(found); - } - } - - void resolve(const std::string &seq, int status, const std::string &result) { - // NOLINTNEXTLINE(modernize-avoid-bind): Lambda with move requires C++14 - dispatch(std::bind( - [seq, status, this](std::string escaped_result) { - std::string js; - js += "(function(){var seq = \""; - js += seq; - js += "\";\n"; - js += "var status = "; - js += std::to_string(status); - js += ";\n"; - js += "var result = "; - js += escaped_result; - js += ";\ -var promise = window._rpc[seq];\ -delete window._rpc[seq];\ -if (result !== undefined) {\ - try {\ - result = JSON.parse(result);\ - } catch {\ - promise.reject(new Error(\"Failed to parse binding result as JSON\"));\ - return;\ - }\ -}\ -if (status === 0) {\ - promise.resolve(result);\ -} else {\ - promise.reject(result);\ -}\ -})()"; - eval(js); - }, - result.empty() ? "undefined" : json_escape(result))); - } - - void *window() { return window_impl(); } - void *widget() { return widget_impl(); } - void *browser_controller() { return browser_controller_impl(); }; - void run() { run_impl(); } - void terminate() { terminate_impl(); } - void dispatch(std::function f) { dispatch_impl(f); } - void set_title(const std::string &title) { set_title_impl(title); } - - void set_size(int width, int height, webview_hint_t hints) { - set_size_impl(width, height, hints); - } - - void set_html(const std::string &html) { set_html_impl(html); } - void init(const std::string &js) { init_impl(js); } - void eval(const std::string &js) { eval_impl(js); } - -protected: - virtual void navigate_impl(const std::string &url) = 0; - virtual void *window_impl() = 0; - virtual void *widget_impl() = 0; - virtual void *browser_controller_impl() = 0; - virtual void run_impl() = 0; - virtual void terminate_impl() = 0; - virtual void dispatch_impl(std::function f) = 0; - virtual void set_title_impl(const std::string &title) = 0; - virtual void set_size_impl(int width, int height, webview_hint_t hints) = 0; - virtual void set_html_impl(const std::string &html) = 0; - virtual void init_impl(const std::string &js) = 0; - virtual void eval_impl(const std::string &js) = 0; - - virtual void on_message(const std::string &msg) { - auto seq = json_parse(msg, "id", 0); - auto name = json_parse(msg, "method", 0); - auto args = json_parse(msg, "params", 0); - auto found = bindings.find(name); - if (found == bindings.end()) { - return; - } - const auto &context = found->second; - context.callback(seq, args, context.arg); - } - - virtual void on_window_created() { inc_window_count(); } - - virtual void on_window_destroyed(bool skip_termination = false) { - if (dec_window_count() <= 0) { - if (!skip_termination) { - terminate(); - } - } - } - -private: - static std::atomic_uint &window_ref_count() { - static std::atomic_uint ref_count{0}; - return ref_count; - } - - static unsigned int inc_window_count() { return ++window_ref_count(); } - - static unsigned int dec_window_count() { - auto &count = window_ref_count(); - if (count > 0) { - return --count; - } - return 0; - } - - std::map bindings; -}; - -} // namespace detail - -WEBVIEW_DEPRECATED_PRIVATE -inline int json_parse_c(const char *s, size_t sz, const char *key, size_t keysz, - const char **value, size_t *valuesz) { - return detail::json_parse_c(s, sz, key, keysz, value, valuesz); -} - -WEBVIEW_DEPRECATED_PRIVATE -inline std::string json_escape(const std::string &s) { - return detail::json_escape(s); -} - -WEBVIEW_DEPRECATED_PRIVATE -inline int json_unescape(const char *s, size_t n, char *out) { - return detail::json_unescape(s, n, out); -} - -WEBVIEW_DEPRECATED_PRIVATE -inline std::string json_parse(const std::string &s, const std::string &key, - const int index) { - return detail::json_parse(s, key, index); -} - -} // namespace webview - -#if defined(WEBVIEW_GTK) -// -// ==================================================================== -// -// This implementation uses webkit2gtk backend. It requires gtk+3.0 and -// webkit2gtk-4.0 libraries. Proper compiler flags can be retrieved via: -// -// pkg-config --cflags --libs gtk+-3.0 webkit2gtk-4.0 -// -// ==================================================================== -// -#include - -#include -#include -#include - -#ifdef GDK_WINDOWING_X11 -#include -#endif - -#include -#include - -namespace webview { -namespace detail { - -// Namespace containing workaround for WebKit 2.42 when using NVIDIA GPU -// driver. -// See WebKit bug: https://bugs.webkit.org/show_bug.cgi?id=261874 -// Please remove all of the code in this namespace when it's no longer needed. -namespace webkit_dmabuf { - -// Get environment variable. Not thread-safe. -static inline std::string get_env(const std::string &name) { - auto *value = std::getenv(name.c_str()); - if (value) { - return {value}; - } - return {}; -} - -// Set environment variable. Not thread-safe. -static inline void set_env(const std::string &name, const std::string &value) { - ::setenv(name.c_str(), value.c_str(), 1); -} - -// Checks whether the NVIDIA GPU driver is used based on whether the kernel -// module is loaded. -static inline bool is_using_nvidia_driver() { - struct ::stat buffer {}; - if (::stat("/sys/module/nvidia", &buffer) != 0) { - return false; - } - return S_ISDIR(buffer.st_mode); -} - -// Checks whether the windowing system is Wayland. -static inline bool is_wayland_display() { - if (!get_env("WAYLAND_DISPLAY").empty()) { - return true; - } - if (get_env("XDG_SESSION_TYPE") == "wayland") { - return true; - } - if (get_env("DESKTOP_SESSION").find("wayland") != std::string::npos) { - return true; - } - return false; -} - -// Checks whether the GDK X11 backend is used. -// See: https://docs.gtk.org/gdk3/class.DisplayManager.html -static inline bool is_gdk_x11_backend() { -#ifdef GDK_WINDOWING_X11 - auto *manager = gdk_display_manager_get(); - auto *display = gdk_display_manager_get_default_display(manager); - return GDK_IS_X11_DISPLAY(display); // NOLINT(misc-const-correctness) -#else - return false; -#endif -} - -// Checks whether WebKit is affected by bug when using DMA-BUF renderer. -// Returns true if all of the following conditions are met: -// - WebKit version is >= 2.42 (please narrow this down when there's a fix). -// - Environment variables are empty or not set: -// - WEBKIT_DISABLE_DMABUF_RENDERER -// - Windowing system is not Wayland. -// - GDK backend is X11. -// - NVIDIA GPU driver is used. -static inline bool is_webkit_dmabuf_bugged() { - auto wk_major = webkit_get_major_version(); - auto wk_minor = webkit_get_minor_version(); - // TODO: Narrow down affected WebKit version when there's a fixed version - auto is_affected_wk_version = wk_major == 2 && wk_minor >= 42; - if (!is_affected_wk_version) { - return false; - } - if (!get_env("WEBKIT_DISABLE_DMABUF_RENDERER").empty()) { - return false; - } - if (is_wayland_display()) { - return false; - } - if (!is_gdk_x11_backend()) { - return false; - } - if (!is_using_nvidia_driver()) { - return false; - } - return true; -} - -// Applies workaround for WebKit DMA-BUF bug if needed. -// See WebKit bug: https://bugs.webkit.org/show_bug.cgi?id=261874 -static inline void apply_webkit_dmabuf_workaround() { - if (!is_webkit_dmabuf_bugged()) { - return; - } - set_env("WEBKIT_DISABLE_DMABUF_RENDERER", "1"); -} -} // namespace webkit_dmabuf - -namespace webkit_symbols { -using webkit_web_view_evaluate_javascript_t = - void (*)(WebKitWebView *, const char *, gssize, const char *, const char *, - GCancellable *, GAsyncReadyCallback, gpointer); - -using webkit_web_view_run_javascript_t = void (*)(WebKitWebView *, - const gchar *, GCancellable *, - GAsyncReadyCallback, - gpointer); - -constexpr auto webkit_web_view_evaluate_javascript = - library_symbol( - "webkit_web_view_evaluate_javascript"); -constexpr auto webkit_web_view_run_javascript = - library_symbol( - "webkit_web_view_run_javascript"); -} // namespace webkit_symbols - -class gtk_webkit_engine : public engine_base { -public: - gtk_webkit_engine(bool debug, void *window) - : m_owns_window{!window}, m_window(static_cast(window)) { - if (m_owns_window) { - if (gtk_init_check(nullptr, nullptr) == FALSE) { - return; - } - m_window = gtk_window_new(GTK_WINDOW_TOPLEVEL); - on_window_created(); - g_signal_connect(G_OBJECT(m_window), "destroy", - G_CALLBACK(+[](GtkWidget *, gpointer arg) { - auto *w = static_cast(arg); - // Widget destroyed along with window. - w->m_webview = nullptr; - w->m_window = nullptr; - w->on_window_destroyed(); - }), - this); - } - webkit_dmabuf::apply_webkit_dmabuf_workaround(); - // Initialize webview widget - m_webview = webkit_web_view_new(); - WebKitUserContentManager *manager = - webkit_web_view_get_user_content_manager(WEBKIT_WEB_VIEW(m_webview)); - g_signal_connect(manager, "script-message-received::external", - G_CALLBACK(+[](WebKitUserContentManager *, - WebKitJavascriptResult *r, gpointer arg) { - auto *w = static_cast(arg); - char *s = get_string_from_js_result(r); - w->on_message(s); - g_free(s); - }), - this); - webkit_user_content_manager_register_script_message_handler(manager, - "external"); - init("window.external={invoke:function(s){window.webkit.messageHandlers." - "external.postMessage(s);}}"); - - gtk_container_add(GTK_CONTAINER(m_window), GTK_WIDGET(m_webview)); - gtk_widget_show(GTK_WIDGET(m_webview)); - - WebKitSettings *settings = - webkit_web_view_get_settings(WEBKIT_WEB_VIEW(m_webview)); - webkit_settings_set_javascript_can_access_clipboard(settings, true); - if (debug) { - webkit_settings_set_enable_write_console_messages_to_stdout(settings, - true); - webkit_settings_set_enable_developer_extras(settings, true); - } - - if (m_owns_window) { - gtk_widget_grab_focus(GTK_WIDGET(m_webview)); - gtk_widget_show_all(m_window); - } - } - - gtk_webkit_engine(const gtk_webkit_engine &) = delete; - gtk_webkit_engine &operator=(const gtk_webkit_engine &) = delete; - gtk_webkit_engine(gtk_webkit_engine &&) = delete; - gtk_webkit_engine &operator=(gtk_webkit_engine &&) = delete; - - virtual ~gtk_webkit_engine() { - if (m_webview) { - gtk_widget_destroy(GTK_WIDGET(m_webview)); - m_webview = nullptr; - } - if (m_window) { - if (m_owns_window) { - // Disconnect handlers to avoid callbacks invoked during destruction. - g_signal_handlers_disconnect_by_data(GTK_WINDOW(m_window), this); - gtk_window_close(GTK_WINDOW(m_window)); - on_window_destroyed(true); - } - m_window = nullptr; - } - if (m_owns_window) { - // Needed for the window to close immediately. - deplete_run_loop_event_queue(); - } - } - - void *window_impl() override { return (void *)m_window; } - void *widget_impl() override { return (void *)m_webview; } - void *browser_controller_impl() override { return (void *)m_webview; }; - void run_impl() override { gtk_main(); } - void terminate_impl() override { - dispatch_impl([] { gtk_main_quit(); }); - } - void dispatch_impl(std::function f) override { - g_idle_add_full(G_PRIORITY_HIGH_IDLE, (GSourceFunc)([](void *f) -> int { - (*static_cast(f))(); - return G_SOURCE_REMOVE; - }), - new std::function(f), - [](void *f) { delete static_cast(f); }); - } - - void set_title_impl(const std::string &title) override { - gtk_window_set_title(GTK_WINDOW(m_window), title.c_str()); - } - - void set_size_impl(int width, int height, webview_hint_t hints) override { - gtk_window_set_resizable(GTK_WINDOW(m_window), hints != WEBVIEW_HINT_FIXED); - if (hints == WEBVIEW_HINT_NONE) { - gtk_window_resize(GTK_WINDOW(m_window), width, height); - } else if (hints == WEBVIEW_HINT_FIXED) { - gtk_widget_set_size_request(m_window, width, height); - } else { - GdkGeometry g; - g.min_width = g.max_width = width; - g.min_height = g.max_height = height; - GdkWindowHints h = - (hints == WEBVIEW_HINT_MIN ? GDK_HINT_MIN_SIZE : GDK_HINT_MAX_SIZE); - // This defines either MIN_SIZE, or MAX_SIZE, but not both: - gtk_window_set_geometry_hints(GTK_WINDOW(m_window), nullptr, &g, h); - } - } - - void navigate_impl(const std::string &url) override { - webkit_web_view_load_uri(WEBKIT_WEB_VIEW(m_webview), url.c_str()); - } - - void set_html_impl(const std::string &html) override { - webkit_web_view_load_html(WEBKIT_WEB_VIEW(m_webview), html.c_str(), - nullptr); - } - - void init_impl(const std::string &js) override { - WebKitUserContentManager *manager = - webkit_web_view_get_user_content_manager(WEBKIT_WEB_VIEW(m_webview)); - webkit_user_content_manager_add_script( - manager, - webkit_user_script_new(js.c_str(), WEBKIT_USER_CONTENT_INJECT_TOP_FRAME, - WEBKIT_USER_SCRIPT_INJECT_AT_DOCUMENT_START, - nullptr, nullptr)); - } - - void eval_impl(const std::string &js) override { - auto &lib = get_webkit_library(); - auto wkmajor = webkit_get_major_version(); - auto wkminor = webkit_get_minor_version(); - if ((wkmajor == 2 && wkminor >= 40) || wkmajor > 2) { - if (auto fn = - lib.get(webkit_symbols::webkit_web_view_evaluate_javascript)) { - fn(WEBKIT_WEB_VIEW(m_webview), js.c_str(), - static_cast(js.size()), nullptr, nullptr, nullptr, nullptr, - nullptr); - } - } else if (auto fn = - lib.get(webkit_symbols::webkit_web_view_run_javascript)) { - fn(WEBKIT_WEB_VIEW(m_webview), js.c_str(), nullptr, nullptr, nullptr); - } - } - -private: - static char *get_string_from_js_result(WebKitJavascriptResult *r) { - char *s; -#if (WEBKIT_MAJOR_VERSION == 2 && WEBKIT_MINOR_VERSION >= 22) || \ - WEBKIT_MAJOR_VERSION > 2 - JSCValue *value = webkit_javascript_result_get_js_value(r); - s = jsc_value_to_string(value); -#else - JSGlobalContextRef ctx = webkit_javascript_result_get_global_context(r); - JSValueRef value = webkit_javascript_result_get_value(r); - JSStringRef js = JSValueToStringCopy(ctx, value, nullptr); - size_t n = JSStringGetMaximumUTF8CStringSize(js); - s = g_new(char, n); - JSStringGetUTF8CString(js, s, n); - JSStringRelease(js); -#endif - return s; - } - - static const native_library &get_webkit_library() { - static const native_library non_loaded_lib; - static native_library loaded_lib; - - if (loaded_lib.is_loaded()) { - return loaded_lib; - } - - constexpr std::array lib_names{"libwebkit2gtk-4.1.so", - "libwebkit2gtk-4.0.so"}; - auto found = - std::find_if(lib_names.begin(), lib_names.end(), [](const char *name) { - return native_library::is_loaded(name); - }); - - if (found == lib_names.end()) { - return non_loaded_lib; - } - - loaded_lib = native_library(*found); - - auto loaded = loaded_lib.is_loaded(); - if (!loaded) { - return non_loaded_lib; - } - - return loaded_lib; - } - - // Blocks while depleting the run loop of events. - void deplete_run_loop_event_queue() { - bool done{}; - dispatch([&] { done = true; }); - while (!done) { - gtk_main_iteration(); - } - } - - bool m_owns_window{}; - GtkWidget *m_window{}; - GtkWidget *m_webview{}; -}; - -} // namespace detail - -using browser_engine = detail::gtk_webkit_engine; - -} // namespace webview - -#elif defined(WEBVIEW_COCOA) - -// -// ==================================================================== -// -// This implementation uses Cocoa WKWebView backend on macOS. It is -// written using ObjC runtime and uses WKWebView class as a browser runtime. -// You should pass "-framework Webkit" flag to the compiler. -// -// ==================================================================== -// - -#include -#include -#include - -namespace webview { -namespace detail { -namespace objc { - -// A convenient template function for unconditionally casting the specified -// C-like function into a function that can be called with the given return -// type and arguments. Caller takes full responsibility for ensuring that -// the function call is valid. It is assumed that the function will not -// throw exceptions. -template -Result invoke(Callable callable, Args... args) noexcept { - return reinterpret_cast(callable)(args...); -} - -// Calls objc_msgSend. -template -Result msg_send(Args... args) noexcept { - return invoke(objc_msgSend, args...); -} - -// Wrapper around NSAutoreleasePool that drains the pool on destruction. -class autoreleasepool { -public: - autoreleasepool() - : m_pool(msg_send(objc_getClass("NSAutoreleasePool"), - sel_registerName("new"))) {} - - ~autoreleasepool() { - if (m_pool) { - msg_send(m_pool, sel_registerName("drain")); - } - } - - autoreleasepool(const autoreleasepool &) = delete; - autoreleasepool &operator=(const autoreleasepool &) = delete; - autoreleasepool(autoreleasepool &&) = delete; - autoreleasepool &operator=(autoreleasepool &&) = delete; - -private: - id m_pool{}; -}; - -inline id autoreleased(id object) { - msg_send(object, sel_registerName("autorelease")); - return object; -} - -} // namespace objc - -enum NSBackingStoreType : NSUInteger { NSBackingStoreBuffered = 2 }; - -enum NSWindowStyleMask : NSUInteger { - NSWindowStyleMaskTitled = 1, - NSWindowStyleMaskClosable = 2, - NSWindowStyleMaskMiniaturizable = 4, - NSWindowStyleMaskResizable = 8 -}; - -enum NSApplicationActivationPolicy : NSInteger { - NSApplicationActivationPolicyRegular = 0 -}; - -enum WKUserScriptInjectionTime : NSInteger { - WKUserScriptInjectionTimeAtDocumentStart = 0 -}; - -enum NSModalResponse : NSInteger { NSModalResponseOK = 1 }; - -// Convenient conversion of string literals. -inline id operator"" _cls(const char *s, std::size_t) { - return (id)objc_getClass(s); -} -inline SEL operator"" _sel(const char *s, std::size_t) { - return sel_registerName(s); -} -inline id operator"" _str(const char *s, std::size_t) { - return objc::msg_send("NSString"_cls, "stringWithUTF8String:"_sel, s); -} - -class cocoa_wkwebview_engine : public engine_base { -public: - cocoa_wkwebview_engine(bool debug, void *window) - : m_debug{debug}, m_window{static_cast(window)}, m_owns_window{ - !window} { - auto app = get_shared_application(); - // See comments related to application lifecycle in create_app_delegate(). - if (!m_owns_window) { - set_up_window(); - } else { - // Only set the app delegate if it hasn't already been set. - auto delegate = objc::msg_send(app, "delegate"_sel); - if (delegate) { - set_up_window(); - } else { - m_app_delegate = create_app_delegate(); - objc_setAssociatedObject(m_app_delegate, "webview", (id)this, - OBJC_ASSOCIATION_ASSIGN); - objc::msg_send(app, "setDelegate:"_sel, m_app_delegate); - - // Start the main run loop so that the app delegate gets the - // NSApplicationDidFinishLaunchingNotification notification after the run - // loop has started in order to perform further initialization. - // We need to return from this constructor so this run loop is only - // temporary. - // Skip the main loop if this isn't the first instance of this class - // because the launch event is only sent once. Instead, proceed to - // create a window. - if (get_and_set_is_first_instance()) { - objc::msg_send(app, "run"_sel); - } else { - set_up_window(); - } - } - } - } - - cocoa_wkwebview_engine(const cocoa_wkwebview_engine &) = delete; - cocoa_wkwebview_engine &operator=(const cocoa_wkwebview_engine &) = delete; - cocoa_wkwebview_engine(cocoa_wkwebview_engine &&) = delete; - cocoa_wkwebview_engine &operator=(cocoa_wkwebview_engine &&) = delete; - - virtual ~cocoa_wkwebview_engine() { - objc::autoreleasepool arp; - if (m_window) { - if (m_webview) { - if (m_webview == objc::msg_send(m_window, "contentView"_sel)) { - objc::msg_send(m_window, "setContentView:"_sel, nullptr); - } - objc::msg_send(m_webview, "release"_sel); - m_webview = nullptr; - } - if (m_owns_window) { - // Replace delegate to avoid callbacks and other bad things during - // destruction. - objc::msg_send(m_window, "setDelegate:"_sel, nullptr); - objc::msg_send(m_window, "close"_sel); - on_window_destroyed(true); - } - m_window = nullptr; - } - if (m_window_delegate) { - objc::msg_send(m_window_delegate, "release"_sel); - m_window_delegate = nullptr; - } - if (m_app_delegate) { - auto app = get_shared_application(); - objc::msg_send(app, "setDelegate:"_sel, nullptr); - // Make sure to release the delegate we created. - objc::msg_send(m_app_delegate, "release"_sel); - m_app_delegate = nullptr; - } - if (m_owns_window) { - // Needed for the window to close immediately. - deplete_run_loop_event_queue(); - } - // TODO: Figure out why m_manager is still alive after the autoreleasepool - // has been drained. - } - - void *window_impl() override { return (void *)m_window; } - void *widget_impl() override { return (void *)m_webview; } - void *browser_controller_impl() override { return (void *)m_webview; }; - void terminate_impl() override { stop_run_loop(); } - void run_impl() override { - auto app = get_shared_application(); - objc::msg_send(app, "run"_sel); - } - void dispatch_impl(std::function f) override { - dispatch_async_f(dispatch_get_main_queue(), new dispatch_fn_t(f), - (dispatch_function_t)([](void *arg) { - auto f = static_cast(arg); - (*f)(); - delete f; - })); - } - void set_title_impl(const std::string &title) override { - objc::autoreleasepool arp; - - objc::msg_send(m_window, "setTitle:"_sel, - objc::msg_send("NSString"_cls, - "stringWithUTF8String:"_sel, - title.c_str())); - } - void set_size_impl(int width, int height, webview_hint_t hints) override { - objc::autoreleasepool arp; - - auto style = static_cast( - NSWindowStyleMaskTitled | NSWindowStyleMaskClosable | - NSWindowStyleMaskMiniaturizable); - if (hints != WEBVIEW_HINT_FIXED) { - style = - static_cast(style | NSWindowStyleMaskResizable); - } - objc::msg_send(m_window, "setStyleMask:"_sel, style); - - if (hints == WEBVIEW_HINT_MIN) { - objc::msg_send(m_window, "setContentMinSize:"_sel, - CGSizeMake(width, height)); - } else if (hints == WEBVIEW_HINT_MAX) { - objc::msg_send(m_window, "setContentMaxSize:"_sel, - CGSizeMake(width, height)); - } else { - objc::msg_send(m_window, "setFrame:display:animate:"_sel, - CGRectMake(0, 0, width, height), YES, NO); - } - objc::msg_send(m_window, "center"_sel); - } - void navigate_impl(const std::string &url) override { - objc::autoreleasepool arp; - - auto nsurl = objc::msg_send( - "NSURL"_cls, "URLWithString:"_sel, - objc::msg_send("NSString"_cls, "stringWithUTF8String:"_sel, - url.c_str())); - - objc::msg_send( - m_webview, "loadRequest:"_sel, - objc::msg_send("NSURLRequest"_cls, "requestWithURL:"_sel, nsurl)); - } - void set_html_impl(const std::string &html) override { - objc::autoreleasepool arp; - objc::msg_send(m_webview, "loadHTMLString:baseURL:"_sel, - objc::msg_send("NSString"_cls, - "stringWithUTF8String:"_sel, - html.c_str()), - nullptr); - } - void init_impl(const std::string &js) override { - objc::autoreleasepool arp; - auto script = objc::autoreleased(objc::msg_send( - objc::msg_send("WKUserScript"_cls, "alloc"_sel), - "initWithSource:injectionTime:forMainFrameOnly:"_sel, - objc::msg_send("NSString"_cls, "stringWithUTF8String:"_sel, - js.c_str()), - WKUserScriptInjectionTimeAtDocumentStart, YES)); - objc::msg_send(m_manager, "addUserScript:"_sel, script); - } - void eval_impl(const std::string &js) override { - objc::autoreleasepool arp; - objc::msg_send(m_webview, "evaluateJavaScript:completionHandler:"_sel, - objc::msg_send("NSString"_cls, - "stringWithUTF8String:"_sel, - js.c_str()), - nullptr); - } - -private: - id create_app_delegate() { - objc::autoreleasepool arp; - constexpr auto class_name = "WebviewAppDelegate"; - // Avoid crash due to registering same class twice - auto cls = objc_lookUpClass(class_name); - if (!cls) { - // Note: Avoid registering the class name "AppDelegate" as it is the - // default name in projects created with Xcode, and using the same name - // causes objc_registerClassPair to crash. - cls = objc_allocateClassPair((Class) "NSResponder"_cls, class_name, 0); - class_addProtocol(cls, objc_getProtocol("NSTouchBarProvider")); - class_addMethod(cls, - "applicationShouldTerminateAfterLastWindowClosed:"_sel, - (IMP)(+[](id, SEL, id) -> BOOL { return NO; }), "c@:@"); - class_addMethod(cls, "applicationDidFinishLaunching:"_sel, - (IMP)(+[](id self, SEL, id notification) { - auto app = - objc::msg_send(notification, "object"_sel); - auto w = get_associated_webview(self); - w->on_application_did_finish_launching(self, app); - }), - "v@:@"); - objc_registerClassPair(cls); - } - return objc::msg_send((id)cls, "new"_sel); - } - id create_script_message_handler() { - objc::autoreleasepool arp; - constexpr auto class_name = "WebviewWKScriptMessageHandler"; - // Avoid crash due to registering same class twice - auto cls = objc_lookUpClass(class_name); - if (!cls) { - cls = objc_allocateClassPair((Class) "NSResponder"_cls, class_name, 0); - class_addProtocol(cls, objc_getProtocol("WKScriptMessageHandler")); - class_addMethod( - cls, "userContentController:didReceiveScriptMessage:"_sel, - (IMP)(+[](id self, SEL, id, id msg) { - auto w = get_associated_webview(self); - w->on_message(objc::msg_send( - objc::msg_send(msg, "body"_sel), "UTF8String"_sel)); - }), - "v@:@@"); - objc_registerClassPair(cls); - } - auto instance = objc::msg_send((id)cls, "new"_sel); - objc_setAssociatedObject(instance, "webview", (id)this, - OBJC_ASSOCIATION_ASSIGN); - return instance; - } - static id create_webkit_ui_delegate() { - objc::autoreleasepool arp; - constexpr auto class_name = "WebviewWKUIDelegate"; - // Avoid crash due to registering same class twice - auto cls = objc_lookUpClass(class_name); - if (!cls) { - cls = objc_allocateClassPair((Class) "NSObject"_cls, class_name, 0); - class_addProtocol(cls, objc_getProtocol("WKUIDelegate")); - class_addMethod( - cls, - "webView:runOpenPanelWithParameters:initiatedByFrame:completionHandler:"_sel, - (IMP)(+[](id, SEL, id, id parameters, id, id completion_handler) { - auto allows_multiple_selection = - objc::msg_send(parameters, "allowsMultipleSelection"_sel); - auto allows_directories = - objc::msg_send(parameters, "allowsDirectories"_sel); - - // Show a panel for selecting files. - auto panel = objc::msg_send("NSOpenPanel"_cls, "openPanel"_sel); - objc::msg_send(panel, "setCanChooseFiles:"_sel, YES); - objc::msg_send(panel, "setCanChooseDirectories:"_sel, - allows_directories); - objc::msg_send(panel, "setAllowsMultipleSelection:"_sel, - allows_multiple_selection); - auto modal_response = - objc::msg_send(panel, "runModal"_sel); - - // Get the URLs for the selected files. If the modal was canceled - // then we pass null to the completion handler to signify - // cancellation. - id urls = modal_response == NSModalResponseOK - ? objc::msg_send(panel, "URLs"_sel) - : nullptr; - - // Invoke the completion handler block. - auto sig = objc::msg_send( - "NSMethodSignature"_cls, "signatureWithObjCTypes:"_sel, "v@?@"); - auto invocation = objc::msg_send( - "NSInvocation"_cls, "invocationWithMethodSignature:"_sel, sig); - objc::msg_send(invocation, "setTarget:"_sel, - completion_handler); - objc::msg_send(invocation, "setArgument:atIndex:"_sel, &urls, - 1); - objc::msg_send(invocation, "invoke"_sel); - }), - "v@:@@@@"); - objc_registerClassPair(cls); - } - return objc::msg_send((id)cls, "new"_sel); - } - static id create_window_delegate() { - objc::autoreleasepool arp; - constexpr auto class_name = "WebviewNSWindowDelegate"; - // Avoid crash due to registering same class twice - auto cls = objc_lookUpClass(class_name); - if (!cls) { - cls = objc_allocateClassPair((Class) "NSObject"_cls, class_name, 0); - class_addProtocol(cls, objc_getProtocol("NSWindowDelegate")); - class_addMethod(cls, "windowWillClose:"_sel, - (IMP)(+[](id self, SEL, id notification) { - auto window = - objc::msg_send(notification, "object"_sel); - auto w = get_associated_webview(self); - w->on_window_will_close(self, window); - }), - "v@:@"); - objc_registerClassPair(cls); - } - return objc::msg_send((id)cls, "new"_sel); - } - static id get_shared_application() { - return objc::msg_send("NSApplication"_cls, "sharedApplication"_sel); - } - static cocoa_wkwebview_engine *get_associated_webview(id object) { - auto w = - (cocoa_wkwebview_engine *)objc_getAssociatedObject(object, "webview"); - assert(w); - return w; - } - static id get_main_bundle() noexcept { - return objc::msg_send("NSBundle"_cls, "mainBundle"_sel); - } - static bool is_app_bundled() noexcept { - auto bundle = get_main_bundle(); - if (!bundle) { - return false; - } - auto bundle_path = objc::msg_send(bundle, "bundlePath"_sel); - auto bundled = - objc::msg_send(bundle_path, "hasSuffix:"_sel, ".app"_str); - return !!bundled; - } - void on_application_did_finish_launching(id /*delegate*/, id app) { - // See comments related to application lifecycle in create_app_delegate(). - if (m_owns_window) { - // Stop the main run loop so that we can return - // from the constructor. - stop_run_loop(); - } - - // Activate the app if it is not bundled. - // Bundled apps launched from Finder are activated automatically but - // otherwise not. Activating the app even when it has been launched from - // Finder does not seem to be harmful but calling this function is rarely - // needed as proper activation is normally taken care of for us. - // Bundled apps have a default activation policy of - // NSApplicationActivationPolicyRegular while non-bundled apps have a - // default activation policy of NSApplicationActivationPolicyProhibited. - if (!is_app_bundled()) { - // "setActivationPolicy:" must be invoked before - // "activateIgnoringOtherApps:" for activation to work. - objc::msg_send(app, "setActivationPolicy:"_sel, - NSApplicationActivationPolicyRegular); - // Activate the app regardless of other active apps. - // This can be obtrusive so we only do it when necessary. - objc::msg_send(app, "activateIgnoringOtherApps:"_sel, YES); - } - - set_up_window(); - } - void on_window_will_close(id /*delegate*/, id /*window*/) { - // Widget destroyed along with window. - m_webview = nullptr; - m_window = nullptr; - dispatch([this] { on_window_destroyed(); }); - } - void set_up_window() { - objc::autoreleasepool arp; - - // Main window - if (m_owns_window) { - m_window = objc::msg_send("NSWindow"_cls, "alloc"_sel); - auto style = NSWindowStyleMaskTitled; - m_window = objc::msg_send( - m_window, "initWithContentRect:styleMask:backing:defer:"_sel, - CGRectMake(0, 0, 0, 0), style, NSBackingStoreBuffered, NO); - - m_window_delegate = create_window_delegate(); - objc_setAssociatedObject(m_window_delegate, "webview", (id)this, - OBJC_ASSOCIATION_ASSIGN); - objc::msg_send(m_window, "setDelegate:"_sel, m_window_delegate); - - on_window_created(); - } - - set_up_web_view(); - - objc::msg_send(m_window, "setContentView:"_sel, m_webview); - - if (m_owns_window) { - objc::msg_send(m_window, "makeKeyAndOrderFront:"_sel, nullptr); - } - } - void set_up_web_view() { - objc::autoreleasepool arp; - - auto config = objc::autoreleased( - objc::msg_send("WKWebViewConfiguration"_cls, "new"_sel)); - - m_manager = objc::msg_send(config, "userContentController"_sel); - m_webview = objc::msg_send("WKWebView"_cls, "alloc"_sel); - - auto preferences = objc::msg_send(config, "preferences"_sel); - auto yes_value = - objc::msg_send("NSNumber"_cls, "numberWithBool:"_sel, YES); - - if (m_debug) { - // Equivalent Obj-C: - // [[config preferences] setValue:@YES forKey:@"developerExtrasEnabled"]; - objc::msg_send(preferences, "setValue:forKey:"_sel, yes_value, - "developerExtrasEnabled"_str); - } - - // Equivalent Obj-C: - // [[config preferences] setValue:@YES forKey:@"fullScreenEnabled"]; - objc::msg_send(preferences, "setValue:forKey:"_sel, yes_value, - "fullScreenEnabled"_str); - - // Equivalent Obj-C: - // [[config preferences] setValue:@YES forKey:@"javaScriptCanAccessClipboard"]; - objc::msg_send(preferences, "setValue:forKey:"_sel, yes_value, - "javaScriptCanAccessClipboard"_str); - - // Equivalent Obj-C: - // [[config preferences] setValue:@YES forKey:@"DOMPasteAllowed"]; - objc::msg_send(preferences, "setValue:forKey:"_sel, yes_value, - "DOMPasteAllowed"_str); - - auto ui_delegate = objc::autoreleased(create_webkit_ui_delegate()); - objc::msg_send(m_webview, "initWithFrame:configuration:"_sel, - CGRectMake(0, 0, 0, 0), config); - objc::msg_send(m_webview, "setUIDelegate:"_sel, ui_delegate); - - if (m_debug) { - // Explicitly make WKWebView inspectable via Safari on OS versions that - // disable the feature by default (macOS 13.3 and later) and support - // enabling it. According to Apple, the behavior on older OS versions is - // for content to always be inspectable in "debug builds". - // Testing shows that this is true for macOS 12.6 but somehow not 10.15. - // https://webkit.org/blog/13936/enabling-the-inspection-of-web-content-in-apps/ -#if defined(__has_builtin) -#if __has_builtin(__builtin_available) - if (__builtin_available(macOS 13.3, iOS 16.4, tvOS 16.4, *)) { - objc::msg_send( - m_webview, "setInspectable:"_sel, - objc::msg_send("NSNumber"_cls, "numberWithBool:"_sel, YES)); - } -#else -#error __builtin_available not supported by compiler -#endif -#else -#error __has_builtin not supported by compiler -#endif - } - - auto script_message_handler = - objc::autoreleased(create_script_message_handler()); - objc::msg_send(m_manager, "addScriptMessageHandler:name:"_sel, - script_message_handler, "external"_str); - - init(R""( - window.external = { - invoke: function(s) { - window.webkit.messageHandlers.external.postMessage(s); - }, - }; - )""); - } - void stop_run_loop() { - objc::autoreleasepool arp; - auto app = get_shared_application(); - // Request the run loop to stop. This doesn't immediately stop the loop. - objc::msg_send(app, "stop:"_sel, nullptr); - // The run loop will stop after processing an NSEvent. - // Event type: NSEventTypeApplicationDefined (macOS 10.12+), - // NSApplicationDefined (macOS 10.0–10.12) - int type = 15; - auto event = objc::msg_send( - "NSEvent"_cls, - "otherEventWithType:location:modifierFlags:timestamp:windowNumber:context:subtype:data1:data2:"_sel, - type, CGPointMake(0, 0), 0, 0, 0, nullptr, 0, 0, 0); - objc::msg_send(app, "postEvent:atStart:"_sel, event, YES); - } - static bool get_and_set_is_first_instance() noexcept { - static std::atomic_bool first{true}; - bool temp = first; - if (temp) { - first = false; - } - return temp; - } - - // Blocks while depleting the run loop of events. - void deplete_run_loop_event_queue() { - objc::autoreleasepool arp; - auto app = get_shared_application(); - bool done{}; - dispatch([&] { done = true; }); - auto mask = NSUIntegerMax; // NSEventMaskAny - // NSDefaultRunLoopMode - auto mode = objc::msg_send("NSString"_cls, "stringWithUTF8String:"_sel, - "kCFRunLoopDefaultMode"); - while (!done) { - objc::autoreleasepool arp; - auto event = objc::msg_send( - app, "nextEventMatchingMask:untilDate:inMode:dequeue:"_sel, mask, - nullptr, mode, YES); - if (event) { - objc::msg_send(app, "sendEvent:"_sel, event); - } - } - } - - bool m_debug{}; - id m_app_delegate{}; - id m_window_delegate{}; - id m_window{}; - id m_webview{}; - id m_manager{}; - bool m_owns_window{}; -}; - -} // namespace detail - -using browser_engine = detail::cocoa_wkwebview_engine; - -} // namespace webview - -#elif defined(WEBVIEW_EDGE) - -// -// ==================================================================== -// -// This implementation uses Win32 API to create a native window. It -// uses Edge/Chromium webview2 backend as a browser engine. -// -// ==================================================================== -// - -#define WIN32_LEAN_AND_MEAN -#include -#include -#include -#include - -#include "WebView2.h" - -#ifdef _MSC_VER -#pragma comment(lib, "advapi32.lib") -#pragma comment(lib, "ole32.lib") -#pragma comment(lib, "shell32.lib") -#pragma comment(lib, "shlwapi.lib") -#pragma comment(lib, "user32.lib") -#pragma comment(lib, "version.lib") -#endif - -namespace webview { -namespace detail { - -using msg_cb_t = std::function; - -// Parses a version string with 1-4 integral components, e.g. "1.2.3.4". -// Missing or invalid components default to 0, and excess components are ignored. -template -std::array -parse_version(const std::basic_string &version) noexcept { - auto parse_component = [](auto sb, auto se) -> unsigned int { - try { - auto n = std::stol(std::basic_string(sb, se)); - return n < 0 ? 0 : n; - } catch (std::exception &) { - return 0; - } - }; - auto end = version.end(); - auto sb = version.begin(); // subrange begin - auto se = sb; // subrange end - unsigned int ci = 0; // component index - std::array components{}; - while (sb != end && se != end && ci < components.size()) { - if (*se == static_cast('.')) { - components[ci++] = parse_component(sb, se); - sb = ++se; - continue; - } - ++se; - } - if (sb < se && ci < components.size()) { - components[ci] = parse_component(sb, se); - } - return components; -} - -template -auto parse_version(const T (&version)[Length]) noexcept { - return parse_version(std::basic_string(version, Length)); -} - -std::wstring get_file_version_string(const std::wstring &file_path) noexcept { - DWORD dummy_handle; // Unused - DWORD info_buffer_length = - GetFileVersionInfoSizeW(file_path.c_str(), &dummy_handle); - if (info_buffer_length == 0) { - return std::wstring(); - } - std::vector info_buffer; - info_buffer.reserve(info_buffer_length); - if (!GetFileVersionInfoW(file_path.c_str(), 0, info_buffer_length, - info_buffer.data())) { - return std::wstring(); - } - auto sub_block = L"\\StringFileInfo\\040904B0\\ProductVersion"; - LPWSTR version = nullptr; - unsigned int version_length = 0; - if (!VerQueryValueW(info_buffer.data(), sub_block, - reinterpret_cast(&version), &version_length)) { - return std::wstring(); - } - if (!version || version_length == 0) { - return std::wstring(); - } - return std::wstring(version, version_length); -} - -// A wrapper around COM library initialization. Calls CoInitializeEx in the -// constructor and CoUninitialize in the destructor. -class com_init_wrapper { -public: - com_init_wrapper() = default; - - com_init_wrapper(DWORD dwCoInit) { - // We can safely continue as long as COM was either successfully - // initialized or already initialized. - // RPC_E_CHANGED_MODE means that CoInitializeEx was already called with - // a different concurrency model. - switch (CoInitializeEx(nullptr, dwCoInit)) { - case S_OK: - case S_FALSE: - m_initialized = true; - break; - } - } - - ~com_init_wrapper() { - if (m_initialized) { - CoUninitialize(); - m_initialized = false; - } - } - - com_init_wrapper(const com_init_wrapper &other) = delete; - com_init_wrapper &operator=(const com_init_wrapper &other) = delete; - com_init_wrapper(com_init_wrapper &&other) { *this = std::move(other); } - - com_init_wrapper &operator=(com_init_wrapper &&other) { - if (this == &other) { - return *this; - } - m_initialized = std::exchange(other.m_initialized, false); - return *this; - } - - bool is_initialized() const { return m_initialized; } - -private: - bool m_initialized = false; -}; - -namespace ntdll_symbols { -using RtlGetVersion_t = - unsigned int /*NTSTATUS*/ (WINAPI *)(RTL_OSVERSIONINFOW *); - -constexpr auto RtlGetVersion = library_symbol("RtlGetVersion"); -} // namespace ntdll_symbols - -namespace user32_symbols { -using DPI_AWARENESS_CONTEXT = HANDLE; -using SetProcessDpiAwarenessContext_t = BOOL(WINAPI *)(DPI_AWARENESS_CONTEXT); -using SetProcessDPIAware_t = BOOL(WINAPI *)(); -using GetDpiForWindow_t = UINT(WINAPI *)(HWND); -using EnableNonClientDpiScaling_t = BOOL(WINAPI *)(HWND); -using AdjustWindowRectExForDpi_t = BOOL(WINAPI *)(LPRECT, DWORD, BOOL, DWORD, - UINT); -using GetWindowDpiAwarenessContext_t = DPI_AWARENESS_CONTEXT(WINAPI *)(HWND); -using AreDpiAwarenessContextsEqual_t = BOOL(WINAPI *)(DPI_AWARENESS_CONTEXT, - DPI_AWARENESS_CONTEXT); - -// Use intptr_t as the underlying type because we need to -// reinterpret_cast which is a pointer. -// Available since Windows 10, version 1607 -enum class dpi_awareness : intptr_t { - per_monitor_v2_aware = -4, // Available since Windows 10, version 1703 - per_monitor_aware = -3 -}; - -constexpr auto SetProcessDpiAwarenessContext = - library_symbol( - "SetProcessDpiAwarenessContext"); -constexpr auto SetProcessDPIAware = - library_symbol("SetProcessDPIAware"); -constexpr auto GetDpiForWindow = - library_symbol("GetDpiForWindow"); -constexpr auto EnableNonClientDpiScaling = - library_symbol("EnableNonClientDpiScaling"); -constexpr auto AdjustWindowRectExForDpi = - library_symbol("AdjustWindowRectExForDpi"); -constexpr auto GetWindowDpiAwarenessContext = - library_symbol( - "GetWindowDpiAwarenessContext"); -constexpr auto AreDpiAwarenessContextsEqual = - library_symbol( - "AreDpiAwarenessContextsEqual"); -} // namespace user32_symbols - -namespace dwmapi_symbols { -typedef enum { - // This undocumented value is used instead of DWMWA_USE_IMMERSIVE_DARK_MODE - // on Windows 10 older than build 19041 (2004/20H1). - DWMWA_USE_IMMERSIVE_DARK_MODE_BEFORE_V10_0_19041 = 19, - // Documented as being supported since Windows 11 build 22000 (21H2) but it - // works since Windows 10 build 19041 (2004/20H1). - DWMWA_USE_IMMERSIVE_DARK_MODE = 20 -} DWMWINDOWATTRIBUTE; -using DwmSetWindowAttribute_t = HRESULT(WINAPI *)(HWND, DWORD, LPCVOID, DWORD); - -constexpr auto DwmSetWindowAttribute = - library_symbol("DwmSetWindowAttribute"); -} // namespace dwmapi_symbols - -namespace shcore_symbols { -typedef enum { PROCESS_PER_MONITOR_DPI_AWARE = 2 } PROCESS_DPI_AWARENESS; -using SetProcessDpiAwareness_t = HRESULT(WINAPI *)(PROCESS_DPI_AWARENESS); - -constexpr auto SetProcessDpiAwareness = - library_symbol("SetProcessDpiAwareness"); -} // namespace shcore_symbols - -class reg_key { -public: - explicit reg_key(HKEY root_key, const wchar_t *sub_key, DWORD options, - REGSAM sam_desired) { - HKEY handle; - auto status = - RegOpenKeyExW(root_key, sub_key, options, sam_desired, &handle); - if (status == ERROR_SUCCESS) { - m_handle = handle; - } - } - - explicit reg_key(HKEY root_key, const std::wstring &sub_key, DWORD options, - REGSAM sam_desired) - : reg_key(root_key, sub_key.c_str(), options, sam_desired) {} - - virtual ~reg_key() { - if (m_handle) { - RegCloseKey(m_handle); - m_handle = nullptr; - } - } - - reg_key(const reg_key &other) = delete; - reg_key &operator=(const reg_key &other) = delete; - reg_key(reg_key &&other) = delete; - reg_key &operator=(reg_key &&other) = delete; - - bool is_open() const { return !!m_handle; } - bool get_handle() const { return m_handle; } - - template - void query_bytes(const wchar_t *name, Container &result) const { - DWORD buf_length = 0; - // Get the size of the data in bytes. - auto status = RegQueryValueExW(m_handle, name, nullptr, nullptr, nullptr, - &buf_length); - if (status != ERROR_SUCCESS && status != ERROR_MORE_DATA) { - result.resize(0); - return; - } - // Read the data. - result.resize(buf_length / sizeof(typename Container::value_type)); - auto *buf = reinterpret_cast(&result[0]); - status = - RegQueryValueExW(m_handle, name, nullptr, nullptr, buf, &buf_length); - if (status != ERROR_SUCCESS) { - result.resize(0); - return; - } - } - - std::wstring query_string(const wchar_t *name) const { - std::wstring result; - query_bytes(name, result); - // Remove trailing null-characters. - for (std::size_t length = result.size(); length > 0; --length) { - if (result[length - 1] != 0) { - result.resize(length); - break; - } - } - return result; - } - - unsigned int query_uint(const wchar_t *name, - unsigned int default_value) const { - std::vector data; - query_bytes(name, data); - if (data.size() < sizeof(DWORD)) { - return default_value; - } - return static_cast(*reinterpret_cast(data.data())); - } - -private: - HKEY m_handle = nullptr; -}; - -// Compare the specified version against the OS version. -// Returns less than 0 if the OS version is less. -// Returns 0 if the versions are equal. -// Returns greater than 0 if the specified version is greater. -inline int compare_os_version(unsigned int major, unsigned int minor, - unsigned int build) { - // Use RtlGetVersion both to bypass potential issues related to - // VerifyVersionInfo and manifests, and because both GetVersion and - // GetVersionEx are deprecated. - auto ntdll = native_library(L"ntdll.dll"); - if (auto fn = ntdll.get(ntdll_symbols::RtlGetVersion)) { - RTL_OSVERSIONINFOW vi{}; - vi.dwOSVersionInfoSize = sizeof(vi); - if (fn(&vi) != 0) { - return false; - } - if (vi.dwMajorVersion == major) { - if (vi.dwMinorVersion == minor) { - return static_cast(vi.dwBuildNumber) - static_cast(build); - } - return static_cast(vi.dwMinorVersion) - static_cast(minor); - } - return static_cast(vi.dwMajorVersion) - static_cast(major); - } - return false; -} - -inline bool is_per_monitor_v2_awareness_available() { - // Windows 10, version 1703 - return compare_os_version(10, 0, 15063) >= 0; -} - -inline bool enable_dpi_awareness() { - auto user32 = native_library(L"user32.dll"); - if (auto fn = user32.get(user32_symbols::SetProcessDpiAwarenessContext)) { - auto dpi_awareness = - reinterpret_cast( - is_per_monitor_v2_awareness_available() - ? user32_symbols::dpi_awareness::per_monitor_v2_aware - : user32_symbols::dpi_awareness::per_monitor_aware); - if (fn(dpi_awareness)) { - return true; - } - return GetLastError() == ERROR_ACCESS_DENIED; - } - if (auto shcore = native_library(L"shcore.dll")) { - if (auto fn = shcore.get(shcore_symbols::SetProcessDpiAwareness)) { - auto result = fn(shcore_symbols::PROCESS_PER_MONITOR_DPI_AWARE); - return result == S_OK || result == E_ACCESSDENIED; - } - } - if (auto fn = user32.get(user32_symbols::SetProcessDPIAware)) { - return !!fn(); - } - return true; -} - -inline bool enable_non_client_dpi_scaling_if_needed(HWND window) { - auto user32 = native_library(L"user32.dll"); - auto get_ctx_fn = user32.get(user32_symbols::GetWindowDpiAwarenessContext); - if (!get_ctx_fn) { - return true; - } - auto awareness = get_ctx_fn(window); - if (!awareness) { - return false; - } - auto ctx_equal_fn = user32.get(user32_symbols::AreDpiAwarenessContextsEqual); - if (!ctx_equal_fn) { - return true; - } - // EnableNonClientDpiScaling is only needed with per monitor v1 awareness. - auto per_monitor = reinterpret_cast( - user32_symbols::dpi_awareness::per_monitor_aware); - if (!ctx_equal_fn(awareness, per_monitor)) { - return true; - } - auto enable_fn = user32.get(user32_symbols::EnableNonClientDpiScaling); - if (!enable_fn) { - return true; - } - return !!enable_fn(window); -} - -constexpr int get_default_window_dpi() { - constexpr const int default_dpi = 96; // USER_DEFAULT_SCREEN_DPI - return default_dpi; -} - -inline int get_window_dpi(HWND window) { - auto user32 = native_library(L"user32.dll"); - if (auto fn = user32.get(user32_symbols::GetDpiForWindow)) { - auto dpi = static_cast(fn(window)); - return dpi; - } - return get_default_window_dpi(); -} - -constexpr int scale_value_for_dpi(int value, int from_dpi, int to_dpi) { - return (value * to_dpi) / from_dpi; -} - -constexpr SIZE scale_size(int width, int height, int from_dpi, int to_dpi) { - auto scaled_width = scale_value_for_dpi(width, from_dpi, to_dpi); - auto scaled_height = scale_value_for_dpi(height, from_dpi, to_dpi); - return {scaled_width, scaled_height}; -} - -inline SIZE make_window_frame_size(HWND window, int width, int height, - int dpi) { - auto style = GetWindowLong(window, GWL_STYLE); - RECT r{0, 0, width, height}; - auto user32 = native_library(L"user32.dll"); - if (auto fn = user32.get(user32_symbols::AdjustWindowRectExForDpi)) { - fn(&r, style, FALSE, 0, static_cast(dpi)); - } else { - AdjustWindowRect(&r, style, 0); - } - auto frame_width = r.right - r.left; - auto frame_height = r.bottom - r.top; - return {frame_width, frame_height}; -} - -inline bool is_dark_theme_enabled() { - constexpr auto *sub_key = - L"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Themes\\Personalize"; - reg_key key(HKEY_CURRENT_USER, sub_key, 0, KEY_READ); - if (!key.is_open()) { - // Default is light theme - return false; - } - return key.query_uint(L"AppsUseLightTheme", 1) == 0; -} - -inline void apply_window_theme(HWND window) { - auto dark_theme_enabled = is_dark_theme_enabled(); - - // Use "immersive dark mode" on systems that support it. - // Changes the color of the window's title bar (light or dark). - BOOL use_dark_mode{dark_theme_enabled ? TRUE : FALSE}; - static native_library dwmapi{L"dwmapi.dll"}; - if (auto fn = dwmapi.get(dwmapi_symbols::DwmSetWindowAttribute)) { - // Try the modern, documented attribute before the older, undocumented one. - if (fn(window, dwmapi_symbols::DWMWA_USE_IMMERSIVE_DARK_MODE, - &use_dark_mode, sizeof(use_dark_mode)) != S_OK) { - fn(window, - dwmapi_symbols::DWMWA_USE_IMMERSIVE_DARK_MODE_BEFORE_V10_0_19041, - &use_dark_mode, sizeof(use_dark_mode)); - } - } -} - -// Enable built-in WebView2Loader implementation by default. -#ifndef WEBVIEW_MSWEBVIEW2_BUILTIN_IMPL -#define WEBVIEW_MSWEBVIEW2_BUILTIN_IMPL 1 -#endif - -// Link WebView2Loader.dll explicitly by default only if the built-in -// implementation is enabled. -#ifndef WEBVIEW_MSWEBVIEW2_EXPLICIT_LINK -#define WEBVIEW_MSWEBVIEW2_EXPLICIT_LINK WEBVIEW_MSWEBVIEW2_BUILTIN_IMPL -#endif - -// Explicit linking of WebView2Loader.dll should be used along with -// the built-in implementation. -#if WEBVIEW_MSWEBVIEW2_BUILTIN_IMPL == 1 && \ - WEBVIEW_MSWEBVIEW2_EXPLICIT_LINK != 1 -#undef WEBVIEW_MSWEBVIEW2_EXPLICIT_LINK -#error Please set WEBVIEW_MSWEBVIEW2_EXPLICIT_LINK=1. -#endif - -#if WEBVIEW_MSWEBVIEW2_BUILTIN_IMPL == 1 -// Gets the last component of a Windows native file path. -// For example, if the path is "C:\a\b" then the result is "b". -template -std::basic_string -get_last_native_path_component(const std::basic_string &path) { - auto pos = path.find_last_of(static_cast('\\')); - if (pos != std::basic_string::npos) { - return path.substr(pos + 1); - } - return std::basic_string(); -} -#endif /* WEBVIEW_MSWEBVIEW2_BUILTIN_IMPL */ - -template struct cast_info_t { - using type = T; - IID iid; -}; - -namespace mswebview2 { -static constexpr IID - IID_ICoreWebView2CreateCoreWebView2ControllerCompletedHandler{ - 0x6C4819F3, - 0xC9B7, - 0x4260, - {0x81, 0x27, 0xC9, 0xF5, 0xBD, 0xE7, 0xF6, 0x8C}}; -static constexpr IID - IID_ICoreWebView2CreateCoreWebView2EnvironmentCompletedHandler{ - 0x4E8A3389, - 0xC9D8, - 0x4BD2, - {0xB6, 0xB5, 0x12, 0x4F, 0xEE, 0x6C, 0xC1, 0x4D}}; -static constexpr IID IID_ICoreWebView2PermissionRequestedEventHandler{ - 0x15E1C6A3, - 0xC72A, - 0x4DF3, - {0x91, 0xD7, 0xD0, 0x97, 0xFB, 0xEC, 0x6B, 0xFD}}; -static constexpr IID IID_ICoreWebView2WebMessageReceivedEventHandler{ - 0x57213F19, - 0x00E6, - 0x49FA, - {0x8E, 0x07, 0x89, 0x8E, 0xA0, 0x1E, 0xCB, 0xD2}}; - -#if WEBVIEW_MSWEBVIEW2_BUILTIN_IMPL == 1 -enum class webview2_runtime_type { installed = 0, embedded = 1 }; - -namespace webview2_symbols { -using CreateWebViewEnvironmentWithOptionsInternal_t = - HRESULT(STDMETHODCALLTYPE *)( - bool, webview2_runtime_type, PCWSTR, IUnknown *, - ICoreWebView2CreateCoreWebView2EnvironmentCompletedHandler *); -using DllCanUnloadNow_t = HRESULT(STDMETHODCALLTYPE *)(); - -static constexpr auto CreateWebViewEnvironmentWithOptionsInternal = - library_symbol( - "CreateWebViewEnvironmentWithOptionsInternal"); -static constexpr auto DllCanUnloadNow = - library_symbol("DllCanUnloadNow"); -} // namespace webview2_symbols -#endif /* WEBVIEW_MSWEBVIEW2_BUILTIN_IMPL */ - -#if WEBVIEW_MSWEBVIEW2_EXPLICIT_LINK == 1 -namespace webview2_symbols { -using CreateCoreWebView2EnvironmentWithOptions_t = HRESULT(STDMETHODCALLTYPE *)( - PCWSTR, PCWSTR, ICoreWebView2EnvironmentOptions *, - ICoreWebView2CreateCoreWebView2EnvironmentCompletedHandler *); -using GetAvailableCoreWebView2BrowserVersionString_t = - HRESULT(STDMETHODCALLTYPE *)(PCWSTR, LPWSTR *); - -static constexpr auto CreateCoreWebView2EnvironmentWithOptions = - library_symbol( - "CreateCoreWebView2EnvironmentWithOptions"); -static constexpr auto GetAvailableCoreWebView2BrowserVersionString = - library_symbol( - "GetAvailableCoreWebView2BrowserVersionString"); -} // namespace webview2_symbols -#endif /* WEBVIEW_MSWEBVIEW2_EXPLICIT_LINK */ - -class loader { -public: - HRESULT create_environment_with_options( - PCWSTR browser_dir, PCWSTR user_data_dir, - ICoreWebView2EnvironmentOptions *env_options, - ICoreWebView2CreateCoreWebView2EnvironmentCompletedHandler - *created_handler) const { -#if WEBVIEW_MSWEBVIEW2_EXPLICIT_LINK == 1 - if (m_lib.is_loaded()) { - if (auto fn = m_lib.get( - webview2_symbols::CreateCoreWebView2EnvironmentWithOptions)) { - return fn(browser_dir, user_data_dir, env_options, created_handler); - } - } -#if WEBVIEW_MSWEBVIEW2_BUILTIN_IMPL == 1 - return create_environment_with_options_impl(browser_dir, user_data_dir, - env_options, created_handler); -#else - return S_FALSE; -#endif -#else - return ::CreateCoreWebView2EnvironmentWithOptions( - browser_dir, user_data_dir, env_options, created_handler); -#endif /* WEBVIEW_MSWEBVIEW2_EXPLICIT_LINK */ - } - - HRESULT - get_available_browser_version_string(PCWSTR browser_dir, - LPWSTR *version) const { -#if WEBVIEW_MSWEBVIEW2_EXPLICIT_LINK == 1 - if (m_lib.is_loaded()) { - if (auto fn = m_lib.get( - webview2_symbols::GetAvailableCoreWebView2BrowserVersionString)) { - return fn(browser_dir, version); - } - } -#if WEBVIEW_MSWEBVIEW2_BUILTIN_IMPL == 1 - return get_available_browser_version_string_impl(browser_dir, version); -#else - return S_FALSE; -#endif -#else - return ::GetAvailableCoreWebView2BrowserVersionString(browser_dir, version); -#endif /* WEBVIEW_MSWEBVIEW2_EXPLICIT_LINK */ - } - -private: -#if WEBVIEW_MSWEBVIEW2_BUILTIN_IMPL == 1 - struct client_info_t { - bool found = false; - std::wstring dll_path; - std::wstring version; - webview2_runtime_type runtime_type; - }; - - HRESULT create_environment_with_options_impl( - PCWSTR browser_dir, PCWSTR user_data_dir, - ICoreWebView2EnvironmentOptions *env_options, - ICoreWebView2CreateCoreWebView2EnvironmentCompletedHandler - *created_handler) const { - auto found_client = find_available_client(browser_dir); - if (!found_client.found) { - return -1; - } - auto client_dll = native_library(found_client.dll_path.c_str()); - if (auto fn = client_dll.get( - webview2_symbols::CreateWebViewEnvironmentWithOptionsInternal)) { - return fn(true, found_client.runtime_type, user_data_dir, env_options, - created_handler); - } - if (auto fn = client_dll.get(webview2_symbols::DllCanUnloadNow)) { - if (!fn()) { - client_dll.detach(); - } - } - return ERROR_SUCCESS; - } - - HRESULT - get_available_browser_version_string_impl(PCWSTR browser_dir, - LPWSTR *version) const { - if (!version) { - return -1; - } - auto found_client = find_available_client(browser_dir); - if (!found_client.found) { - return -1; - } - auto info_length_bytes = - found_client.version.size() * sizeof(found_client.version[0]); - auto info = static_cast(CoTaskMemAlloc(info_length_bytes)); - if (!info) { - return -1; - } - CopyMemory(info, found_client.version.c_str(), info_length_bytes); - *version = info; - return 0; - } - - client_info_t find_available_client(PCWSTR browser_dir) const { - if (browser_dir) { - return find_embedded_client(api_version, browser_dir); - } - auto found_client = - find_installed_client(api_version, true, default_release_channel_guid); - if (!found_client.found) { - found_client = find_installed_client(api_version, false, - default_release_channel_guid); - } - return found_client; - } - - std::wstring make_client_dll_path(const std::wstring &dir) const { - auto dll_path = dir; - if (!dll_path.empty()) { - auto last_char = dir[dir.size() - 1]; - if (last_char != L'\\' && last_char != L'/') { - dll_path += L'\\'; - } - } - dll_path += L"EBWebView\\"; -#if defined(_M_X64) || defined(__x86_64__) - dll_path += L"x64"; -#elif defined(_M_IX86) || defined(__i386__) - dll_path += L"x86"; -#elif defined(_M_ARM64) || defined(__aarch64__) - dll_path += L"arm64"; -#else -#error WebView2 integration for this platform is not yet supported. -#endif - dll_path += L"\\EmbeddedBrowserWebView.dll"; - return dll_path; - } - - client_info_t - find_installed_client(unsigned int min_api_version, bool system, - const std::wstring &release_channel) const { - std::wstring sub_key = client_state_reg_sub_key; - sub_key += release_channel; - auto root_key = system ? HKEY_LOCAL_MACHINE : HKEY_CURRENT_USER; - reg_key key(root_key, sub_key, 0, KEY_READ | KEY_WOW64_32KEY); - if (!key.is_open()) { - return {}; - } - auto ebwebview_value = key.query_string(L"EBWebView"); - - auto client_version_string = - get_last_native_path_component(ebwebview_value); - auto client_version = parse_version(client_version_string); - if (client_version[2] < min_api_version) { - // Our API version is greater than the runtime API version. - return {}; - } - - auto client_dll_path = make_client_dll_path(ebwebview_value); - return {true, client_dll_path, client_version_string, - webview2_runtime_type::installed}; - } - - client_info_t find_embedded_client(unsigned int min_api_version, - const std::wstring &dir) const { - auto client_dll_path = make_client_dll_path(dir); - - auto client_version_string = get_file_version_string(client_dll_path); - auto client_version = parse_version(client_version_string); - if (client_version[2] < min_api_version) { - // Our API version is greater than the runtime API version. - return {}; - } - - return {true, client_dll_path, client_version_string, - webview2_runtime_type::embedded}; - } - - // The minimum WebView2 API version we need regardless of the SDK release - // actually used. The number comes from the SDK release version, - // e.g. 1.0.1150.38. To be safe the SDK should have a number that is greater - // than or equal to this number. The Edge browser webview client must - // have a number greater than or equal to this number. - static constexpr unsigned int api_version = 1150; - - static constexpr auto client_state_reg_sub_key = - L"SOFTWARE\\Microsoft\\EdgeUpdate\\ClientState\\"; - - // GUID for the stable release channel. - static constexpr auto stable_release_guid = - L"{F3017226-FE2A-4295-8BDF-00C3A9A7E4C5}"; - - static constexpr auto default_release_channel_guid = stable_release_guid; -#endif /* WEBVIEW_MSWEBVIEW2_BUILTIN_IMPL */ - -#if WEBVIEW_MSWEBVIEW2_EXPLICIT_LINK == 1 - native_library m_lib{L"WebView2Loader.dll"}; -#endif -}; - -namespace cast_info { -static constexpr auto controller_completed = - cast_info_t{ - IID_ICoreWebView2CreateCoreWebView2ControllerCompletedHandler}; - -static constexpr auto environment_completed = - cast_info_t{ - IID_ICoreWebView2CreateCoreWebView2EnvironmentCompletedHandler}; - -static constexpr auto message_received = - cast_info_t{ - IID_ICoreWebView2WebMessageReceivedEventHandler}; - -static constexpr auto permission_requested = - cast_info_t{ - IID_ICoreWebView2PermissionRequestedEventHandler}; -} // namespace cast_info -} // namespace mswebview2 - -class webview2_com_handler - : public ICoreWebView2CreateCoreWebView2EnvironmentCompletedHandler, - public ICoreWebView2CreateCoreWebView2ControllerCompletedHandler, - public ICoreWebView2WebMessageReceivedEventHandler, - public ICoreWebView2PermissionRequestedEventHandler { - using webview2_com_handler_cb_t = - std::function; - -public: - webview2_com_handler(HWND hwnd, msg_cb_t msgCb, webview2_com_handler_cb_t cb) - : m_window(hwnd), m_msgCb(msgCb), m_cb(cb) {} - - virtual ~webview2_com_handler() = default; - webview2_com_handler(const webview2_com_handler &other) = delete; - webview2_com_handler &operator=(const webview2_com_handler &other) = delete; - webview2_com_handler(webview2_com_handler &&other) = delete; - webview2_com_handler &operator=(webview2_com_handler &&other) = delete; - - ULONG STDMETHODCALLTYPE AddRef() { return ++m_ref_count; } - ULONG STDMETHODCALLTYPE Release() { - if (m_ref_count > 1) { - return --m_ref_count; - } - delete this; - return 0; - } - HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, LPVOID *ppv) { - using namespace mswebview2::cast_info; - - if (!ppv) { - return E_POINTER; - } - - // All of the COM interfaces we implement should be added here regardless - // of whether they are required. - // This is just to be on the safe side in case the WebView2 Runtime ever - // requests a pointer to an interface we implement. - // The WebView2 Runtime must at the very least be able to get a pointer to - // ICoreWebView2CreateCoreWebView2EnvironmentCompletedHandler when we use - // our custom WebView2 loader implementation, and observations have shown - // that it is the only interface requested in this case. None have been - // observed to be requested when using the official WebView2 loader. - - if (cast_if_equal_iid(riid, controller_completed, ppv) || - cast_if_equal_iid(riid, environment_completed, ppv) || - cast_if_equal_iid(riid, message_received, ppv) || - cast_if_equal_iid(riid, permission_requested, ppv)) { - return S_OK; - } - - return E_NOINTERFACE; - } - HRESULT STDMETHODCALLTYPE Invoke(HRESULT res, ICoreWebView2Environment *env) { - if (SUCCEEDED(res)) { - res = env->CreateCoreWebView2Controller(m_window, this); - if (SUCCEEDED(res)) { - return S_OK; - } - } - try_create_environment(); - return S_OK; - } - HRESULT STDMETHODCALLTYPE Invoke(HRESULT res, - ICoreWebView2Controller *controller) { - if (FAILED(res)) { - // See try_create_environment() regarding - // HRESULT_FROM_WIN32(ERROR_INVALID_STATE). - // The result is E_ABORT if the parent window has been destroyed already. - switch (res) { - case HRESULT_FROM_WIN32(ERROR_INVALID_STATE): - case E_ABORT: - return S_OK; - } - try_create_environment(); - return S_OK; - } - - ICoreWebView2 *webview; - ::EventRegistrationToken token; - controller->get_CoreWebView2(&webview); - webview->add_WebMessageReceived(this, &token); - webview->add_PermissionRequested(this, &token); - - m_cb(controller, webview); - return S_OK; - } - HRESULT STDMETHODCALLTYPE Invoke( - ICoreWebView2 *sender, ICoreWebView2WebMessageReceivedEventArgs *args) { - LPWSTR message; - args->TryGetWebMessageAsString(&message); - m_msgCb(narrow_string(message)); - sender->PostWebMessageAsString(message); - - CoTaskMemFree(message); - return S_OK; - } - HRESULT STDMETHODCALLTYPE - Invoke(ICoreWebView2 * /*sender*/, - ICoreWebView2PermissionRequestedEventArgs *args) { - COREWEBVIEW2_PERMISSION_KIND kind; - args->get_PermissionKind(&kind); - if (kind == COREWEBVIEW2_PERMISSION_KIND_CLIPBOARD_READ) { - args->put_State(COREWEBVIEW2_PERMISSION_STATE_ALLOW); - } - return S_OK; - } - - // Checks whether the specified IID equals the IID of the specified type and - // if so casts the "this" pointer to T and returns it. Returns nullptr on - // mismatching IIDs. - // If ppv is specified then the pointer will also be assigned to *ppv. - template - T *cast_if_equal_iid(REFIID riid, const cast_info_t &info, - LPVOID *ppv = nullptr) noexcept { - T *ptr = nullptr; - if (IsEqualIID(riid, info.iid)) { - ptr = static_cast(this); - ptr->AddRef(); - } - if (ppv) { - *ppv = ptr; - } - return ptr; - } - - // Set the function that will perform the initiating logic for creating - // the WebView2 environment. - void set_attempt_handler(std::function attempt_handler) noexcept { - m_attempt_handler = attempt_handler; - } - - // Retry creating a WebView2 environment. - // The initiating logic for creating the environment is defined by the - // caller of set_attempt_handler(). - void try_create_environment() noexcept { - // WebView creation fails with HRESULT_FROM_WIN32(ERROR_INVALID_STATE) if - // a running instance using the same user data folder exists, and the - // Environment objects have different EnvironmentOptions. - // Source: https://docs.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/icorewebview2environment?view=webview2-1.0.1150.38 - if (m_attempts < m_max_attempts) { - ++m_attempts; - auto res = m_attempt_handler(); - if (SUCCEEDED(res)) { - return; - } - // Not entirely sure if this error code only applies to - // CreateCoreWebView2Controller so we check here as well. - if (res == HRESULT_FROM_WIN32(ERROR_INVALID_STATE)) { - return; - } - try_create_environment(); - return; - } - // Give up. - m_cb(nullptr, nullptr); - } - -private: - HWND m_window; - msg_cb_t m_msgCb; - webview2_com_handler_cb_t m_cb; - std::atomic m_ref_count{1}; - std::function m_attempt_handler; - unsigned int m_max_attempts = 5; - unsigned int m_attempts = 0; -}; - -class win32_edge_engine : public engine_base { -public: - win32_edge_engine(bool debug, void *window) : m_owns_window{!window} { - if (!is_webview2_available()) { - return; - } - - HINSTANCE hInstance = GetModuleHandle(nullptr); - - if (m_owns_window) { - m_com_init = {COINIT_APARTMENTTHREADED}; - if (!m_com_init.is_initialized()) { - return; - } - enable_dpi_awareness(); - - HICON icon = (HICON)LoadImage( - hInstance, IDI_APPLICATION, IMAGE_ICON, GetSystemMetrics(SM_CXICON), - GetSystemMetrics(SM_CYICON), LR_DEFAULTCOLOR); - - // Create a top-level window. - WNDCLASSEXW wc; - ZeroMemory(&wc, sizeof(WNDCLASSEX)); - wc.cbSize = sizeof(WNDCLASSEX); - wc.hInstance = hInstance; - wc.lpszClassName = L"webview"; - wc.hIcon = icon; - wc.lpfnWndProc = (WNDPROC)(+[](HWND hwnd, UINT msg, WPARAM wp, - LPARAM lp) -> LRESULT { - win32_edge_engine *w{}; - - if (msg == WM_NCCREATE) { - auto *lpcs{reinterpret_cast(lp)}; - w = static_cast(lpcs->lpCreateParams); - w->m_window = hwnd; - SetWindowLongPtrW(hwnd, GWLP_USERDATA, reinterpret_cast(w)); - enable_non_client_dpi_scaling_if_needed(hwnd); - apply_window_theme(hwnd); - } else { - w = reinterpret_cast( - GetWindowLongPtrW(hwnd, GWLP_USERDATA)); - } - - if (!w) { - return DefWindowProcW(hwnd, msg, wp, lp); - } - - switch (msg) { - case WM_SIZE: - w->resize_widget(); - break; - case WM_CLOSE: - DestroyWindow(hwnd); - break; - case WM_DESTROY: - w->m_window = nullptr; - SetWindowLongPtrW(hwnd, GWLP_USERDATA, 0); - w->on_window_destroyed(); - break; - case WM_GETMINMAXINFO: { - auto lpmmi = (LPMINMAXINFO)lp; - if (w->m_maxsz.x > 0 && w->m_maxsz.y > 0) { - lpmmi->ptMaxSize = w->m_maxsz; - lpmmi->ptMaxTrackSize = w->m_maxsz; - } - if (w->m_minsz.x > 0 && w->m_minsz.y > 0) { - lpmmi->ptMinTrackSize = w->m_minsz; - } - } break; - case 0x02E4 /*WM_GETDPISCALEDSIZE*/: { - auto dpi = static_cast(wp); - auto *size{reinterpret_cast(lp)}; - *size = w->get_scaled_size(w->m_dpi, dpi); - return TRUE; - } - case 0x02E0 /*WM_DPICHANGED*/: { - // Windows 10: The size we get here is exactly what we supplied to WM_GETDPISCALEDSIZE. - // Windows 11: The size we get here is NOT what we supplied to WM_GETDPISCALEDSIZE. - // Due to this difference, don't use the suggested bounds. - auto dpi = static_cast(HIWORD(wp)); - w->on_dpi_changed(dpi); - break; - } - case WM_SETTINGCHANGE: { - auto *area = reinterpret_cast(lp); - if (area) { - w->on_system_setting_change(area); - } - break; - } - case WM_ACTIVATE: - if (LOWORD(wp) != WA_INACTIVE) { - w->focus_webview(); - } - break; - default: - return DefWindowProcW(hwnd, msg, wp, lp); - } - return 0; - }); - RegisterClassExW(&wc); - - CreateWindowW(L"webview", L"", WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, - CW_USEDEFAULT, 0, 0, nullptr, nullptr, hInstance, this); - if (m_window == nullptr) { - return; - } - on_window_created(); - - m_dpi = get_window_dpi(m_window); - constexpr const int initial_width = 640; - constexpr const int initial_height = 480; - set_size(initial_width, initial_height, WEBVIEW_HINT_NONE); - } else { - m_window = IsWindow(static_cast(window)) - ? static_cast(window) - : *(static_cast(window)); - m_dpi = get_window_dpi(m_window); - } - - // Create a window that WebView2 will be embedded into. - WNDCLASSEXW widget_wc{}; - widget_wc.cbSize = sizeof(WNDCLASSEX); - widget_wc.hInstance = hInstance; - widget_wc.lpszClassName = L"webview_widget"; - widget_wc.lpfnWndProc = (WNDPROC)(+[](HWND hwnd, UINT msg, WPARAM wp, - LPARAM lp) -> LRESULT { - win32_edge_engine *w{}; - - if (msg == WM_NCCREATE) { - auto *lpcs{reinterpret_cast(lp)}; - w = static_cast(lpcs->lpCreateParams); - w->m_widget = hwnd; - SetWindowLongPtrW(hwnd, GWLP_USERDATA, reinterpret_cast(w)); - } else { - w = reinterpret_cast( - GetWindowLongPtrW(hwnd, GWLP_USERDATA)); - } - - if (!w) { - return DefWindowProcW(hwnd, msg, wp, lp); - } - - switch (msg) { - case WM_SIZE: - w->resize_webview(); - break; - case WM_DESTROY: - w->m_widget = nullptr; - SetWindowLongPtrW(hwnd, GWLP_USERDATA, 0); - break; - default: - return DefWindowProcW(hwnd, msg, wp, lp); - } - return 0; - }); - RegisterClassExW(&widget_wc); - CreateWindowExW(WS_EX_CONTROLPARENT, L"webview_widget", nullptr, WS_CHILD, - 0, 0, 0, 0, m_window, nullptr, hInstance, this); - - // Create a message-only window for internal messaging. - WNDCLASSEXW message_wc{}; - message_wc.cbSize = sizeof(WNDCLASSEX); - message_wc.hInstance = hInstance; - message_wc.lpszClassName = L"webview_message"; - message_wc.lpfnWndProc = (WNDPROC)(+[](HWND hwnd, UINT msg, WPARAM wp, - LPARAM lp) -> LRESULT { - win32_edge_engine *w{}; - - if (msg == WM_NCCREATE) { - auto *lpcs{reinterpret_cast(lp)}; - w = static_cast(lpcs->lpCreateParams); - w->m_message_window = hwnd; - SetWindowLongPtrW(hwnd, GWLP_USERDATA, reinterpret_cast(w)); - } else { - w = reinterpret_cast( - GetWindowLongPtrW(hwnd, GWLP_USERDATA)); - } - - if (!w) { - return DefWindowProcW(hwnd, msg, wp, lp); - } - - switch (msg) { - case WM_APP: - if (auto f = (dispatch_fn_t *)(lp)) { - (*f)(); - delete f; - } - break; - case WM_DESTROY: - w->m_message_window = nullptr; - SetWindowLongPtrW(hwnd, GWLP_USERDATA, 0); - break; - default: - return DefWindowProcW(hwnd, msg, wp, lp); - } - return 0; - }); - RegisterClassExW(&message_wc); - CreateWindowExW(0, L"webview_message", nullptr, 0, 0, 0, 0, 0, HWND_MESSAGE, - nullptr, hInstance, this); - - if (m_owns_window) { - ShowWindow(m_window, SW_SHOW); - UpdateWindow(m_window); - SetFocus(m_window); - } - - auto cb = - std::bind(&win32_edge_engine::on_message, this, std::placeholders::_1); - - embed(m_widget, debug, cb); - } - - virtual ~win32_edge_engine() { - if (m_com_handler) { - m_com_handler->Release(); - m_com_handler = nullptr; - } - if (m_webview) { - m_webview->Release(); - m_webview = nullptr; - } - if (m_controller) { - m_controller->Release(); - m_controller = nullptr; - } - // Replace wndproc to avoid callbacks and other bad things during - // destruction. - auto wndproc = reinterpret_cast( - +[](HWND hwnd, UINT msg, WPARAM wp, LPARAM lp) -> LRESULT { - return DefWindowProcW(hwnd, msg, wp, lp); - }); - if (m_widget) { - SetWindowLongPtrW(m_widget, GWLP_WNDPROC, wndproc); - } - if (m_window && m_owns_window) { - SetWindowLongPtrW(m_window, GWLP_WNDPROC, wndproc); - } - if (m_widget) { - DestroyWindow(m_widget); - m_widget = nullptr; - } - if (m_window) { - if (m_owns_window) { - DestroyWindow(m_window); - on_window_destroyed(true); - } - m_window = nullptr; - } - if (m_owns_window) { - // Not strictly needed for windows to close immediately but aligns - // behavior across backends. - deplete_run_loop_event_queue(); - } - // We need the message window in order to deplete the event queue. - if (m_message_window) { - SetWindowLongPtrW(m_message_window, GWLP_WNDPROC, wndproc); - DestroyWindow(m_message_window); - m_message_window = nullptr; - } - } - - win32_edge_engine(const win32_edge_engine &other) = delete; - win32_edge_engine &operator=(const win32_edge_engine &other) = delete; - win32_edge_engine(win32_edge_engine &&other) = delete; - win32_edge_engine &operator=(win32_edge_engine &&other) = delete; - - void run_impl() override { - MSG msg; - while (GetMessageW(&msg, nullptr, 0, 0) > 0) { - TranslateMessage(&msg); - DispatchMessageW(&msg); - } - } - void *window_impl() override { return (void *)m_window; } - void *widget_impl() override { return (void *)m_widget; } - void *browser_controller_impl() override { return (void *)m_controller; } - void terminate_impl() override { PostQuitMessage(0); } - void dispatch_impl(dispatch_fn_t f) override { - PostMessageW(m_message_window, WM_APP, 0, (LPARAM) new dispatch_fn_t(f)); - } - - void set_title_impl(const std::string &title) override { - SetWindowTextW(m_window, widen_string(title).c_str()); - } - - void set_size_impl(int width, int height, webview_hint_t hints) override { - auto style = GetWindowLong(m_window, GWL_STYLE); - if (hints == WEBVIEW_HINT_FIXED) { - style &= ~(WS_THICKFRAME | WS_MAXIMIZEBOX); - } else { - style |= (WS_THICKFRAME | WS_MAXIMIZEBOX); - } - SetWindowLong(m_window, GWL_STYLE, style); - - if (hints == WEBVIEW_HINT_MAX) { - m_maxsz.x = width; - m_maxsz.y = height; - } else if (hints == WEBVIEW_HINT_MIN) { - m_minsz.x = width; - m_minsz.y = height; - } else { - auto dpi = get_window_dpi(m_window); - m_dpi = dpi; - auto scaled_size = - scale_size(width, height, get_default_window_dpi(), dpi); - auto frame_size = - make_window_frame_size(m_window, scaled_size.cx, scaled_size.cy, dpi); - SetWindowPos(m_window, nullptr, 0, 0, frame_size.cx, frame_size.cy, - SWP_NOZORDER | SWP_NOACTIVATE | SWP_NOMOVE | - SWP_FRAMECHANGED); - } - } - - void navigate_impl(const std::string &url) override { - auto wurl = widen_string(url); - m_webview->Navigate(wurl.c_str()); - } - - void init_impl(const std::string &js) override { - auto wjs = widen_string(js); - m_webview->AddScriptToExecuteOnDocumentCreated(wjs.c_str(), nullptr); - } - - void eval_impl(const std::string &js) override { - auto wjs = widen_string(js); - m_webview->ExecuteScript(wjs.c_str(), nullptr); - } - - void set_html_impl(const std::string &html) override { - m_webview->NavigateToString(widen_string(html).c_str()); - } - -private: - bool embed(HWND wnd, bool debug, msg_cb_t cb) { - std::atomic_flag flag = ATOMIC_FLAG_INIT; - flag.test_and_set(); - - wchar_t currentExePath[MAX_PATH]; - GetModuleFileNameW(nullptr, currentExePath, MAX_PATH); - wchar_t *currentExeName = PathFindFileNameW(currentExePath); - - wchar_t dataPath[MAX_PATH]; - if (!SUCCEEDED( - SHGetFolderPathW(nullptr, CSIDL_APPDATA, nullptr, 0, dataPath))) { - return false; - } - wchar_t userDataFolder[MAX_PATH]; - PathCombineW(userDataFolder, dataPath, currentExeName); - - m_com_handler = new webview2_com_handler( - wnd, cb, - [&](ICoreWebView2Controller *controller, ICoreWebView2 *webview) { - if (!controller || !webview) { - flag.clear(); - return; - } - controller->AddRef(); - webview->AddRef(); - m_controller = controller; - m_webview = webview; - flag.clear(); - }); - - m_com_handler->set_attempt_handler([&] { - return m_webview2_loader.create_environment_with_options( - nullptr, userDataFolder, nullptr, m_com_handler); - }); - m_com_handler->try_create_environment(); - - // Pump the message loop until WebView2 has finished initialization. - bool got_quit_msg = false; - MSG msg; - while (flag.test_and_set() && GetMessageW(&msg, nullptr, 0, 0) >= 0) { - if (msg.message == WM_QUIT) { - got_quit_msg = true; - break; - } - TranslateMessage(&msg); - DispatchMessageW(&msg); - } - if (got_quit_msg) { - return false; - } - if (!m_controller || !m_webview) { - return false; - } - ICoreWebView2Settings *settings = nullptr; - auto res = m_webview->get_Settings(&settings); - if (res != S_OK) { - return false; - } - res = settings->put_AreDevToolsEnabled(debug ? TRUE : FALSE); - if (res != S_OK) { - return false; - } - res = settings->put_IsStatusBarEnabled(FALSE); - if (res != S_OK) { - return false; - } - init("window.external={invoke:s=>window.chrome.webview.postMessage(s)}"); - resize_webview(); - m_controller->put_IsVisible(TRUE); - ShowWindow(m_widget, SW_SHOW); - UpdateWindow(m_widget); - if (m_owns_window) { - focus_webview(); - } - return true; - } - - void resize_widget() { - if (m_widget) { - RECT r{}; - if (GetClientRect(GetParent(m_widget), &r)) { - MoveWindow(m_widget, r.left, r.top, r.right - r.left, r.bottom - r.top, - TRUE); - } - } - } - - void resize_webview() { - if (m_widget && m_controller) { - RECT bounds{}; - if (GetClientRect(m_widget, &bounds)) { - m_controller->put_Bounds(bounds); - } - } - } - - void focus_webview() { - if (m_controller) { - m_controller->MoveFocus(COREWEBVIEW2_MOVE_FOCUS_REASON_PROGRAMMATIC); - } - } - - bool is_webview2_available() const noexcept { - LPWSTR version_info = nullptr; - auto res = m_webview2_loader.get_available_browser_version_string( - nullptr, &version_info); - // The result will be equal to HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND) - // if the WebView2 runtime is not installed. - auto ok = SUCCEEDED(res) && version_info; - if (version_info) { - CoTaskMemFree(version_info); - } - return ok; - } - - void on_dpi_changed(int dpi) { - auto scaled_size = get_scaled_size(m_dpi, dpi); - auto frame_size = - make_window_frame_size(m_window, scaled_size.cx, scaled_size.cy, dpi); - SetWindowPos(m_window, nullptr, 0, 0, frame_size.cx, frame_size.cy, - SWP_NOZORDER | SWP_NOACTIVATE | SWP_NOMOVE | SWP_FRAMECHANGED); - m_dpi = dpi; - } - - SIZE get_size() const { - RECT bounds; - GetClientRect(m_window, &bounds); - auto width = bounds.right - bounds.left; - auto height = bounds.bottom - bounds.top; - return {width, height}; - } - - SIZE get_scaled_size(int from_dpi, int to_dpi) const { - auto size = get_size(); - return scale_size(size.cx, size.cy, from_dpi, to_dpi); - } - - void on_system_setting_change(const wchar_t *area) { - // Detect light/dark mode change in system. - if (lstrcmpW(area, L"ImmersiveColorSet") == 0) { - apply_window_theme(m_window); - } - } - - // Blocks while depleting the run loop of events. - void deplete_run_loop_event_queue() { - bool done{}; - dispatch([&] { done = true; }); - while (!done) { - MSG msg; - if (GetMessageW(&msg, nullptr, 0, 0) > 0) { - TranslateMessage(&msg); - DispatchMessageW(&msg); - } - } - } - - // The app is expected to call CoInitializeEx before - // CreateCoreWebView2EnvironmentWithOptions. - // Source: https://docs.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/webview2-idl#createcorewebview2environmentwithoptions - com_init_wrapper m_com_init; - HWND m_window = nullptr; - HWND m_widget = nullptr; - HWND m_message_window = nullptr; - POINT m_minsz = POINT{0, 0}; - POINT m_maxsz = POINT{0, 0}; - DWORD m_main_thread = GetCurrentThreadId(); - ICoreWebView2 *m_webview = nullptr; - ICoreWebView2Controller *m_controller = nullptr; - webview2_com_handler *m_com_handler = nullptr; - mswebview2::loader m_webview2_loader; - int m_dpi{}; - bool m_owns_window{}; -}; - -} // namespace detail - -using browser_engine = detail::win32_edge_engine; - -} // namespace webview - -#endif /* WEBVIEW_GTK, WEBVIEW_COCOA, WEBVIEW_EDGE */ - -namespace webview { -using webview = browser_engine; -} // namespace webview - -WEBVIEW_API webview_t webview_create(int debug, void *wnd) { - auto w = new webview::webview(debug, wnd); - if (!w->window()) { - delete w; - return nullptr; - } - return w; -} - -WEBVIEW_API void webview_destroy(webview_t w) { - delete static_cast(w); -} - -WEBVIEW_API void webview_run(webview_t w) { - static_cast(w)->run(); -} - -WEBVIEW_API void webview_terminate(webview_t w) { - static_cast(w)->terminate(); -} - -WEBVIEW_API void webview_dispatch(webview_t w, void (*fn)(webview_t, void *), - void *arg) { - static_cast(w)->dispatch([=]() { fn(w, arg); }); -} - -WEBVIEW_API void *webview_get_window(webview_t w) { - return static_cast(w)->window(); -} - -WEBVIEW_API void *webview_get_native_handle(webview_t w, - webview_native_handle_kind_t kind) { - auto *w_ = static_cast(w); - switch (kind) { - case WEBVIEW_NATIVE_HANDLE_KIND_UI_WINDOW: - return w_->window(); - case WEBVIEW_NATIVE_HANDLE_KIND_UI_WIDGET: - return w_->widget(); - case WEBVIEW_NATIVE_HANDLE_KIND_BROWSER_CONTROLLER: - return w_->browser_controller(); - default: - return nullptr; - } -} - -WEBVIEW_API void webview_set_title(webview_t w, const char *title) { - static_cast(w)->set_title(title); -} - -WEBVIEW_API void webview_set_size(webview_t w, int width, int height, - webview_hint_t hints) { - static_cast(w)->set_size(width, height, hints); -} - -WEBVIEW_API void webview_navigate(webview_t w, const char *url) { - static_cast(w)->navigate(url); -} - -WEBVIEW_API void webview_set_html(webview_t w, const char *html) { - static_cast(w)->set_html(html); -} - -WEBVIEW_API void webview_init(webview_t w, const char *js) { - static_cast(w)->init(js); -} - -WEBVIEW_API void webview_eval(webview_t w, const char *js) { - static_cast(w)->eval(js); -} - -WEBVIEW_API void webview_bind(webview_t w, const char *name, - void (*fn)(const char *seq, const char *req, - void *arg), - void *arg) { - static_cast(w)->bind( - name, - [=](const std::string &seq, const std::string &req, void *arg) { - fn(seq.c_str(), req.c_str(), arg); - }, - arg); -} - -WEBVIEW_API void webview_unbind(webview_t w, const char *name) { - static_cast(w)->unbind(name); -} - -WEBVIEW_API void webview_return(webview_t w, const char *seq, int status, - const char *result) { - static_cast(w)->resolve(seq, status, result); -} - -WEBVIEW_API const webview_version_info_t *webview_version(void) { - return &webview::detail::library_version_info; -} - -#endif /* WEBVIEW_HEADER */ -#endif /* __cplusplus */ -#endif /* WEBVIEW_H */ diff --git a/libs/webview/include/webview.h b/libs/webview/include/webview.h new file mode 120000 index 0000000..6fbe47b --- /dev/null +++ b/libs/webview/include/webview.h @@ -0,0 +1 @@ +/Users/pavel/src/webview/core/include/webview/webview.h \ No newline at end of file diff --git a/ua_test/build.sh b/ua_test/build.sh new file mode 100755 index 0000000..6592d5f --- /dev/null +++ b/ua_test/build.sh @@ -0,0 +1,10 @@ +#!/bin/bash +set -e + +cd "$(dirname "$0")" + +echo "Building webview_app for macOS..." +go build -o webview_app main.go + +echo "Build complete!" +echo "Run ./webview_app to test the custom User-Agent." diff --git a/ua_test/go.mod b/ua_test/go.mod new file mode 100644 index 0000000..5526593 --- /dev/null +++ b/ua_test/go.mod @@ -0,0 +1,9 @@ +module ua-test + +go 1.22 + +require ( + github.com/webview/webview_go v0.0.0-00010101000000-000000000000 +) + +replace github.com/webview/webview_go => .. diff --git a/ua_test/main.go b/ua_test/main.go new file mode 100644 index 0000000..fdff70c --- /dev/null +++ b/ua_test/main.go @@ -0,0 +1,15 @@ +package main + +import ( + webview "github.com/webview/webview_go" +) + +func main() { + w := webview.New(false) + defer w.Destroy() + w.SetTitle("Basic Example") + w.SetSize(480, 320, webview.HintNone) + w.SetUserAgent("MySuperCustomUserAgent/1.0 (macOS)") + w.Navigate("https://www.whatismybrowser.com/detect/what-is-my-user-agent/") + w.Run() +} diff --git a/webview.go b/webview.go index 7cf0d9f..e55f04a 100644 --- a/webview.go +++ b/webview.go @@ -25,12 +25,12 @@ void CgoWebViewUnbind(webview_t w, const char *name); */ import "C" import ( + "encoding/json" + "errors" _ "github.com/webview/webview_go/libs/mswebview2" _ "github.com/webview/webview_go/libs/mswebview2/include" _ "github.com/webview/webview_go/libs/webview" _ "github.com/webview/webview_go/libs/webview/include" - "encoding/json" - "errors" "reflect" "runtime" "sync" @@ -89,6 +89,8 @@ type WebView interface { // SetSize updates native window size. See Hint constants. SetSize(w int, h int, hint Hint) + SetUserAgent(userAgent string) + // Navigate navigates webview to the given URL. URL may be a properly encoded data. // URI. Examples: // w.Navigate("https://github.com/webview/webview") @@ -100,6 +102,8 @@ type WebView interface { // Example: w.SetHtml(w, "

Hello

"); SetHtml(html string) + // SetUserAgent(userAgent string) + // Init injects JavaScript code at the initialization of the new page. Every // time the webview will open a the new page - this initialization code will // be executed. It is guaranteed that code is executed before window.onload. @@ -196,6 +200,12 @@ func (w *webview) SetSize(width int, height int, hint Hint) { C.webview_set_size(w.w, C.int(width), C.int(height), C.webview_hint_t(hint)) } +func (w *webview) SetUserAgent(ua string) { + cstr := C.CString(ua) + defer C.free(unsafe.Pointer(cstr)) + C.webview_set_user_agent(w.w, cstr) +} + func (w *webview) Init(js string) { s := C.CString(js) defer C.free(unsafe.Pointer(s))