/* * 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. */ #ifndef WEBVIEW_H #define WEBVIEW_H #ifndef WEBVIEW_API #define WEBVIEW_API extern #endif #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 10 #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 // 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) // 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; #ifdef __cplusplus extern "C" { #endif typedef void* webview_t; // Creates a new webview instance. If debug is non-zero - developer tools will // be enabled (if the platform supports them). Window parameter can be a // pointer to the native window handle. If it's non-null - then child WebView // is embedded into the given parent window. Otherwise a new window is created. // Depending on the platform, a GtkWindow, NSWindow or HWND pointer can be // passed here. Returns 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 and closes the native window. WEBVIEW_API void webview_destroy(webview_t w); // Runs the main loop until it's terminated. After this function exits - you // must destroy the webview. WEBVIEW_API void webview_run(webview_t w); // Stops the main loop. It is safe to call this function from another other // background thread. WEBVIEW_API void webview_terminate(webview_t w); // Posts a function to be executed on the main thread. You normally do not need // to call this function, unless you want to tweak the native window. WEBVIEW_API void webview_dispatch(webview_t w, void (*fn)(webview_t w, void* arg), void* arg); // Returns a native window handle pointer. When using GTK backend the pointer // is GtkWindow pointer, when using Cocoa backend the pointer is NSWindow // pointer, when using Win32 backend the pointer is HWND pointer. WEBVIEW_API void* webview_get_window(webview_t w); // Updates the title of the native window. Must be called from the UI thread. WEBVIEW_API void webview_set_title(webview_t w, const char* title); // Window size hints #define WEBVIEW_HINT_NONE 0 // Width and height are default size #define WEBVIEW_HINT_MIN 1 // Width and height are minimum bounds #define WEBVIEW_HINT_MAX 2 // Width and height are maximum bounds #define WEBVIEW_HINT_FIXED 3 // Window size can not be changed by a user // Updates native window size. See WEBVIEW_HINT constants. WEBVIEW_API void webview_set_size(webview_t w, int width, int height, int hints); // Navigates webview to the given URL. URL may be a properly encoded data URI. // Examples: // 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="); WEBVIEW_API void webview_navigate(webview_t w, const char* url); // Set webview HTML directly. // Example: webview_set_html(w, "

Hello

"); WEBVIEW_API void webview_set_html(webview_t w, const char* html); // 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. WEBVIEW_API void webview_init(webview_t w, const char* js); // Evaluates arbitrary JavaScript code. Evaluation happens asynchronously, also // the result of the expression is ignored. Use RPC bindings if you want to // receive notifications about the results of the evaluation. WEBVIEW_API void webview_eval(webview_t w, const char* js); // Binds a native C callback so that it will appear under the given name as a // global JavaScript function. Internally it uses webview_init(). Callback // receives a request string and a user-provided argument pointer. Request // string is a JSON array of all the arguments passed to the JavaScript // function. 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 native C callback that was previously set by webview_bind. WEBVIEW_API void webview_unbind(webview_t w, const char* name); // Allows to return a value from the native binding. Original request pointer // must be provided to help internal RPC engine match requests with responses. // If status is zero - result is expected to be a valid JSON result value. // If status is not zero - result is an error JSON object. 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(); #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 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 }; 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; } inline std::string json_escape(const std::string& s) { // TODO: implement return '"' + s + '"'; } 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 = nullptr; size_t value_sz = 0; 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 ""; } } // 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 namespace webview { namespace detail { class gtk_webkit_engine { public: gtk_webkit_engine(bool debug, void* window) : m_window(static_cast(window)) { if (gtk_init_check(nullptr, nullptr) == FALSE) { return; } m_window = static_cast(window); if (m_window == nullptr) { m_window = gtk_window_new(GTK_WINDOW_TOPLEVEL); } g_signal_connect(G_OBJECT(m_window), "destroy", G_CALLBACK(+[](GtkWidget*, gpointer arg) { static_cast(arg)->terminate(); }), this); // 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_grab_focus(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); } gtk_widget_show_all(m_window); } virtual ~gtk_webkit_engine() = default; void* window() { return (void*)m_window; } void run() { gtk_main(); } void terminate() { gtk_main_quit(); } void dispatch(std::function f) { 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(const std::string& title) { gtk_window_set_title(GTK_WINDOW(m_window), title.c_str()); } void set_size(int width, int height, int hints) { 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(const std::string& url) { webkit_web_view_load_uri(WEBKIT_WEB_VIEW(m_webview), url.c_str()); } void add_navigate_listener(std::function callback, void* arg) { g_signal_connect(WEBKIT_WEB_VIEW(m_webview), "load-changed", G_CALLBACK(on_load_changed), this); navigateCallbackArg = arg; navigateCallback = std::move(callback); } void add_scheme_handler(const std::string& scheme, std::function callback, void* arg) { auto view = WEBKIT_WEB_VIEW(m_webview); auto context = webkit_web_view_get_context(view); scheme_handlers.insert({ scheme, { .arg = arg, .fkt = callback } }); webkit_web_context_register_uri_scheme(context, scheme.c_str(), scheme_handler, static_cast(this), nullptr); } void set_html(const std::string& html) { webkit_web_view_load_html(WEBKIT_WEB_VIEW(m_webview), html.c_str(), nullptr); } void init(const std::string& js) { 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(const std::string& js) { webkit_web_view_run_javascript(WEBKIT_WEB_VIEW(m_webview), js.c_str(), nullptr, nullptr, nullptr); } private: virtual void on_message(const std::string& msg) = 0; struct handler_t { void* arg; std::function fkt; }; std::map scheme_handlers; void scheme_handler_call(const std::string& scheme, const std::string& url) { auto handler = scheme_handlers.find(scheme); if (handler != scheme_handlers.end()) { const auto& arg = handler->second; arg.fkt(url, arg.arg); } } static void scheme_handler(WebKitURISchemeRequest* request, gpointer user_data) { auto _this = static_cast(user_data); auto scheme = webkit_uri_scheme_request_get_scheme(request); auto uri = webkit_uri_scheme_request_get_uri(request); _this->scheme_handler_call(scheme, uri); } static char* get_string_from_js_result(WebKitJavascriptResult* r) { char* s = nullptr; #if WEBKIT_MAJOR_VERSION >= 2 && WEBKIT_MINOR_VERSION >= 22 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; } GtkWidget* m_window; GtkWidget* m_webview; void* navigateCallbackArg = nullptr; std::function navigateCallback = nullptr; static void on_load_changed(WebKitWebView* web_view, WebKitLoadEvent load_event, gpointer arg) { if (load_event == WEBKIT_LOAD_FINISHED) { auto inst = static_cast(arg); inst->navigateCallback(webkit_web_view_get_uri(web_view), inst->navigateCallbackArg); } } }; } // 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...); } } // 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: cocoa_wkwebview_engine(bool debug, void* window) : m_debug{ debug }, m_parent_window{ window } { auto app = get_shared_application(); auto delegate = create_app_delegate(); objc_setAssociatedObject(delegate, "webview", (id)this, OBJC_ASSOCIATION_ASSIGN); objc::msg_send(app, "setDelegate:"_sel, delegate); // See comments related to application lifecycle in create_app_delegate(). if (window) { on_application_did_finish_launching(delegate, app); } else { // 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. objc::msg_send(app, "run"_sel); } } virtual ~cocoa_wkwebview_engine() = default; void* window() { return (void*)m_window; } void terminate() { auto app = get_shared_application(); objc::msg_send(app, "terminate:"_sel, nullptr); } void run() { auto app = get_shared_application(); objc::msg_send(app, "run"_sel); } void dispatch(std::function f) { 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(const std::string& title) { objc::msg_send( m_window, "setTitle:"_sel, objc::msg_send("NSString"_cls, "stringWithUTF8String:"_sel, title.c_str())); } void set_size(int width, int height, int hints) { 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(const std::string& url) { 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 add_navigate_listener(std::function callback, void* arg) { m_navigateCallback = callback; m_navigateCallbackArg = arg; } void set_html(const std::string& html) { objc::msg_send( m_webview, "loadHTMLString:baseURL:"_sel, objc::msg_send("NSString"_cls, "stringWithUTF8String:"_sel, html.c_str()), nullptr); } void init(const std::string& js) { // Equivalent Obj-C: // [m_manager addUserScript:[[WKUserScript alloc] initWithSource:[NSString // stringWithUTF8String:js.c_str()] // injectionTime:WKUserScriptInjectionTimeAtDocumentStart forMainFrameOnly:YES]] objc::msg_send( m_manager, "addUserScript:"_sel, 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)); } void eval(const std::string& js) { objc::msg_send( m_webview, "evaluateJavaScript:completionHandler:"_sel, objc::msg_send("NSString"_cls, "stringWithUTF8String:"_sel, js.c_str()), nullptr); } private: virtual void on_message(const std::string& msg) = 0; id create_app_delegate() { // 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. auto cls = objc_allocateClassPair((Class) "NSResponder"_cls, "WebviewAppDelegate", 0); class_addProtocol(cls, objc_getProtocol("NSTouchBarProvider")); class_addMethod(cls, "applicationShouldTerminateAfterLastWindowClosed:"_sel, (IMP)(+[](id, SEL, id) -> BOOL { return 1; }), "c@:@"); // If the library was not initialized with an existing window then the user // is likely managing the application lifecycle and we would not get the // "applicationDidFinishLaunching:" message and therefore do not need to // add this method. if (!m_parent_window) { 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() { auto cls = objc_allocateClassPair((Class) "NSResponder"_cls, "WebkitScriptMessageHandler", 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() { auto cls = objc_allocateClassPair((Class) "NSObject"_cls, "WebkitUIDelegate", 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); } id create_webkit_navigation_delegate() { auto cls = objc_allocateClassPair((Class) "NSObject"_cls, "WebkitNavigationDelegate", 0); class_addProtocol(cls, objc_getProtocol("WKNavigationDelegate")); class_addMethod(cls, "webView:didFinishNavigation:"_sel, (IMP)(+[](id delegate, SEL sel, id webview, id navigation) { auto w = get_associated_webview(delegate); auto url = objc::msg_send(webview, "URL"_sel); auto nstr = objc::msg_send(url, "absoluteString"_sel); auto str = objc::msg_send(nstr, "UTF8String"_sel); w->m_navigateCallback(str, w->m_navigateCallbackArg); }), "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 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_parent_window) { // Stop the main run loop so that we can return // from the constructor. objc::msg_send(app, "stop:"_sel, nullptr); } // 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); } // Main window if (!m_parent_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); } else { m_window = (id)m_parent_window; } // Webview auto config = objc::msg_send("WKWebViewConfiguration"_cls, "new"_sel); m_manager = objc::msg_send(config, "userContentController"_sel); m_webview = objc::msg_send("WKWebView"_cls, "alloc"_sel); if (m_debug) { // Equivalent Obj-C: // [[config preferences] setValue:@YES forKey:@"developerExtrasEnabled"]; objc::msg_send( objc::msg_send(config, "preferences"_sel), "setValue:forKey:"_sel, objc::msg_send("NSNumber"_cls, "numberWithBool:"_sel, YES), "developerExtrasEnabled"_str); } // Equivalent Obj-C: // [[config preferences] setValue:@YES forKey:@"fullScreenEnabled"]; objc::msg_send(objc::msg_send(config, "preferences"_sel), "setValue:forKey:"_sel, objc::msg_send("NSNumber"_cls, "numberWithBool:"_sel, YES), "fullScreenEnabled"_str); // Equivalent Obj-C: // [[config preferences] setValue:@YES forKey:@"javaScriptCanAccessClipboard"]; objc::msg_send(objc::msg_send(config, "preferences"_sel), "setValue:forKey:"_sel, objc::msg_send("NSNumber"_cls, "numberWithBool:"_sel, YES), "javaScriptCanAccessClipboard"_str); // Equivalent Obj-C: // [[config preferences] setValue:@YES forKey:@"DOMPasteAllowed"]; objc::msg_send(objc::msg_send(config, "preferences"_sel), "setValue:forKey:"_sel, objc::msg_send("NSNumber"_cls, "numberWithBool:"_sel, YES), "DOMPasteAllowed"_str); auto ui_delegate = 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); auto navigation_delegate = create_webkit_navigation_delegate(); objc::msg_send(m_webview, "setNavigationDelegate:"_sel, navigation_delegate); auto script_message_handler = 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); }, }; )""); objc::msg_send(m_window, "setContentView:"_sel, m_webview); objc::msg_send(m_window, "makeKeyAndOrderFront:"_sel, nullptr); } bool m_debug; void* m_parent_window; id m_window; id m_webview; id m_manager; void* m_navigateCallbackArg = nullptr; std::function m_navigateCallback = 0; }; } // 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; // 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) { if (input.empty()) { return std::string(); } UINT cp = CP_UTF8; DWORD flags = WC_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(); } // 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(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) = delete; com_init_wrapper& operator=(com_init_wrapper&& other) = delete; bool is_initialized() const { return m_initialized; } private: bool m_initialized = false; }; // 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: explicit native_library(const wchar_t* name) : m_handle(LoadLibraryW(name)) { } ~native_library() { if (m_handle) { FreeLibrary(m_handle); m_handle = nullptr; } } native_library(const native_library& other) = delete; native_library& operator=(const native_library& other) = delete; native_library(native_library&& other) = default; native_library& operator=(native_library&& other) = default; // 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()) { return reinterpret_cast( GetProcAddress(m_handle, symbol.get_name())); } return nullptr; } // Returns true if the library is currently loaded; otherwise false. bool is_loaded() const { return !!m_handle; } void detach() { m_handle = nullptr; } private: HMODULE m_handle = nullptr; }; struct user32_symbols { using DPI_AWARENESS_CONTEXT = HANDLE; using SetProcessDpiAwarenessContext_t = BOOL(WINAPI*)(DPI_AWARENESS_CONTEXT); using SetProcessDPIAware_t = BOOL(WINAPI*)(); static constexpr auto SetProcessDpiAwarenessContext = library_symbol("SetProcessDpiAwarenessContext"); static constexpr auto SetProcessDPIAware = library_symbol("SetProcessDPIAware"); }; struct shcore_symbols { typedef enum { PROCESS_PER_MONITOR_DPI_AWARE = 2 } PROCESS_DPI_AWARENESS; using SetProcessDpiAwareness_t = HRESULT(WINAPI*)(PROCESS_DPI_AWARENESS); static constexpr auto SetProcessDpiAwareness = library_symbol("SetProcessDpiAwareness"); }; 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; } std::wstring query_string(const wchar_t* name) 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) { return std::wstring(); } // Read the data. std::wstring result(buf_length / sizeof(wchar_t), 0); auto buf = reinterpret_cast(&result[0]); status = RegQueryValueExW(m_handle, name, nullptr, nullptr, buf, &buf_length); if (status != ERROR_SUCCESS) { return std::wstring(); } // 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; } private: HKEY m_handle = nullptr; }; inline bool enable_dpi_awareness() { auto user32 = native_library(L"user32.dll"); if (auto fn = user32.get(user32_symbols::SetProcessDpiAwarenessContext)) { if (fn(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE)) { 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; } // 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) { if (auto pos = path.find_last_of(static_cast('\\')); 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, public ICoreWebView2NavigationCompletedEventHandler { 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); webview->add_NavigationCompleted(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; } HRESULT STDMETHODCALLTYPE Invoke(ICoreWebView2* sender, ICoreWebView2NavigationCompletedEventArgs* args) { PWSTR uri = nullptr; auto hr = sender->get_Source(&uri); if (SUCCEEDED(hr)) { auto curi = std::wstring_convert >().to_bytes(uri); if (navigateCallback) navigateCallback(curi, navigateCallbackArg); } CoTaskMemFree(uri); return hr; } // 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); } void STDMETHODCALLTYPE add_navigate_listener( std::function callback, void* arg) { navigateCallback = std::move(callback); navigateCallbackArg = arg; } 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; void* navigateCallbackArg = nullptr; std::function navigateCallback = 0; }; class win32_edge_engine { public: win32_edge_engine(bool debug, void* window) { if (!is_webview2_available()) { return; } if (!m_com_init.is_initialized()) { return; } enable_dpi_awareness(); if (window == nullptr) { HINSTANCE hInstance = GetModuleHandle(nullptr); HICON icon = (HICON)LoadImage(hInstance, IDI_APPLICATION, IMAGE_ICON, GetSystemMetrics(SM_CXICON), GetSystemMetrics(SM_CYICON), LR_DEFAULTCOLOR); 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 { auto w = (win32_edge_engine*)GetWindowLongPtr(hwnd, GWLP_USERDATA); switch (msg) { case WM_SIZE: w->resize(hwnd); break; case WM_CLOSE: DestroyWindow(hwnd); break; case WM_DESTROY: w->terminate(); break; case WM_GETMINMAXINFO: { auto lpmmi = (LPMINMAXINFO)lp; if (w == nullptr) { return 0; } 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; default: return DefWindowProcW(hwnd, msg, wp, lp); } return 0; }); RegisterClassExW(&wc); m_window = CreateWindowW(L"webview", L"", WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, 640, 480, nullptr, nullptr, hInstance, nullptr); if (m_window == nullptr) { return; } SetWindowLongPtr(m_window, GWLP_USERDATA, (LONG_PTR)this); } else { m_window = *(static_cast(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_window, debug, cb); resize(m_window); m_controller->MoveFocus(COREWEBVIEW2_MOVE_FOCUS_REASON_PROGRAMMATIC); } 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; } } 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() { MSG msg; BOOL res; while ((res = GetMessage(&msg, nullptr, 0, 0)) != -1) { if (msg.hwnd) { TranslateMessage(&msg); DispatchMessage(&msg); continue; } if (msg.message == WM_APP) { auto f = (dispatch_fn_t*)(msg.lParam); (*f)(); delete f; } else if (msg.message == WM_QUIT) { return; } } } void* window() { return (void*)m_window; } void terminate() { PostQuitMessage(0); } void dispatch(dispatch_fn_t f) { PostThreadMessage(m_main_thread, WM_APP, 0, (LPARAM) new dispatch_fn_t(f)); } void set_title(const std::string& title) { SetWindowTextW(m_window, widen_string(title).c_str()); } void set_size(int width, int height, int hints) { 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 { RECT r; r.left = r.top = 0; r.right = width; r.bottom = height; AdjustWindowRect(&r, WS_OVERLAPPEDWINDOW, 0); SetWindowPos(m_window, nullptr, r.left, r.top, r.right - r.left, r.bottom - r.top, SWP_NOZORDER | SWP_NOACTIVATE | SWP_NOMOVE | SWP_FRAMECHANGED); resize(m_window); } } void navigate(const std::string& url) { auto wurl = widen_string(url); m_webview->Navigate(wurl.c_str()); } void init(const std::string& js) { auto wjs = widen_string(js); m_webview->AddScriptToExecuteOnDocumentCreated(wjs.c_str(), nullptr); } void eval(const std::string& js) { auto wjs = widen_string(js); m_webview->ExecuteScript(wjs.c_str(), nullptr); } void add_navigate_listener(std::function callback, void* arg) { m_com_handler->add_navigate_listener(callback, arg); } void set_html(const std::string& html) { 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(); MSG msg = {}; while (flag.test_and_set() && GetMessage(&msg, nullptr, 0, 0)) { TranslateMessage(&msg); DispatchMessage(&msg); } 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; } init("window.external={invoke:s=>window.chrome.webview.postMessage(s)}"); return true; } void resize(HWND wnd) { if (m_controller == nullptr) { return; } RECT bounds; GetClientRect(wnd, &bounds); m_controller->put_Bounds(bounds); } 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; } virtual void on_message(const std::string& msg) = 0; // 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{ COINIT_APARTMENTTHREADED }; HWND m_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; }; } // namespace detail using browser_engine = detail::win32_edge_engine; } // namespace webview #endif /* WEBVIEW_GTK, WEBVIEW_COCOA, WEBVIEW_EDGE */ namespace webview { class webview : public browser_engine { public: webview(bool debug = false, void* wnd = nullptr) : browser_engine(debug, wnd) { } void navigate(const std::string& url) { if (url.empty()) { browser_engine::navigate("about:blank"); return; } browser_engine::navigate(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) { 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) { dispatch([seq, status, result, this]() { if (status == 0) { eval("window._rpc[" + seq + "].resolve(" + result + "); delete window._rpc[" + seq + "]"); } else { eval("window._rpc[" + seq + "].reject(" + result + "); delete window._rpc[" + seq + "]"); } }); } private: void on_message(const std::string& msg) override { auto seq = detail::json_parse(msg, "id", 0); auto name = detail::json_parse(msg, "method", 0); auto args = detail::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); } std::map bindings; }; } // 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_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, int 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() { return &webview::detail::library_version_info; } #endif /* WEBVIEW_HEADER */ #endif /* __cplusplus */ #endif /* WEBVIEW_H */