FreeRDP
All Data Structures Namespaces Files Functions Variables Typedefs Enumerations Enumerator Modules Pages
webview.h
1/*
2 * MIT License
3 *
4 * Copyright (c) 2017 Serge Zaitsev
5 * Copyright (c) 2022 Steffen André Langnes
6 *
7 * Permission is hereby granted, free of charge, to any person obtaining a copy
8 * of this software and associated documentation files (the "Software"), to deal
9 * in the Software without restriction, including without limitation the rights
10 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11 * copies of the Software, and to permit persons to whom the Software is
12 * furnished to do so, subject to the following conditions:
13 *
14 * The above copyright notice and this permission notice shall be included in
15 * all copies or substantial portions of the Software.
16 *
17 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
23 * SOFTWARE.
24 */
25#ifndef WEBVIEW_H
26#define WEBVIEW_H
27
28#ifndef WEBVIEW_API
29#define WEBVIEW_API extern
30#endif
31
32#ifndef WEBVIEW_VERSION_MAJOR
33// The current library major version.
34#define WEBVIEW_VERSION_MAJOR 0
35#endif
36
37#ifndef WEBVIEW_VERSION_MINOR
38// The current library minor version.
39#define WEBVIEW_VERSION_MINOR 10
40#endif
41
42#ifndef WEBVIEW_VERSION_PATCH
43// The current library patch version.
44#define WEBVIEW_VERSION_PATCH 0
45#endif
46
47#ifndef WEBVIEW_VERSION_PRE_RELEASE
48// SemVer 2.0.0 pre-release labels prefixed with "-".
49#define WEBVIEW_VERSION_PRE_RELEASE ""
50#endif
51
52#ifndef WEBVIEW_VERSION_BUILD_METADATA
53// SemVer 2.0.0 build metadata prefixed with "+".
54#define WEBVIEW_VERSION_BUILD_METADATA ""
55#endif
56
57// Utility macro for stringifying a macro argument.
58#define WEBVIEW_STRINGIFY(x) #x
59
60// Utility macro for stringifying the result of a macro argument expansion.
61#define WEBVIEW_EXPAND_AND_STRINGIFY(x) WEBVIEW_STRINGIFY(x)
62
63// SemVer 2.0.0 version number in MAJOR.MINOR.PATCH format.
64#define WEBVIEW_VERSION_NUMBER \
65 WEBVIEW_EXPAND_AND_STRINGIFY(WEBVIEW_VERSION_MAJOR) \
66 "." WEBVIEW_EXPAND_AND_STRINGIFY(WEBVIEW_VERSION_MINOR) "." WEBVIEW_EXPAND_AND_STRINGIFY( \
67 WEBVIEW_VERSION_PATCH)
68
69// Holds the elements of a MAJOR.MINOR.PATCH version number.
70typedef struct
71{
72 // Major version.
73 unsigned int major;
74 // Minor version.
75 unsigned int minor;
76 // Patch version.
77 unsigned int patch;
79
80// Holds the library's version information.
81typedef struct
82{
83 // The elements of the version number.
84 webview_version_t version;
85 // SemVer 2.0.0 version number in MAJOR.MINOR.PATCH format.
86 char version_number[32];
87 // SemVer 2.0.0 pre-release labels prefixed with "-" if specified, otherwise
88 // an empty string.
89 char pre_release[48];
90 // SemVer 2.0.0 build metadata prefixed with "+", otherwise an empty string.
91 char build_metadata[48];
93
94#ifdef __cplusplus
95extern "C"
96{
97#endif
98
99 typedef void* webview_t;
100
101 // Creates a new webview instance. If debug is non-zero - developer tools will
102 // be enabled (if the platform supports them). Window parameter can be a
103 // pointer to the native window handle. If it's non-null - then child WebView
104 // is embedded into the given parent window. Otherwise a new window is created.
105 // Depending on the platform, a GtkWindow, NSWindow or HWND pointer can be
106 // passed here. Returns null on failure. Creation can fail for various reasons
107 // such as when required runtime dependencies are missing or when window creation
108 // fails.
109 WEBVIEW_API webview_t webview_create(int debug, void* window);
110
111 // Destroys a webview and closes the native window.
112 WEBVIEW_API void webview_destroy(webview_t w);
113
114 // Runs the main loop until it's terminated. After this function exits - you
115 // must destroy the webview.
116 WEBVIEW_API void webview_run(webview_t w);
117
118 // Stops the main loop. It is safe to call this function from another other
119 // background thread.
120 WEBVIEW_API void webview_terminate(webview_t w);
121
122 // Posts a function to be executed on the main thread. You normally do not need
123 // to call this function, unless you want to tweak the native window.
124 WEBVIEW_API void webview_dispatch(webview_t w, void (*fn)(webview_t w, void* arg), void* arg);
125
126 // Returns a native window handle pointer. When using GTK backend the pointer
127 // is GtkWindow pointer, when using Cocoa backend the pointer is NSWindow
128 // pointer, when using Win32 backend the pointer is HWND pointer.
129 WEBVIEW_API void* webview_get_window(webview_t w);
130
131 // Updates the title of the native window. Must be called from the UI thread.
132 WEBVIEW_API void webview_set_title(webview_t w, const char* title);
133
134// Window size hints
135#define WEBVIEW_HINT_NONE 0 // Width and height are default size
136#define WEBVIEW_HINT_MIN 1 // Width and height are minimum bounds
137#define WEBVIEW_HINT_MAX 2 // Width and height are maximum bounds
138#define WEBVIEW_HINT_FIXED 3 // Window size can not be changed by a user
139 // Updates native window size. See WEBVIEW_HINT constants.
140 WEBVIEW_API void webview_set_size(webview_t w, int width, int height, int hints);
141
142 // Navigates webview to the given URL. URL may be a properly encoded data URI.
143 // Examples:
144 // webview_navigate(w, "https://github.com/webview/webview");
145 // webview_navigate(w, "data:text/html,%3Ch1%3EHello%3C%2Fh1%3E");
146 // webview_navigate(w, "data:text/html;base64,PGgxPkhlbGxvPC9oMT4=");
147 WEBVIEW_API void webview_navigate(webview_t w, const char* url);
148
149 // Set webview HTML directly.
150 // Example: webview_set_html(w, "<h1>Hello</h1>");
151 WEBVIEW_API void webview_set_html(webview_t w, const char* html);
152
153 // Injects JavaScript code at the initialization of the new page. Every time
154 // the webview will open a the new page - this initialization code will be
155 // executed. It is guaranteed that code is executed before window.onload.
156 WEBVIEW_API void webview_init(webview_t w, const char* js);
157
158 // Evaluates arbitrary JavaScript code. Evaluation happens asynchronously, also
159 // the result of the expression is ignored. Use RPC bindings if you want to
160 // receive notifications about the results of the evaluation.
161 WEBVIEW_API void webview_eval(webview_t w, const char* js);
162
163 // Binds a native C callback so that it will appear under the given name as a
164 // global JavaScript function. Internally it uses webview_init(). Callback
165 // receives a request string and a user-provided argument pointer. Request
166 // string is a JSON array of all the arguments passed to the JavaScript
167 // function.
168 WEBVIEW_API void webview_bind(webview_t w, const char* name,
169 void (*fn)(const char* seq, const char* req, void* arg),
170 void* arg);
171
172 // Removes a native C callback that was previously set by webview_bind.
173 WEBVIEW_API void webview_unbind(webview_t w, const char* name);
174
175 // Allows to return a value from the native binding. Original request pointer
176 // must be provided to help internal RPC engine match requests with responses.
177 // If status is zero - result is expected to be a valid JSON result value.
178 // If status is not zero - result is an error JSON object.
179 WEBVIEW_API void webview_return(webview_t w, const char* seq, int status, const char* result);
180
181 // Get the library's version information.
182 // @since 0.10
183 WEBVIEW_API const webview_version_info_t* webview_version();
184
185#ifdef __cplusplus
186}
187
188#ifndef WEBVIEW_HEADER
189
190#if !defined(WEBVIEW_GTK) && !defined(WEBVIEW_COCOA) && !defined(WEBVIEW_EDGE)
191#if defined(__APPLE__)
192#define WEBVIEW_COCOA
193#elif defined(__unix__)
194#define WEBVIEW_GTK
195#elif defined(_WIN32)
196#define WEBVIEW_EDGE
197#else
198#error "please, specify webview backend"
199#endif
200#endif
201
202#ifndef WEBVIEW_DEPRECATED
203#if __cplusplus >= 201402L
204#define WEBVIEW_DEPRECATED(reason) [[deprecated(reason)]]
205#elif defined(_MSC_VER)
206#define WEBVIEW_DEPRECATED(reason) __declspec(deprecated(reason))
207#else
208#define WEBVIEW_DEPRECATED(reason) __attribute__((deprecated(reason)))
209#endif
210#endif
211
212#ifndef WEBVIEW_DEPRECATED_PRIVATE
213#define WEBVIEW_DEPRECATED_PRIVATE WEBVIEW_DEPRECATED("Private API should not be used")
214#endif
215
216#include <array>
217#include <atomic>
218#include <functional>
219#include <future>
220#include <map>
221#include <string>
222#include <utility>
223#include <vector>
224#include <locale>
225#include <codecvt>
226#include <cstring>
227
228namespace webview
229{
230
231 using dispatch_fn_t = std::function<void()>;
232
233 namespace detail
234 {
235
236 // The library's version information.
237 constexpr const webview_version_info_t library_version_info{
238 { WEBVIEW_VERSION_MAJOR, WEBVIEW_VERSION_MINOR, WEBVIEW_VERSION_PATCH },
239 WEBVIEW_VERSION_NUMBER,
240 WEBVIEW_VERSION_PRE_RELEASE,
241 WEBVIEW_VERSION_BUILD_METADATA
242 };
243
244 inline int json_parse_c(const char* s, size_t sz, const char* key, size_t keysz,
245 const char** value, size_t* valuesz)
246 {
247 enum
248 {
249 JSON_STATE_VALUE,
250 JSON_STATE_LITERAL,
251 JSON_STATE_STRING,
252 JSON_STATE_ESCAPE,
253 JSON_STATE_UTF8
254 } state = JSON_STATE_VALUE;
255 const char* k = nullptr;
256 int index = 1;
257 int depth = 0;
258 int utf8_bytes = 0;
259
260 *value = nullptr;
261 *valuesz = 0;
262
263 if (key == nullptr)
264 {
265 index = static_cast<decltype(index)>(keysz);
266 if (index < 0)
267 {
268 return -1;
269 }
270 keysz = 0;
271 }
272
273 for (; sz > 0; s++, sz--)
274 {
275 enum
276 {
277 JSON_ACTION_NONE,
278 JSON_ACTION_START,
279 JSON_ACTION_END,
280 JSON_ACTION_START_STRUCT,
281 JSON_ACTION_END_STRUCT
282 } action = JSON_ACTION_NONE;
283 auto c = static_cast<unsigned char>(*s);
284 switch (state)
285 {
286 case JSON_STATE_VALUE:
287 if (c == ' ' || c == '\t' || c == '\n' || c == '\r' || c == ',' || c == ':')
288 {
289 continue;
290 }
291 else if (c == '"')
292 {
293 action = JSON_ACTION_START;
294 state = JSON_STATE_STRING;
295 }
296 else if (c == '{' || c == '[')
297 {
298 action = JSON_ACTION_START_STRUCT;
299 }
300 else if (c == '}' || c == ']')
301 {
302 action = JSON_ACTION_END_STRUCT;
303 }
304 else if (c == 't' || c == 'f' || c == 'n' || c == '-' ||
305 (c >= '0' && c <= '9'))
306 {
307 action = JSON_ACTION_START;
308 state = JSON_STATE_LITERAL;
309 }
310 else
311 {
312 return -1;
313 }
314 break;
315 case JSON_STATE_LITERAL:
316 if (c == ' ' || c == '\t' || c == '\n' || c == '\r' || c == ',' ||
317 c == ']' || c == '}' || c == ':')
318 {
319 state = JSON_STATE_VALUE;
320 s--;
321 sz++;
322 action = JSON_ACTION_END;
323 }
324 else if (c < 32 || c > 126)
325 {
326 return -1;
327 } // fallthrough
328 case JSON_STATE_STRING:
329 if (c < 32 || (c > 126 && c < 192))
330 {
331 return -1;
332 }
333 else if (c == '"')
334 {
335 action = JSON_ACTION_END;
336 state = JSON_STATE_VALUE;
337 }
338 else if (c == '\\')
339 {
340 state = JSON_STATE_ESCAPE;
341 }
342 else if (c >= 192 && c < 224)
343 {
344 utf8_bytes = 1;
345 state = JSON_STATE_UTF8;
346 }
347 else if (c >= 224 && c < 240)
348 {
349 utf8_bytes = 2;
350 state = JSON_STATE_UTF8;
351 }
352 else if (c >= 240 && c < 247)
353 {
354 utf8_bytes = 3;
355 state = JSON_STATE_UTF8;
356 }
357 else if (c >= 128 && c < 192)
358 {
359 return -1;
360 }
361 break;
362 case JSON_STATE_ESCAPE:
363 if (c == '"' || c == '\\' || c == '/' || c == 'b' || c == 'f' || c == 'n' ||
364 c == 'r' || c == 't' || c == 'u')
365 {
366 state = JSON_STATE_STRING;
367 }
368 else
369 {
370 return -1;
371 }
372 break;
373 case JSON_STATE_UTF8:
374 if (c < 128 || c > 191)
375 {
376 return -1;
377 }
378 utf8_bytes--;
379 if (utf8_bytes == 0)
380 {
381 state = JSON_STATE_STRING;
382 }
383 break;
384 default:
385 return -1;
386 }
387
388 if (action == JSON_ACTION_END_STRUCT)
389 {
390 depth--;
391 }
392
393 if (depth == 1)
394 {
395 if (action == JSON_ACTION_START || action == JSON_ACTION_START_STRUCT)
396 {
397 if (index == 0)
398 {
399 *value = s;
400 }
401 else if (keysz > 0 && index == 1)
402 {
403 k = s;
404 }
405 else
406 {
407 index--;
408 }
409 }
410 else if (action == JSON_ACTION_END || action == JSON_ACTION_END_STRUCT)
411 {
412 if (*value != nullptr && index == 0)
413 {
414 *valuesz = (size_t)(s + 1 - *value);
415 return 0;
416 }
417 else if (keysz > 0 && k != nullptr)
418 {
419 if (keysz == (size_t)(s - k - 1) && memcmp(key, k + 1, keysz) == 0)
420 {
421 index = 0;
422 }
423 else
424 {
425 index = 2;
426 }
427 k = nullptr;
428 }
429 }
430 }
431
432 if (action == JSON_ACTION_START_STRUCT)
433 {
434 depth++;
435 }
436 }
437 return -1;
438 }
439
440 inline std::string json_escape(const std::string& s)
441 {
442 // TODO: implement
443 return '"' + s + '"';
444 }
445
446 inline int json_unescape(const char* s, size_t n, char* out)
447 {
448 int r = 0;
449 if (*s++ != '"')
450 {
451 return -1;
452 }
453 while (n > 2)
454 {
455 char c = *s;
456 if (c == '\\')
457 {
458 s++;
459 n--;
460 switch (*s)
461 {
462 case 'b':
463 c = '\b';
464 break;
465 case 'f':
466 c = '\f';
467 break;
468 case 'n':
469 c = '\n';
470 break;
471 case 'r':
472 c = '\r';
473 break;
474 case 't':
475 c = '\t';
476 break;
477 case '\\':
478 c = '\\';
479 break;
480 case '/':
481 c = '/';
482 break;
483 case '\"':
484 c = '\"';
485 break;
486 default: // TODO: support unicode decoding
487 return -1;
488 }
489 }
490 if (out != nullptr)
491 {
492 *out++ = c;
493 }
494 s++;
495 n--;
496 r++;
497 }
498 if (*s != '"')
499 {
500 return -1;
501 }
502 if (out != nullptr)
503 {
504 *out = '\0';
505 }
506 return r;
507 }
508
509 inline std::string json_parse(const std::string& s, const std::string& key, const int index)
510 {
511 const char* value = nullptr;
512 size_t value_sz = 0;
513 if (key.empty())
514 {
515 json_parse_c(s.c_str(), s.length(), nullptr, index, &value, &value_sz);
516 }
517 else
518 {
519 json_parse_c(s.c_str(), s.length(), key.c_str(), key.length(), &value, &value_sz);
520 }
521 if (value != nullptr)
522 {
523 if (value[0] != '"')
524 {
525 return { value, value_sz };
526 }
527 int n = json_unescape(value, value_sz, nullptr);
528 if (n > 0)
529 {
530 char* decoded = new char[1ull + n];
531 json_unescape(value, value_sz, decoded);
532 std::string result(decoded, n);
533 delete[] decoded;
534 return result;
535 }
536 }
537 return "";
538 }
539
540 } // namespace detail
541
542 WEBVIEW_DEPRECATED_PRIVATE
543 inline int json_parse_c(const char* s, size_t sz, const char* key, size_t keysz,
544 const char** value, size_t* valuesz)
545 {
546 return detail::json_parse_c(s, sz, key, keysz, value, valuesz);
547 }
548
549 WEBVIEW_DEPRECATED_PRIVATE
550 inline std::string json_escape(const std::string& s)
551 {
552 return detail::json_escape(s);
553 }
554
555 WEBVIEW_DEPRECATED_PRIVATE
556 inline int json_unescape(const char* s, size_t n, char* out)
557 {
558 return detail::json_unescape(s, n, out);
559 }
560
561 WEBVIEW_DEPRECATED_PRIVATE
562 inline std::string json_parse(const std::string& s, const std::string& key, const int index)
563 {
564 return detail::json_parse(s, key, index);
565 }
566
567} // namespace webview
568
569#if defined(WEBVIEW_GTK)
570//
571// ====================================================================
572//
573// This implementation uses webkit2gtk backend. It requires gtk+3.0 and
574// webkit2gtk-4.0 libraries. Proper compiler flags can be retrieved via:
575//
576// pkg-config --cflags --libs gtk+-3.0 webkit2gtk-4.0
577//
578// ====================================================================
579//
580#include <JavaScriptCore/JavaScript.h>
581#include <gtk/gtk.h>
582#include <webkit2/webkit2.h>
583
584namespace webview
585{
586 namespace detail
587 {
588
589 class gtk_webkit_engine
590 {
591 public:
592 gtk_webkit_engine(bool debug, void* window) : m_window(static_cast<GtkWidget*>(window))
593 {
594 if (gtk_init_check(nullptr, nullptr) == FALSE)
595 {
596 return;
597 }
598 m_window = static_cast<GtkWidget*>(window);
599 if (m_window == nullptr)
600 {
601 m_window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
602 }
603 g_signal_connect(G_OBJECT(m_window), "destroy",
604 G_CALLBACK(+[](GtkWidget*, gpointer arg)
605 { static_cast<gtk_webkit_engine*>(arg)->terminate(); }),
606 this);
607 // Initialize webview widget
608 m_webview = webkit_web_view_new();
609 WebKitUserContentManager* manager =
610 webkit_web_view_get_user_content_manager(WEBKIT_WEB_VIEW(m_webview));
611 g_signal_connect(manager, "script-message-received::external",
612 G_CALLBACK(+[](WebKitUserContentManager*,
613 WebKitJavascriptResult* r, gpointer arg)
614 {
615 auto* w = static_cast<gtk_webkit_engine*>(arg);
616 char* s = get_string_from_js_result(r);
617 w->on_message(s);
618 g_free(s);
619 }),
620 this);
621 webkit_user_content_manager_register_script_message_handler(manager, "external");
622 init("window.external={invoke:function(s){window.webkit.messageHandlers."
623 "external.postMessage(s);}}");
624
625 gtk_container_add(GTK_CONTAINER(m_window), GTK_WIDGET(m_webview));
626 gtk_widget_grab_focus(GTK_WIDGET(m_webview));
627
628 WebKitSettings* settings = webkit_web_view_get_settings(WEBKIT_WEB_VIEW(m_webview));
629 webkit_settings_set_javascript_can_access_clipboard(settings, true);
630 if (debug)
631 {
632 webkit_settings_set_enable_write_console_messages_to_stdout(settings, true);
633 webkit_settings_set_enable_developer_extras(settings, true);
634 }
635
636 gtk_widget_show_all(m_window);
637 }
638 virtual ~gtk_webkit_engine() = default;
639 void* window()
640 {
641 return (void*)m_window;
642 }
643 void run()
644 {
645 gtk_main();
646 }
647 void terminate()
648 {
649 gtk_main_quit();
650 }
651 void dispatch(std::function<void()> f)
652 {
653 g_idle_add_full(G_PRIORITY_HIGH_IDLE, (GSourceFunc)([](void* f) -> int {
654 (*static_cast<dispatch_fn_t*>(f))();
655 return G_SOURCE_REMOVE;
656 }),
657 new std::function<void()>(f),
658 [](void* f) { delete static_cast<dispatch_fn_t*>(f); });
659 }
660
661 void set_title(const std::string& title)
662 {
663 gtk_window_set_title(GTK_WINDOW(m_window), title.c_str());
664 }
665
666 void set_size(int width, int height, int hints)
667 {
668 gtk_window_set_resizable(GTK_WINDOW(m_window), hints != WEBVIEW_HINT_FIXED);
669 if (hints == WEBVIEW_HINT_NONE)
670 {
671 gtk_window_resize(GTK_WINDOW(m_window), width, height);
672 }
673 else if (hints == WEBVIEW_HINT_FIXED)
674 {
675 gtk_widget_set_size_request(m_window, width, height);
676 }
677 else
678 {
679 GdkGeometry g;
680 g.min_width = g.max_width = width;
681 g.min_height = g.max_height = height;
682 GdkWindowHints h =
683 (hints == WEBVIEW_HINT_MIN ? GDK_HINT_MIN_SIZE : GDK_HINT_MAX_SIZE);
684 // This defines either MIN_SIZE, or MAX_SIZE, but not both:
685 gtk_window_set_geometry_hints(GTK_WINDOW(m_window), nullptr, &g, h);
686 }
687 }
688
689 void navigate(const std::string& url)
690 {
691 webkit_web_view_load_uri(WEBKIT_WEB_VIEW(m_webview), url.c_str());
692 }
693
694 void add_navigate_listener(std::function<void(const std::string&, void*)> callback,
695 void* arg)
696 {
697 g_signal_connect(WEBKIT_WEB_VIEW(m_webview), "load-changed",
698 G_CALLBACK(on_load_changed), this);
699 navigateCallbackArg = arg;
700 navigateCallback = std::move(callback);
701 }
702
703 void add_scheme_handler(const std::string& scheme,
704 std::function<void(const std::string&, void*)> callback,
705 void* arg)
706 {
707 auto view = WEBKIT_WEB_VIEW(m_webview);
708 auto context = webkit_web_view_get_context(view);
709
710 scheme_handlers.insert({ scheme, { .arg = arg, .fkt = callback } });
711 webkit_web_context_register_uri_scheme(context, scheme.c_str(), scheme_handler,
712 static_cast<gpointer>(this), nullptr);
713 }
714
715 void set_html(const std::string& html)
716 {
717 webkit_web_view_load_html(WEBKIT_WEB_VIEW(m_webview), html.c_str(), nullptr);
718 }
719
720 void init(const std::string& js)
721 {
722 WebKitUserContentManager* manager =
723 webkit_web_view_get_user_content_manager(WEBKIT_WEB_VIEW(m_webview));
724 webkit_user_content_manager_add_script(
725 manager, webkit_user_script_new(
726 js.c_str(), WEBKIT_USER_CONTENT_INJECT_TOP_FRAME,
727 WEBKIT_USER_SCRIPT_INJECT_AT_DOCUMENT_START, nullptr, nullptr));
728 }
729
730 void eval(const std::string& js)
731 {
732 webkit_web_view_run_javascript(WEBKIT_WEB_VIEW(m_webview), js.c_str(), nullptr,
733 nullptr, nullptr);
734 }
735
736 private:
737 virtual void on_message(const std::string& msg) = 0;
738
739 struct handler_t
740 {
741 void* arg;
742 std::function<void(const std::string&, void*)> fkt;
743 };
744
745 std::map<std::string, handler_t> scheme_handlers;
746
747 void scheme_handler_call(const std::string& scheme, const std::string& url)
748 {
749 auto handler = scheme_handlers.find(scheme);
750 if (handler != scheme_handlers.end())
751 {
752 const auto& arg = handler->second;
753 arg.fkt(url, arg.arg);
754 }
755 }
756
757 static void scheme_handler(WebKitURISchemeRequest* request, gpointer user_data)
758 {
759 auto _this = static_cast<gtk_webkit_engine*>(user_data);
760
761 auto scheme = webkit_uri_scheme_request_get_scheme(request);
762 auto uri = webkit_uri_scheme_request_get_uri(request);
763 _this->scheme_handler_call(scheme, uri);
764 }
765
766 static char* get_string_from_js_result(WebKitJavascriptResult* r)
767 {
768 char* s = nullptr;
769#if WEBKIT_MAJOR_VERSION >= 2 && WEBKIT_MINOR_VERSION >= 22
770 JSCValue* value = webkit_javascript_result_get_js_value(r);
771 s = jsc_value_to_string(value);
772#else
773 JSGlobalContextRef ctx = webkit_javascript_result_get_global_context(r);
774 JSValueRef value = webkit_javascript_result_get_value(r);
775 JSStringRef js = JSValueToStringCopy(ctx, value, nullptr);
776 size_t n = JSStringGetMaximumUTF8CStringSize(js);
777 s = g_new(char, n);
778 JSStringGetUTF8CString(js, s, n);
779 JSStringRelease(js);
780#endif
781 return s;
782 }
783
784 GtkWidget* m_window;
785 GtkWidget* m_webview;
786
787 void* navigateCallbackArg = nullptr;
788 std::function<void(const std::string&, void*)> navigateCallback = nullptr;
789
790 static void on_load_changed(WebKitWebView* web_view, WebKitLoadEvent load_event,
791 gpointer arg)
792 {
793 if (load_event == WEBKIT_LOAD_FINISHED)
794 {
795 auto inst = static_cast<gtk_webkit_engine*>(arg);
796 inst->navigateCallback(webkit_web_view_get_uri(web_view),
797 inst->navigateCallbackArg);
798 }
799 }
800 };
801
802 } // namespace detail
803
804 using browser_engine = detail::gtk_webkit_engine;
805
806} // namespace webview
807
808#elif defined(WEBVIEW_COCOA)
809
810//
811// ====================================================================
812//
813// This implementation uses Cocoa WKWebView backend on macOS. It is
814// written using ObjC runtime and uses WKWebView class as a browser runtime.
815// You should pass "-framework Webkit" flag to the compiler.
816//
817// ====================================================================
818//
819
820#include <CoreGraphics/CoreGraphics.h>
821#include <objc/NSObjCRuntime.h>
822#include <objc/objc-runtime.h>
823
824namespace webview
825{
826 namespace detail
827 {
828 namespace objc
829 {
830
831 // A convenient template function for unconditionally casting the specified
832 // C-like function into a function that can be called with the given return
833 // type and arguments. Caller takes full responsibility for ensuring that
834 // the function call is valid. It is assumed that the function will not
835 // throw exceptions.
836 template <typename Result, typename Callable, typename... Args>
837 Result invoke(Callable callable, Args... args) noexcept
838 {
839 return reinterpret_cast<Result (*)(Args...)>(callable)(args...);
840 }
841
842 // Calls objc_msgSend.
843 template <typename Result, typename... Args> Result msg_send(Args... args) noexcept
844 {
845 return invoke<Result>(objc_msgSend, args...);
846 }
847
848 } // namespace objc
849
850 enum NSBackingStoreType : NSUInteger
851 {
852 NSBackingStoreBuffered = 2
853 };
854
855 enum NSWindowStyleMask : NSUInteger
856 {
857 NSWindowStyleMaskTitled = 1,
858 NSWindowStyleMaskClosable = 2,
859 NSWindowStyleMaskMiniaturizable = 4,
860 NSWindowStyleMaskResizable = 8
861 };
862
863 enum NSApplicationActivationPolicy : NSInteger
864 {
865 NSApplicationActivationPolicyRegular = 0
866 };
867
868 enum WKUserScriptInjectionTime : NSInteger
869 {
870 WKUserScriptInjectionTimeAtDocumentStart = 0
871 };
872
873 enum NSModalResponse : NSInteger
874 {
875 NSModalResponseOK = 1
876 };
877
878 // Convenient conversion of string literals.
879 inline id operator"" _cls(const char* s, std::size_t)
880 {
881 return (id)objc_getClass(s);
882 }
883 inline SEL operator"" _sel(const char* s, std::size_t)
884 {
885 return sel_registerName(s);
886 }
887 inline id operator"" _str(const char* s, std::size_t)
888 {
889 return objc::msg_send<id>("NSString"_cls, "stringWithUTF8String:"_sel, s);
890 }
891
892 class cocoa_wkwebview_engine
893 {
894 public:
895 cocoa_wkwebview_engine(bool debug, void* window)
896 : m_debug{ debug }, m_parent_window{ window }
897 {
898 auto app = get_shared_application();
899 auto delegate = create_app_delegate();
900 objc_setAssociatedObject(delegate, "webview", (id)this, OBJC_ASSOCIATION_ASSIGN);
901 objc::msg_send<void>(app, "setDelegate:"_sel, delegate);
902
903 // See comments related to application lifecycle in create_app_delegate().
904 if (window)
905 {
906 on_application_did_finish_launching(delegate, app);
907 }
908 else
909 {
910 // Start the main run loop so that the app delegate gets the
911 // NSApplicationDidFinishLaunchingNotification notification after the run
912 // loop has started in order to perform further initialization.
913 // We need to return from this constructor so this run loop is only
914 // temporary.
915 objc::msg_send<void>(app, "run"_sel);
916 }
917 }
918 virtual ~cocoa_wkwebview_engine() = default;
919 void* window()
920 {
921 return (void*)m_window;
922 }
923 void terminate()
924 {
925 auto app = get_shared_application();
926 objc::msg_send<void>(app, "terminate:"_sel, nullptr);
927 }
928 void run()
929 {
930 auto app = get_shared_application();
931 objc::msg_send<void>(app, "run"_sel);
932 }
933 void dispatch(std::function<void()> f)
934 {
935 dispatch_async_f(dispatch_get_main_queue(), new dispatch_fn_t(f),
936 (dispatch_function_t)([](void* arg) {
937 auto f = static_cast<dispatch_fn_t*>(arg);
938 (*f)();
939 delete f;
940 }));
941 }
942 void set_title(const std::string& title)
943 {
944 objc::msg_send<void>(
945 m_window, "setTitle:"_sel,
946 objc::msg_send<id>("NSString"_cls, "stringWithUTF8String:"_sel, title.c_str()));
947 }
948 void set_size(int width, int height, int hints)
949 {
950 auto style = static_cast<NSWindowStyleMask>(NSWindowStyleMaskTitled |
951 NSWindowStyleMaskClosable |
952 NSWindowStyleMaskMiniaturizable);
953 if (hints != WEBVIEW_HINT_FIXED)
954 {
955 style = static_cast<NSWindowStyleMask>(style | NSWindowStyleMaskResizable);
956 }
957 objc::msg_send<void>(m_window, "setStyleMask:"_sel, style);
958
959 if (hints == WEBVIEW_HINT_MIN)
960 {
961 objc::msg_send<void>(m_window, "setContentMinSize:"_sel,
962 CGSizeMake(width, height));
963 }
964 else if (hints == WEBVIEW_HINT_MAX)
965 {
966 objc::msg_send<void>(m_window, "setContentMaxSize:"_sel,
967 CGSizeMake(width, height));
968 }
969 else
970 {
971 objc::msg_send<void>(m_window, "setFrame:display:animate:"_sel,
972 CGRectMake(0, 0, width, height), YES, NO);
973 }
974 objc::msg_send<void>(m_window, "center"_sel);
975 }
976 void navigate(const std::string& url)
977 {
978 auto nsurl = objc::msg_send<id>(
979 "NSURL"_cls, "URLWithString:"_sel,
980 objc::msg_send<id>("NSString"_cls, "stringWithUTF8String:"_sel, url.c_str()));
981
982 objc::msg_send<void>(
983 m_webview, "loadRequest:"_sel,
984 objc::msg_send<id>("NSURLRequest"_cls, "requestWithURL:"_sel, nsurl));
985 }
986
987 void add_navigate_listener(std::function<void(const std::string&, void*)> callback,
988 void* arg)
989 {
990 m_navigateCallback = callback;
991 m_navigateCallbackArg = arg;
992 }
993
994 void add_scheme_handler(const std::string& scheme,
995 std::function<void(const std::string&, void*)> callback,
996 void* arg)
997 {
998 // TODO: Implement
999 }
1000
1001 void set_html(const std::string& html)
1002 {
1003 objc::msg_send<void>(
1004 m_webview, "loadHTMLString:baseURL:"_sel,
1005 objc::msg_send<id>("NSString"_cls, "stringWithUTF8String:"_sel, html.c_str()),
1006 nullptr);
1007 }
1008 void init(const std::string& js)
1009 {
1010 // Equivalent Obj-C:
1011 // [m_manager addUserScript:[[WKUserScript alloc] initWithSource:[NSString
1012 // stringWithUTF8String:js.c_str()]
1013 // injectionTime:WKUserScriptInjectionTimeAtDocumentStart forMainFrameOnly:YES]]
1014 objc::msg_send<void>(
1015 m_manager, "addUserScript:"_sel,
1016 objc::msg_send<id>(
1017 objc::msg_send<id>("WKUserScript"_cls, "alloc"_sel),
1018 "initWithSource:injectionTime:forMainFrameOnly:"_sel,
1019 objc::msg_send<id>("NSString"_cls, "stringWithUTF8String:"_sel, js.c_str()),
1020 WKUserScriptInjectionTimeAtDocumentStart, YES));
1021 }
1022 void eval(const std::string& js)
1023 {
1024 objc::msg_send<void>(
1025 m_webview, "evaluateJavaScript:completionHandler:"_sel,
1026 objc::msg_send<id>("NSString"_cls, "stringWithUTF8String:"_sel, js.c_str()),
1027 nullptr);
1028 }
1029
1030 private:
1031 virtual void on_message(const std::string& msg) = 0;
1032 id create_app_delegate()
1033 {
1034 // Note: Avoid registering the class name "AppDelegate" as it is the
1035 // default name in projects created with Xcode, and using the same name
1036 // causes objc_registerClassPair to crash.
1037 auto cls =
1038 objc_allocateClassPair((Class) "NSResponder"_cls, "WebviewAppDelegate", 0);
1039 class_addProtocol(cls, objc_getProtocol("NSTouchBarProvider"));
1040 class_addMethod(cls, "applicationShouldTerminateAfterLastWindowClosed:"_sel,
1041 (IMP)(+[](id, SEL, id) -> BOOL { return 1; }), "c@:@");
1042 // If the library was not initialized with an existing window then the user
1043 // is likely managing the application lifecycle and we would not get the
1044 // "applicationDidFinishLaunching:" message and therefore do not need to
1045 // add this method.
1046 if (!m_parent_window)
1047 {
1048 class_addMethod(cls, "applicationDidFinishLaunching:"_sel,
1049 (IMP)(+[](id self, SEL, id notification)
1050 {
1051 auto app =
1052 objc::msg_send<id>(notification, "object"_sel);
1053 auto w = get_associated_webview(self);
1054 w->on_application_did_finish_launching(self, app);
1055 }),
1056 "v@:@");
1057 }
1058 objc_registerClassPair(cls);
1059 return objc::msg_send<id>((id)cls, "new"_sel);
1060 }
1061 id create_script_message_handler()
1062 {
1063 auto cls = objc_allocateClassPair((Class) "NSResponder"_cls,
1064 "WebkitScriptMessageHandler", 0);
1065 class_addProtocol(cls, objc_getProtocol("WKScriptMessageHandler"));
1066 class_addMethod(
1067 cls, "userContentController:didReceiveScriptMessage:"_sel,
1068 (IMP)(+[](id self, SEL, id, id msg)
1069 {
1070 auto w = get_associated_webview(self);
1071 w->on_message(objc::msg_send<const char*>(
1072 objc::msg_send<id>(msg, "body"_sel), "UTF8String"_sel));
1073 }),
1074 "v@:@@");
1075 objc_registerClassPair(cls);
1076 auto instance = objc::msg_send<id>((id)cls, "new"_sel);
1077 objc_setAssociatedObject(instance, "webview", (id)this, OBJC_ASSOCIATION_ASSIGN);
1078 return instance;
1079 }
1080 static id create_webkit_ui_delegate()
1081 {
1082 auto cls = objc_allocateClassPair((Class) "NSObject"_cls, "WebkitUIDelegate", 0);
1083 class_addProtocol(cls, objc_getProtocol("WKUIDelegate"));
1084 class_addMethod(
1085 cls,
1086 "webView:runOpenPanelWithParameters:initiatedByFrame:completionHandler:"_sel,
1087 (IMP)(+[](id, SEL, id, id parameters, id, id completion_handler)
1088 {
1089 auto allows_multiple_selection =
1090 objc::msg_send<BOOL>(parameters, "allowsMultipleSelection"_sel);
1091 auto allows_directories =
1092 objc::msg_send<BOOL>(parameters, "allowsDirectories"_sel);
1093
1094 // Show a panel for selecting files.
1095 auto panel = objc::msg_send<id>("NSOpenPanel"_cls, "openPanel"_sel);
1096 objc::msg_send<void>(panel, "setCanChooseFiles:"_sel, YES);
1097 objc::msg_send<void>(panel, "setCanChooseDirectories:"_sel,
1098 allows_directories);
1099 objc::msg_send<void>(panel, "setAllowsMultipleSelection:"_sel,
1100 allows_multiple_selection);
1101 auto modal_response =
1102 objc::msg_send<NSModalResponse>(panel, "runModal"_sel);
1103
1104 // Get the URLs for the selected files. If the modal was canceled
1105 // then we pass null to the completion handler to signify
1106 // cancellation.
1107 id urls = modal_response == NSModalResponseOK
1108 ? objc::msg_send<id>(panel, "URLs"_sel)
1109 : nullptr;
1110
1111 // Invoke the completion handler block.
1112 auto sig = objc::msg_send<id>("NSMethodSignature"_cls,
1113 "signatureWithObjCTypes:"_sel, "v@?@");
1114 auto invocation = objc::msg_send<id>(
1115 "NSInvocation"_cls, "invocationWithMethodSignature:"_sel, sig);
1116 objc::msg_send<void>(invocation, "setTarget:"_sel,
1117 completion_handler);
1118 objc::msg_send<void>(invocation, "setArgument:atIndex:"_sel, &urls,
1119 1);
1120 objc::msg_send<void>(invocation, "invoke"_sel);
1121 }),
1122 "v@:@@@@");
1123 objc_registerClassPair(cls);
1124 return objc::msg_send<id>((id)cls, "new"_sel);
1125 }
1126 id create_webkit_navigation_delegate()
1127 {
1128 auto cls =
1129 objc_allocateClassPair((Class) "NSObject"_cls, "WebkitNavigationDelegate", 0);
1130 class_addProtocol(cls, objc_getProtocol("WKNavigationDelegate"));
1131 class_addMethod(cls, "webView:didFinishNavigation:"_sel,
1132 (IMP)(+[](id delegate, SEL sel, id webview, id navigation)
1133 {
1134 auto w = get_associated_webview(delegate);
1135 auto url = objc::msg_send<id>(webview, "URL"_sel);
1136 auto nstr = objc::msg_send<id>(url, "absoluteString"_sel);
1137 auto str = objc::msg_send<char*>(nstr, "UTF8String"_sel);
1138 w->m_navigateCallback(str, w->m_navigateCallbackArg);
1139 }),
1140 "v@:@");
1141 objc_registerClassPair(cls);
1142 auto instance = objc::msg_send<id>((id)cls, "new"_sel);
1143 objc_setAssociatedObject(instance, "webview", (id)this, OBJC_ASSOCIATION_ASSIGN);
1144 return instance;
1145 }
1146 static id get_shared_application()
1147 {
1148 return objc::msg_send<id>("NSApplication"_cls, "sharedApplication"_sel);
1149 }
1150 static cocoa_wkwebview_engine* get_associated_webview(id object)
1151 {
1152 auto w = (cocoa_wkwebview_engine*)objc_getAssociatedObject(object, "webview");
1153 assert(w);
1154 return w;
1155 }
1156 static id get_main_bundle() noexcept
1157 {
1158 return objc::msg_send<id>("NSBundle"_cls, "mainBundle"_sel);
1159 }
1160 static bool is_app_bundled() noexcept
1161 {
1162 auto bundle = get_main_bundle();
1163 if (!bundle)
1164 {
1165 return false;
1166 }
1167 auto bundle_path = objc::msg_send<id>(bundle, "bundlePath"_sel);
1168 auto bundled = objc::msg_send<BOOL>(bundle_path, "hasSuffix:"_sel, ".app"_str);
1169 return !!bundled;
1170 }
1171 void on_application_did_finish_launching(id /*delegate*/, id app)
1172 {
1173 // See comments related to application lifecycle in create_app_delegate().
1174 if (!m_parent_window)
1175 {
1176 // Stop the main run loop so that we can return
1177 // from the constructor.
1178 objc::msg_send<void>(app, "stop:"_sel, nullptr);
1179 }
1180
1181 // Activate the app if it is not bundled.
1182 // Bundled apps launched from Finder are activated automatically but
1183 // otherwise not. Activating the app even when it has been launched from
1184 // Finder does not seem to be harmful but calling this function is rarely
1185 // needed as proper activation is normally taken care of for us.
1186 // Bundled apps have a default activation policy of
1187 // NSApplicationActivationPolicyRegular while non-bundled apps have a
1188 // default activation policy of NSApplicationActivationPolicyProhibited.
1189 if (!is_app_bundled())
1190 {
1191 // "setActivationPolicy:" must be invoked before
1192 // "activateIgnoringOtherApps:" for activation to work.
1193 objc::msg_send<void>(app, "setActivationPolicy:"_sel,
1194 NSApplicationActivationPolicyRegular);
1195 // Activate the app regardless of other active apps.
1196 // This can be obtrusive so we only do it when necessary.
1197 objc::msg_send<void>(app, "activateIgnoringOtherApps:"_sel, YES);
1198 }
1199
1200 // Main window
1201 if (!m_parent_window)
1202 {
1203 m_window = objc::msg_send<id>("NSWindow"_cls, "alloc"_sel);
1204 auto style = NSWindowStyleMaskTitled;
1205 m_window = objc::msg_send<id>(
1206 m_window, "initWithContentRect:styleMask:backing:defer:"_sel,
1207 CGRectMake(0, 0, 0, 0), style, NSBackingStoreBuffered, NO);
1208 }
1209 else
1210 {
1211 m_window = (id)m_parent_window;
1212 }
1213
1214 // Webview
1215 auto config = objc::msg_send<id>("WKWebViewConfiguration"_cls, "new"_sel);
1216 m_manager = objc::msg_send<id>(config, "userContentController"_sel);
1217 m_webview = objc::msg_send<id>("WKWebView"_cls, "alloc"_sel);
1218
1219 if (m_debug)
1220 {
1221 // Equivalent Obj-C:
1222 // [[config preferences] setValue:@YES forKey:@"developerExtrasEnabled"];
1223 objc::msg_send<id>(
1224 objc::msg_send<id>(config, "preferences"_sel), "setValue:forKey:"_sel,
1225 objc::msg_send<id>("NSNumber"_cls, "numberWithBool:"_sel, YES),
1226 "developerExtrasEnabled"_str);
1227 }
1228
1229 // Equivalent Obj-C:
1230 // [[config preferences] setValue:@YES forKey:@"fullScreenEnabled"];
1231 objc::msg_send<id>(objc::msg_send<id>(config, "preferences"_sel),
1232 "setValue:forKey:"_sel,
1233 objc::msg_send<id>("NSNumber"_cls, "numberWithBool:"_sel, YES),
1234 "fullScreenEnabled"_str);
1235
1236 // Equivalent Obj-C:
1237 // [[config preferences] setValue:@YES forKey:@"javaScriptCanAccessClipboard"];
1238 objc::msg_send<id>(objc::msg_send<id>(config, "preferences"_sel),
1239 "setValue:forKey:"_sel,
1240 objc::msg_send<id>("NSNumber"_cls, "numberWithBool:"_sel, YES),
1241 "javaScriptCanAccessClipboard"_str);
1242
1243 // Equivalent Obj-C:
1244 // [[config preferences] setValue:@YES forKey:@"DOMPasteAllowed"];
1245 objc::msg_send<id>(objc::msg_send<id>(config, "preferences"_sel),
1246 "setValue:forKey:"_sel,
1247 objc::msg_send<id>("NSNumber"_cls, "numberWithBool:"_sel, YES),
1248 "DOMPasteAllowed"_str);
1249
1250 auto ui_delegate = create_webkit_ui_delegate();
1251 objc::msg_send<void>(m_webview, "initWithFrame:configuration:"_sel,
1252 CGRectMake(0, 0, 0, 0), config);
1253 objc::msg_send<void>(m_webview, "setUIDelegate:"_sel, ui_delegate);
1254
1255 auto navigation_delegate = create_webkit_navigation_delegate();
1256 objc::msg_send<void>(m_webview, "setNavigationDelegate:"_sel, navigation_delegate);
1257 auto script_message_handler = create_script_message_handler();
1258 objc::msg_send<void>(m_manager, "addScriptMessageHandler:name:"_sel,
1259 script_message_handler, "external"_str);
1260
1261 init(R""(
1262 window.external = {
1263 invoke: function(s) {
1264 window.webkit.messageHandlers.external.postMessage(s);
1265 },
1266 };
1267 )"");
1268 objc::msg_send<void>(m_window, "setContentView:"_sel, m_webview);
1269 objc::msg_send<void>(m_window, "makeKeyAndOrderFront:"_sel, nullptr);
1270 }
1271 bool m_debug;
1272 void* m_parent_window;
1273 id m_window;
1274 id m_webview;
1275 id m_manager;
1276 void* m_navigateCallbackArg = nullptr;
1277 std::function<void(const std::string&, void*)> m_navigateCallback = 0;
1278 };
1279
1280 } // namespace detail
1281
1282 using browser_engine = detail::cocoa_wkwebview_engine;
1283
1284} // namespace webview
1285
1286#elif defined(WEBVIEW_EDGE)
1287
1288//
1289// ====================================================================
1290//
1291// This implementation uses Win32 API to create a native window. It
1292// uses Edge/Chromium webview2 backend as a browser engine.
1293//
1294// ====================================================================
1295//
1296
1297#define WIN32_LEAN_AND_MEAN
1298#include <shlobj.h>
1299#include <shlwapi.h>
1300#include <stdlib.h>
1301#include <windows.h>
1302
1303#include "WebView2.h"
1304
1305#ifdef _MSC_VER
1306#pragma comment(lib, "advapi32.lib")
1307#pragma comment(lib, "ole32.lib")
1308#pragma comment(lib, "shell32.lib")
1309#pragma comment(lib, "shlwapi.lib")
1310#pragma comment(lib, "user32.lib")
1311#pragma comment(lib, "version.lib")
1312#endif
1313
1314namespace webview
1315{
1316 namespace detail
1317 {
1318
1319 using msg_cb_t = std::function<void(const std::string)>;
1320
1321 // Converts a narrow (UTF-8-encoded) string into a wide (UTF-16-encoded) string.
1322 inline std::wstring widen_string(const std::string& input)
1323 {
1324 if (input.empty())
1325 {
1326 return std::wstring();
1327 }
1328 UINT cp = CP_UTF8;
1329 DWORD flags = MB_ERR_INVALID_CHARS;
1330 auto input_c = input.c_str();
1331 auto input_length = static_cast<int>(input.size());
1332 auto required_length =
1333 MultiByteToWideChar(cp, flags, input_c, input_length, nullptr, 0);
1334 if (required_length > 0)
1335 {
1336 std::wstring output(static_cast<std::size_t>(required_length), L'\0');
1337 if (MultiByteToWideChar(cp, flags, input_c, input_length, &output[0],
1338 required_length) > 0)
1339 {
1340 return output;
1341 }
1342 }
1343 // Failed to convert string from UTF-8 to UTF-16
1344 return std::wstring();
1345 }
1346
1347 // Converts a wide (UTF-16-encoded) string into a narrow (UTF-8-encoded) string.
1348 inline std::string narrow_string(const std::wstring& input)
1349 {
1350 if (input.empty())
1351 {
1352 return std::string();
1353 }
1354 UINT cp = CP_UTF8;
1355 DWORD flags = WC_ERR_INVALID_CHARS;
1356 auto input_c = input.c_str();
1357 auto input_length = static_cast<int>(input.size());
1358 auto required_length =
1359 WideCharToMultiByte(cp, flags, input_c, input_length, nullptr, 0, nullptr, nullptr);
1360 if (required_length > 0)
1361 {
1362 std::string output(static_cast<std::size_t>(required_length), '\0');
1363 if (WideCharToMultiByte(cp, flags, input_c, input_length, &output[0],
1364 required_length, nullptr, nullptr) > 0)
1365 {
1366 return output;
1367 }
1368 }
1369 // Failed to convert string from UTF-16 to UTF-8
1370 return std::string();
1371 }
1372
1373 // Parses a version string with 1-4 integral components, e.g. "1.2.3.4".
1374 // Missing or invalid components default to 0, and excess components are ignored.
1375 template <typename T>
1376 std::array<unsigned int, 4> parse_version(const std::basic_string<T>& version) noexcept
1377 {
1378 auto parse_component = [](auto sb, auto se) -> unsigned int
1379 {
1380 try
1381 {
1382 auto n = std::stol(std::basic_string<T>(sb, se));
1383 return n < 0 ? 0 : n;
1384 }
1385 catch (std::exception&)
1386 {
1387 return 0;
1388 }
1389 };
1390 auto end = version.end();
1391 auto sb = version.begin(); // subrange begin
1392 auto se = sb; // subrange end
1393 unsigned int ci = 0; // component index
1394 std::array<unsigned int, 4> components{};
1395 while (sb != end && se != end && ci < components.size())
1396 {
1397 if (*se == static_cast<T>('.'))
1398 {
1399 components[ci++] = parse_component(sb, se);
1400 sb = ++se;
1401 continue;
1402 }
1403 ++se;
1404 }
1405 if (sb < se && ci < components.size())
1406 {
1407 components[ci] = parse_component(sb, se);
1408 }
1409 return components;
1410 }
1411
1412 template <typename T, std::size_t Length>
1413 auto parse_version(const T (&version)[Length]) noexcept
1414 {
1415 return parse_version(std::basic_string<T>(version, Length));
1416 }
1417
1418 std::wstring get_file_version_string(const std::wstring& file_path) noexcept
1419 {
1420 DWORD dummy_handle; // Unused
1421 DWORD info_buffer_length = GetFileVersionInfoSizeW(file_path.c_str(), &dummy_handle);
1422 if (info_buffer_length == 0)
1423 {
1424 return std::wstring();
1425 }
1426 std::vector<char> info_buffer;
1427 info_buffer.reserve(info_buffer_length);
1428 if (!GetFileVersionInfoW(file_path.c_str(), 0, info_buffer_length, info_buffer.data()))
1429 {
1430 return std::wstring();
1431 }
1432 auto sub_block = L"\\StringFileInfo\\040904B0\\ProductVersion";
1433 LPWSTR version = nullptr;
1434 unsigned int version_length = 0;
1435 if (!VerQueryValueW(info_buffer.data(), sub_block, reinterpret_cast<LPVOID*>(&version),
1436 &version_length))
1437 {
1438 return std::wstring();
1439 }
1440 if (!version || version_length == 0)
1441 {
1442 return std::wstring();
1443 }
1444 return std::wstring(version, version_length);
1445 }
1446
1447 // A wrapper around COM library initialization. Calls CoInitializeEx in the
1448 // constructor and CoUninitialize in the destructor.
1449 class com_init_wrapper
1450 {
1451 public:
1452 com_init_wrapper(DWORD dwCoInit)
1453 {
1454 // We can safely continue as long as COM was either successfully
1455 // initialized or already initialized.
1456 // RPC_E_CHANGED_MODE means that CoInitializeEx was already called with
1457 // a different concurrency model.
1458 switch (CoInitializeEx(nullptr, dwCoInit))
1459 {
1460 case S_OK:
1461 case S_FALSE:
1462 m_initialized = true;
1463 break;
1464 }
1465 }
1466
1467 ~com_init_wrapper()
1468 {
1469 if (m_initialized)
1470 {
1471 CoUninitialize();
1472 m_initialized = false;
1473 }
1474 }
1475
1476 com_init_wrapper(const com_init_wrapper& other) = delete;
1477 com_init_wrapper& operator=(const com_init_wrapper& other) = delete;
1478 com_init_wrapper(com_init_wrapper&& other) = delete;
1479 com_init_wrapper& operator=(com_init_wrapper&& other) = delete;
1480
1481 bool is_initialized() const
1482 {
1483 return m_initialized;
1484 }
1485
1486 private:
1487 bool m_initialized = false;
1488 };
1489
1490 // Holds a symbol name and associated type for code clarity.
1491 template <typename T> class library_symbol
1492 {
1493 public:
1494 using type = T;
1495
1496 constexpr explicit library_symbol(const char* name) : m_name(name)
1497 {
1498 }
1499 constexpr const char* get_name() const
1500 {
1501 return m_name;
1502 }
1503
1504 private:
1505 const char* m_name;
1506 };
1507
1508 // Loads a native shared library and allows one to get addresses for those
1509 // symbols.
1510 class native_library
1511 {
1512 public:
1513 explicit native_library(const wchar_t* name) : m_handle(LoadLibraryW(name))
1514 {
1515 }
1516
1517 ~native_library()
1518 {
1519 if (m_handle)
1520 {
1521 FreeLibrary(m_handle);
1522 m_handle = nullptr;
1523 }
1524 }
1525
1526 native_library(const native_library& other) = delete;
1527 native_library& operator=(const native_library& other) = delete;
1528 native_library(native_library&& other) = default;
1529 native_library& operator=(native_library&& other) = default;
1530
1531 // Returns true if the library is currently loaded; otherwise false.
1532 operator bool() const
1533 {
1534 return is_loaded();
1535 }
1536
1537 // Get the address for the specified symbol or nullptr if not found.
1538 template <typename Symbol> typename Symbol::type get(const Symbol& symbol) const
1539 {
1540 if (is_loaded())
1541 {
1542 return reinterpret_cast<typename Symbol::type>(
1543 GetProcAddress(m_handle, symbol.get_name()));
1544 }
1545 return nullptr;
1546 }
1547
1548 // Returns true if the library is currently loaded; otherwise false.
1549 bool is_loaded() const
1550 {
1551 return !!m_handle;
1552 }
1553
1554 void detach()
1555 {
1556 m_handle = nullptr;
1557 }
1558
1559 private:
1560 HMODULE m_handle = nullptr;
1561 };
1562
1563 struct user32_symbols
1564 {
1565 using DPI_AWARENESS_CONTEXT = HANDLE;
1566 using SetProcessDpiAwarenessContext_t = BOOL(WINAPI*)(DPI_AWARENESS_CONTEXT);
1567 using SetProcessDPIAware_t = BOOL(WINAPI*)();
1568
1569 static constexpr auto SetProcessDpiAwarenessContext =
1570 library_symbol<SetProcessDpiAwarenessContext_t>("SetProcessDpiAwarenessContext");
1571 static constexpr auto SetProcessDPIAware =
1572 library_symbol<SetProcessDPIAware_t>("SetProcessDPIAware");
1573 };
1574
1575 struct shcore_symbols
1576 {
1577 typedef enum
1578 {
1579 PROCESS_PER_MONITOR_DPI_AWARE = 2
1580 } PROCESS_DPI_AWARENESS;
1581 using SetProcessDpiAwareness_t = HRESULT(WINAPI*)(PROCESS_DPI_AWARENESS);
1582
1583 static constexpr auto SetProcessDpiAwareness =
1584 library_symbol<SetProcessDpiAwareness_t>("SetProcessDpiAwareness");
1585 };
1586
1587 class reg_key
1588 {
1589 public:
1590 explicit reg_key(HKEY root_key, const wchar_t* sub_key, DWORD options,
1591 REGSAM sam_desired)
1592 {
1593 HKEY handle;
1594 auto status = RegOpenKeyExW(root_key, sub_key, options, sam_desired, &handle);
1595 if (status == ERROR_SUCCESS)
1596 {
1597 m_handle = handle;
1598 }
1599 }
1600
1601 explicit reg_key(HKEY root_key, const std::wstring& sub_key, DWORD options,
1602 REGSAM sam_desired)
1603 : reg_key(root_key, sub_key.c_str(), options, sam_desired)
1604 {
1605 }
1606
1607 virtual ~reg_key()
1608 {
1609 if (m_handle)
1610 {
1611 RegCloseKey(m_handle);
1612 m_handle = nullptr;
1613 }
1614 }
1615
1616 reg_key(const reg_key& other) = delete;
1617 reg_key& operator=(const reg_key& other) = delete;
1618 reg_key(reg_key&& other) = delete;
1619 reg_key& operator=(reg_key&& other) = delete;
1620
1621 bool is_open() const
1622 {
1623 return !!m_handle;
1624 }
1625 bool get_handle() const
1626 {
1627 return m_handle;
1628 }
1629
1630 std::wstring query_string(const wchar_t* name) const
1631 {
1632 DWORD buf_length = 0;
1633 // Get the size of the data in bytes.
1634 auto status =
1635 RegQueryValueExW(m_handle, name, nullptr, nullptr, nullptr, &buf_length);
1636 if (status != ERROR_SUCCESS && status != ERROR_MORE_DATA)
1637 {
1638 return std::wstring();
1639 }
1640 // Read the data.
1641 std::wstring result(buf_length / sizeof(wchar_t), 0);
1642 auto buf = reinterpret_cast<LPBYTE>(&result[0]);
1643 status = RegQueryValueExW(m_handle, name, nullptr, nullptr, buf, &buf_length);
1644 if (status != ERROR_SUCCESS)
1645 {
1646 return std::wstring();
1647 }
1648 // Remove trailing null-characters.
1649 for (std::size_t length = result.size(); length > 0; --length)
1650 {
1651 if (result[length - 1] != 0)
1652 {
1653 result.resize(length);
1654 break;
1655 }
1656 }
1657 return result;
1658 }
1659
1660 private:
1661 HKEY m_handle = nullptr;
1662 };
1663
1664 inline bool enable_dpi_awareness()
1665 {
1666 auto user32 = native_library(L"user32.dll");
1667 if (auto fn = user32.get(user32_symbols::SetProcessDpiAwarenessContext))
1668 {
1669 if (fn(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE))
1670 {
1671 return true;
1672 }
1673 return GetLastError() == ERROR_ACCESS_DENIED;
1674 }
1675 if (auto shcore = native_library(L"shcore.dll"))
1676 {
1677 if (auto fn = shcore.get(shcore_symbols::SetProcessDpiAwareness))
1678 {
1679 auto result = fn(shcore_symbols::PROCESS_PER_MONITOR_DPI_AWARE);
1680 return result == S_OK || result == E_ACCESSDENIED;
1681 }
1682 }
1683 if (auto fn = user32.get(user32_symbols::SetProcessDPIAware))
1684 {
1685 return !!fn();
1686 }
1687 return true;
1688 }
1689
1690// Enable built-in WebView2Loader implementation by default.
1691#ifndef WEBVIEW_MSWEBVIEW2_BUILTIN_IMPL
1692#define WEBVIEW_MSWEBVIEW2_BUILTIN_IMPL 1
1693#endif
1694
1695// Link WebView2Loader.dll explicitly by default only if the built-in
1696// implementation is enabled.
1697#ifndef WEBVIEW_MSWEBVIEW2_EXPLICIT_LINK
1698#define WEBVIEW_MSWEBVIEW2_EXPLICIT_LINK WEBVIEW_MSWEBVIEW2_BUILTIN_IMPL
1699#endif
1700
1701// Explicit linking of WebView2Loader.dll should be used along with
1702// the built-in implementation.
1703#if WEBVIEW_MSWEBVIEW2_BUILTIN_IMPL == 1 && WEBVIEW_MSWEBVIEW2_EXPLICIT_LINK != 1
1704#undef WEBVIEW_MSWEBVIEW2_EXPLICIT_LINK
1705#error Please set WEBVIEW_MSWEBVIEW2_EXPLICIT_LINK=1.
1706#endif
1707
1708#if WEBVIEW_MSWEBVIEW2_BUILTIN_IMPL == 1
1709 // Gets the last component of a Windows native file path.
1710 // For example, if the path is "C:\a\b" then the result is "b".
1711 template <typename T>
1712 std::basic_string<T> get_last_native_path_component(const std::basic_string<T>& path)
1713 {
1714 if (auto pos = path.find_last_of(static_cast<T>('\\'));
1715 pos != std::basic_string<T>::npos)
1716 {
1717 return path.substr(pos + 1);
1718 }
1719 return std::basic_string<T>();
1720 }
1721#endif /* WEBVIEW_MSWEBVIEW2_BUILTIN_IMPL */
1722
1723 template <typename T> struct cast_info_t
1724 {
1725 using type = T;
1726 IID iid;
1727 };
1728
1729 namespace mswebview2
1730 {
1731 static constexpr IID IID_ICoreWebView2CreateCoreWebView2ControllerCompletedHandler{
1732 0x6C4819F3, 0xC9B7, 0x4260, 0x81, 0x27, 0xC9, 0xF5, 0xBD, 0xE7, 0xF6, 0x8C
1733 };
1734 static constexpr IID IID_ICoreWebView2CreateCoreWebView2EnvironmentCompletedHandler{
1735 0x4E8A3389, 0xC9D8, 0x4BD2, 0xB6, 0xB5, 0x12, 0x4F, 0xEE, 0x6C, 0xC1, 0x4D
1736 };
1737 static constexpr IID IID_ICoreWebView2PermissionRequestedEventHandler{
1738 0x15E1C6A3, 0xC72A, 0x4DF3, 0x91, 0xD7, 0xD0, 0x97, 0xFB, 0xEC, 0x6B, 0xFD
1739 };
1740 static constexpr IID IID_ICoreWebView2WebMessageReceivedEventHandler{
1741 0x57213F19, 0x00E6, 0x49FA, 0x8E, 0x07, 0x89, 0x8E, 0xA0, 0x1E, 0xCB, 0xD2
1742 };
1743
1744#if WEBVIEW_MSWEBVIEW2_BUILTIN_IMPL == 1
1745 enum class webview2_runtime_type
1746 {
1747 installed = 0,
1748 embedded = 1
1749 };
1750
1751 namespace webview2_symbols
1752 {
1753 using CreateWebViewEnvironmentWithOptionsInternal_t = HRESULT(STDMETHODCALLTYPE*)(
1754 bool, webview2_runtime_type, PCWSTR, IUnknown*,
1755 ICoreWebView2CreateCoreWebView2EnvironmentCompletedHandler*);
1756 using DllCanUnloadNow_t = HRESULT(STDMETHODCALLTYPE*)();
1757
1758 static constexpr auto CreateWebViewEnvironmentWithOptionsInternal =
1759 library_symbol<CreateWebViewEnvironmentWithOptionsInternal_t>(
1760 "CreateWebViewEnvironmentWithOptionsInternal");
1761 static constexpr auto DllCanUnloadNow =
1762 library_symbol<DllCanUnloadNow_t>("DllCanUnloadNow");
1763 } // namespace webview2_symbols
1764#endif /* WEBVIEW_MSWEBVIEW2_BUILTIN_IMPL */
1765
1766#if WEBVIEW_MSWEBVIEW2_EXPLICIT_LINK == 1
1767 namespace webview2_symbols
1768 {
1769 using CreateCoreWebView2EnvironmentWithOptions_t = HRESULT(STDMETHODCALLTYPE*)(
1770 PCWSTR, PCWSTR, ICoreWebView2EnvironmentOptions*,
1771 ICoreWebView2CreateCoreWebView2EnvironmentCompletedHandler*);
1772 using GetAvailableCoreWebView2BrowserVersionString_t =
1773 HRESULT(STDMETHODCALLTYPE*)(PCWSTR, LPWSTR*);
1774
1775 static constexpr auto CreateCoreWebView2EnvironmentWithOptions =
1776 library_symbol<CreateCoreWebView2EnvironmentWithOptions_t>(
1777 "CreateCoreWebView2EnvironmentWithOptions");
1778 static constexpr auto GetAvailableCoreWebView2BrowserVersionString =
1779 library_symbol<GetAvailableCoreWebView2BrowserVersionString_t>(
1780 "GetAvailableCoreWebView2BrowserVersionString");
1781 } // namespace webview2_symbols
1782#endif /* WEBVIEW_MSWEBVIEW2_EXPLICIT_LINK */
1783
1784 class loader
1785 {
1786 public:
1787 HRESULT create_environment_with_options(
1788 PCWSTR browser_dir, PCWSTR user_data_dir,
1789 ICoreWebView2EnvironmentOptions* env_options,
1790 ICoreWebView2CreateCoreWebView2EnvironmentCompletedHandler* created_handler)
1791 const
1792 {
1793#if WEBVIEW_MSWEBVIEW2_EXPLICIT_LINK == 1
1794 if (m_lib.is_loaded())
1795 {
1796 if (auto fn = m_lib.get(
1797 webview2_symbols::CreateCoreWebView2EnvironmentWithOptions))
1798 {
1799 return fn(browser_dir, user_data_dir, env_options, created_handler);
1800 }
1801 }
1802#if WEBVIEW_MSWEBVIEW2_BUILTIN_IMPL == 1
1803 return create_environment_with_options_impl(browser_dir, user_data_dir,
1804 env_options, created_handler);
1805#else
1806 return S_FALSE;
1807#endif
1808#else
1809 return ::CreateCoreWebView2EnvironmentWithOptions(browser_dir, user_data_dir,
1810 env_options, created_handler);
1811#endif /* WEBVIEW_MSWEBVIEW2_EXPLICIT_LINK */
1812 }
1813
1814 HRESULT
1815 get_available_browser_version_string(PCWSTR browser_dir, LPWSTR* version) const
1816 {
1817#if WEBVIEW_MSWEBVIEW2_EXPLICIT_LINK == 1
1818 if (m_lib.is_loaded())
1819 {
1820 if (auto fn = m_lib.get(
1821 webview2_symbols::GetAvailableCoreWebView2BrowserVersionString))
1822 {
1823 return fn(browser_dir, version);
1824 }
1825 }
1826#if WEBVIEW_MSWEBVIEW2_BUILTIN_IMPL == 1
1827 return get_available_browser_version_string_impl(browser_dir, version);
1828#else
1829 return S_FALSE;
1830#endif
1831#else
1832 return ::GetAvailableCoreWebView2BrowserVersionString(browser_dir, version);
1833#endif /* WEBVIEW_MSWEBVIEW2_EXPLICIT_LINK */
1834 }
1835
1836 private:
1837#if WEBVIEW_MSWEBVIEW2_BUILTIN_IMPL == 1
1838 struct client_info_t
1839 {
1840 bool found = false;
1841 std::wstring dll_path;
1842 std::wstring version;
1843 webview2_runtime_type runtime_type;
1844 };
1845
1846 HRESULT create_environment_with_options_impl(
1847 PCWSTR browser_dir, PCWSTR user_data_dir,
1848 ICoreWebView2EnvironmentOptions* env_options,
1849 ICoreWebView2CreateCoreWebView2EnvironmentCompletedHandler* created_handler)
1850 const
1851 {
1852 auto found_client = find_available_client(browser_dir);
1853 if (!found_client.found)
1854 {
1855 return -1;
1856 }
1857 auto client_dll = native_library(found_client.dll_path.c_str());
1858 if (auto fn = client_dll.get(
1859 webview2_symbols::CreateWebViewEnvironmentWithOptionsInternal))
1860 {
1861 return fn(true, found_client.runtime_type, user_data_dir, env_options,
1862 created_handler);
1863 }
1864 if (auto fn = client_dll.get(webview2_symbols::DllCanUnloadNow))
1865 {
1866 if (!fn())
1867 {
1868 client_dll.detach();
1869 }
1870 }
1871 return ERROR_SUCCESS;
1872 }
1873
1874 HRESULT
1875 get_available_browser_version_string_impl(PCWSTR browser_dir, LPWSTR* version) const
1876 {
1877 if (!version)
1878 {
1879 return -1;
1880 }
1881 auto found_client = find_available_client(browser_dir);
1882 if (!found_client.found)
1883 {
1884 return -1;
1885 }
1886 auto info_length_bytes =
1887 found_client.version.size() * sizeof(found_client.version[0]);
1888 auto info = static_cast<LPWSTR>(CoTaskMemAlloc(info_length_bytes));
1889 if (!info)
1890 {
1891 return -1;
1892 }
1893 CopyMemory(info, found_client.version.c_str(), info_length_bytes);
1894 *version = info;
1895 return 0;
1896 }
1897
1898 client_info_t find_available_client(PCWSTR browser_dir) const
1899 {
1900 if (browser_dir)
1901 {
1902 return find_embedded_client(api_version, browser_dir);
1903 }
1904 auto found_client =
1905 find_installed_client(api_version, true, default_release_channel_guid);
1906 if (!found_client.found)
1907 {
1908 found_client =
1909 find_installed_client(api_version, false, default_release_channel_guid);
1910 }
1911 return found_client;
1912 }
1913
1914 std::wstring make_client_dll_path(const std::wstring& dir) const
1915 {
1916 auto dll_path = dir;
1917 if (!dll_path.empty())
1918 {
1919 auto last_char = dir[dir.size() - 1];
1920 if (last_char != L'\\' && last_char != L'/')
1921 {
1922 dll_path += L'\\';
1923 }
1924 }
1925 dll_path += L"EBWebView\\";
1926#if defined(_M_X64) || defined(__x86_64__)
1927 dll_path += L"x64";
1928#elif defined(_M_IX86) || defined(__i386__)
1929 dll_path += L"x86";
1930#elif defined(_M_ARM64) || defined(__aarch64__)
1931 dll_path += L"arm64";
1932#else
1933#error WebView2 integration for this platform is not yet supported.
1934#endif
1935 dll_path += L"\\EmbeddedBrowserWebView.dll";
1936 return dll_path;
1937 }
1938
1939 client_info_t find_installed_client(unsigned int min_api_version, bool system,
1940 const std::wstring& release_channel) const
1941 {
1942 std::wstring sub_key = client_state_reg_sub_key;
1943 sub_key += release_channel;
1944 auto root_key = system ? HKEY_LOCAL_MACHINE : HKEY_CURRENT_USER;
1945 reg_key key(root_key, sub_key, 0, KEY_READ | KEY_WOW64_32KEY);
1946 if (!key.is_open())
1947 {
1948 return {};
1949 }
1950 auto ebwebview_value = key.query_string(L"EBWebView");
1951
1952 auto client_version_string = get_last_native_path_component(ebwebview_value);
1953 auto client_version = parse_version(client_version_string);
1954 if (client_version[2] < min_api_version)
1955 {
1956 // Our API version is greater than the runtime API version.
1957 return {};
1958 }
1959
1960 auto client_dll_path = make_client_dll_path(ebwebview_value);
1961 return { true, client_dll_path, client_version_string,
1962 webview2_runtime_type::installed };
1963 }
1964
1965 client_info_t find_embedded_client(unsigned int min_api_version,
1966 const std::wstring& dir) const
1967 {
1968 auto client_dll_path = make_client_dll_path(dir);
1969
1970 auto client_version_string = get_file_version_string(client_dll_path);
1971 auto client_version = parse_version(client_version_string);
1972 if (client_version[2] < min_api_version)
1973 {
1974 // Our API version is greater than the runtime API version.
1975 return {};
1976 }
1977
1978 return { true, client_dll_path, client_version_string,
1979 webview2_runtime_type::embedded };
1980 }
1981
1982 // The minimum WebView2 API version we need regardless of the SDK release
1983 // actually used. The number comes from the SDK release version,
1984 // e.g. 1.0.1150.38. To be safe the SDK should have a number that is greater
1985 // than or equal to this number. The Edge browser webview client must
1986 // have a number greater than or equal to this number.
1987 static constexpr unsigned int api_version = 1150;
1988
1989 static constexpr auto client_state_reg_sub_key =
1990 L"SOFTWARE\\Microsoft\\EdgeUpdate\\ClientState\\";
1991
1992 // GUID for the stable release channel.
1993 static constexpr auto stable_release_guid =
1994 L"{F3017226-FE2A-4295-8BDF-00C3A9A7E4C5}";
1995
1996 static constexpr auto default_release_channel_guid = stable_release_guid;
1997#endif /* WEBVIEW_MSWEBVIEW2_BUILTIN_IMPL */
1998
1999#if WEBVIEW_MSWEBVIEW2_EXPLICIT_LINK == 1
2000 native_library m_lib{ L"WebView2Loader.dll" };
2001#endif
2002 };
2003
2004 namespace cast_info
2005 {
2006 static constexpr auto controller_completed =
2007 cast_info_t<ICoreWebView2CreateCoreWebView2ControllerCompletedHandler>{
2008 IID_ICoreWebView2CreateCoreWebView2ControllerCompletedHandler
2009 };
2010
2011 static constexpr auto environment_completed =
2012 cast_info_t<ICoreWebView2CreateCoreWebView2EnvironmentCompletedHandler>{
2013 IID_ICoreWebView2CreateCoreWebView2EnvironmentCompletedHandler
2014 };
2015
2016 static constexpr auto message_received =
2017 cast_info_t<ICoreWebView2WebMessageReceivedEventHandler>{
2018 IID_ICoreWebView2WebMessageReceivedEventHandler
2019 };
2020
2021 static constexpr auto permission_requested =
2022 cast_info_t<ICoreWebView2PermissionRequestedEventHandler>{
2023 IID_ICoreWebView2PermissionRequestedEventHandler
2024 };
2025 } // namespace cast_info
2026 } // namespace mswebview2
2027
2028 class webview2_com_handler
2029 : public ICoreWebView2CreateCoreWebView2EnvironmentCompletedHandler,
2030 public ICoreWebView2CreateCoreWebView2ControllerCompletedHandler,
2031 public ICoreWebView2WebMessageReceivedEventHandler,
2032 public ICoreWebView2PermissionRequestedEventHandler,
2033 public ICoreWebView2NavigationCompletedEventHandler
2034 {
2035 using webview2_com_handler_cb_t =
2036 std::function<void(ICoreWebView2Controller*, ICoreWebView2* webview)>;
2037
2038 public:
2039 webview2_com_handler(HWND hwnd, msg_cb_t msgCb, webview2_com_handler_cb_t cb)
2040 : m_window(hwnd), m_msgCb(msgCb), m_cb(cb)
2041 {
2042 }
2043
2044 virtual ~webview2_com_handler() = default;
2045 webview2_com_handler(const webview2_com_handler& other) = delete;
2046 webview2_com_handler& operator=(const webview2_com_handler& other) = delete;
2047 webview2_com_handler(webview2_com_handler&& other) = delete;
2048 webview2_com_handler& operator=(webview2_com_handler&& other) = delete;
2049
2050 ULONG STDMETHODCALLTYPE AddRef()
2051 {
2052 return ++m_ref_count;
2053 }
2054 ULONG STDMETHODCALLTYPE Release()
2055 {
2056 if (m_ref_count > 1)
2057 {
2058 return --m_ref_count;
2059 }
2060 delete this;
2061 return 0;
2062 }
2063 HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, LPVOID* ppv)
2064 {
2065 using namespace mswebview2::cast_info;
2066
2067 if (!ppv)
2068 {
2069 return E_POINTER;
2070 }
2071
2072 // All of the COM interfaces we implement should be added here regardless
2073 // of whether they are required.
2074 // This is just to be on the safe side in case the WebView2 Runtime ever
2075 // requests a pointer to an interface we implement.
2076 // The WebView2 Runtime must at the very least be able to get a pointer to
2077 // ICoreWebView2CreateCoreWebView2EnvironmentCompletedHandler when we use
2078 // our custom WebView2 loader implementation, and observations have shown
2079 // that it is the only interface requested in this case. None have been
2080 // observed to be requested when using the official WebView2 loader.
2081
2082 if (cast_if_equal_iid(riid, controller_completed, ppv) ||
2083 cast_if_equal_iid(riid, environment_completed, ppv) ||
2084 cast_if_equal_iid(riid, message_received, ppv) ||
2085 cast_if_equal_iid(riid, permission_requested, ppv))
2086 {
2087 return S_OK;
2088 }
2089
2090 return E_NOINTERFACE;
2091 }
2092 HRESULT STDMETHODCALLTYPE Invoke(HRESULT res, ICoreWebView2Environment* env)
2093 {
2094 if (SUCCEEDED(res))
2095 {
2096 res = env->CreateCoreWebView2Controller(m_window, this);
2097 if (SUCCEEDED(res))
2098 {
2099 return S_OK;
2100 }
2101 }
2102 try_create_environment();
2103 return S_OK;
2104 }
2105 HRESULT STDMETHODCALLTYPE Invoke(HRESULT res, ICoreWebView2Controller* controller)
2106 {
2107 if (FAILED(res))
2108 {
2109 // See try_create_environment() regarding
2110 // HRESULT_FROM_WIN32(ERROR_INVALID_STATE).
2111 // The result is E_ABORT if the parent window has been destroyed already.
2112 switch (res)
2113 {
2114 case HRESULT_FROM_WIN32(ERROR_INVALID_STATE):
2115 case E_ABORT:
2116 return S_OK;
2117 }
2118 try_create_environment();
2119 return S_OK;
2120 }
2121
2122 ICoreWebView2* webview;
2123 ::EventRegistrationToken token;
2124 controller->get_CoreWebView2(&webview);
2125 webview->add_WebMessageReceived(this, &token);
2126 webview->add_PermissionRequested(this, &token);
2127 webview->add_NavigationCompleted(this, &token);
2128
2129 m_cb(controller, webview);
2130 return S_OK;
2131 }
2132 HRESULT STDMETHODCALLTYPE Invoke(ICoreWebView2* sender,
2133 ICoreWebView2WebMessageReceivedEventArgs* args)
2134 {
2135 LPWSTR message;
2136 args->TryGetWebMessageAsString(&message);
2137 m_msgCb(narrow_string(message));
2138 sender->PostWebMessageAsString(message);
2139
2140 CoTaskMemFree(message);
2141 return S_OK;
2142 }
2143 HRESULT STDMETHODCALLTYPE Invoke(ICoreWebView2* sender,
2144 ICoreWebView2PermissionRequestedEventArgs* args)
2145 {
2146 COREWEBVIEW2_PERMISSION_KIND kind;
2147 args->get_PermissionKind(&kind);
2148 if (kind == COREWEBVIEW2_PERMISSION_KIND_CLIPBOARD_READ)
2149 {
2150 args->put_State(COREWEBVIEW2_PERMISSION_STATE_ALLOW);
2151 }
2152 return S_OK;
2153 }
2154 HRESULT STDMETHODCALLTYPE Invoke(ICoreWebView2* sender,
2155 ICoreWebView2NavigationCompletedEventArgs* args)
2156 {
2157 PWSTR uri = nullptr;
2158 auto hr = sender->get_Source(&uri);
2159 if (SUCCEEDED(hr))
2160 {
2161 auto curi = std::wstring_convert<std::codecvt_utf8<wchar_t> >().to_bytes(uri);
2162 if (navigateCallback)
2163 navigateCallback(curi, navigateCallbackArg);
2164 }
2165 CoTaskMemFree(uri);
2166 return hr;
2167 }
2168
2169 // Checks whether the specified IID equals the IID of the specified type and
2170 // if so casts the "this" pointer to T and returns it. Returns nullptr on
2171 // mismatching IIDs.
2172 // If ppv is specified then the pointer will also be assigned to *ppv.
2173 template <typename T>
2174 T* cast_if_equal_iid(REFIID riid, const cast_info_t<T>& info,
2175 LPVOID* ppv = nullptr) noexcept
2176 {
2177 T* ptr = nullptr;
2178 if (IsEqualIID(riid, info.iid))
2179 {
2180 ptr = static_cast<T*>(this);
2181 ptr->AddRef();
2182 }
2183 if (ppv)
2184 {
2185 *ppv = ptr;
2186 }
2187 return ptr;
2188 }
2189
2190 // Set the function that will perform the initiating logic for creating
2191 // the WebView2 environment.
2192 void set_attempt_handler(std::function<HRESULT()> attempt_handler) noexcept
2193 {
2194 m_attempt_handler = attempt_handler;
2195 }
2196
2197 // Retry creating a WebView2 environment.
2198 // The initiating logic for creating the environment is defined by the
2199 // caller of set_attempt_handler().
2200 void try_create_environment() noexcept
2201 {
2202 // WebView creation fails with HRESULT_FROM_WIN32(ERROR_INVALID_STATE) if
2203 // a running instance using the same user data folder exists, and the
2204 // Environment objects have different EnvironmentOptions.
2205 // Source:
2206 // https://docs.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/icorewebview2environment?view=webview2-1.0.1150.38
2207 if (m_attempts < m_max_attempts)
2208 {
2209 ++m_attempts;
2210 auto res = m_attempt_handler();
2211 if (SUCCEEDED(res))
2212 {
2213 return;
2214 }
2215 // Not entirely sure if this error code only applies to
2216 // CreateCoreWebView2Controller so we check here as well.
2217 if (res == HRESULT_FROM_WIN32(ERROR_INVALID_STATE))
2218 {
2219 return;
2220 }
2221 try_create_environment();
2222 return;
2223 }
2224 // Give up.
2225 m_cb(nullptr, nullptr);
2226 }
2227
2228 void STDMETHODCALLTYPE add_navigate_listener(
2229 std::function<void(const std::string&, void*)> callback, void* arg)
2230 {
2231 navigateCallback = std::move(callback);
2232 navigateCallbackArg = arg;
2233 }
2234
2235 private:
2236 HWND m_window;
2237 msg_cb_t m_msgCb;
2238 webview2_com_handler_cb_t m_cb;
2239 std::atomic<ULONG> m_ref_count{ 1 };
2240 std::function<HRESULT()> m_attempt_handler;
2241 unsigned int m_max_attempts = 5;
2242 unsigned int m_attempts = 0;
2243 void* navigateCallbackArg = nullptr;
2244 std::function<void(const std::string&, void*)> navigateCallback = 0;
2245 };
2246
2247 class win32_edge_engine
2248 {
2249 public:
2250 win32_edge_engine(bool debug, void* window)
2251 {
2252 if (!is_webview2_available())
2253 {
2254 return;
2255 }
2256 if (!m_com_init.is_initialized())
2257 {
2258 return;
2259 }
2260 enable_dpi_awareness();
2261 if (window == nullptr)
2262 {
2263 HINSTANCE hInstance = GetModuleHandle(nullptr);
2264 HICON icon = (HICON)LoadImage(hInstance, IDI_APPLICATION, IMAGE_ICON,
2265 GetSystemMetrics(SM_CXICON),
2266 GetSystemMetrics(SM_CYICON), LR_DEFAULTCOLOR);
2267
2268 WNDCLASSEXW wc;
2269 ZeroMemory(&wc, sizeof(WNDCLASSEX));
2270 wc.cbSize = sizeof(WNDCLASSEX);
2271 wc.hInstance = hInstance;
2272 wc.lpszClassName = L"webview";
2273 wc.hIcon = icon;
2274 wc.lpfnWndProc =
2275 (WNDPROC)(+[](HWND hwnd, UINT msg, WPARAM wp, LPARAM lp) -> LRESULT
2276 {
2277 auto w =
2278 (win32_edge_engine*)GetWindowLongPtr(hwnd, GWLP_USERDATA);
2279 switch (msg)
2280 {
2281 case WM_SIZE:
2282 w->resize(hwnd);
2283 break;
2284 case WM_CLOSE:
2285 DestroyWindow(hwnd);
2286 break;
2287 case WM_DESTROY:
2288 w->terminate();
2289 break;
2290 case WM_GETMINMAXINFO:
2291 {
2292 auto lpmmi = (LPMINMAXINFO)lp;
2293 if (w == nullptr)
2294 {
2295 return 0;
2296 }
2297 if (w->m_maxsz.x > 0 && w->m_maxsz.y > 0)
2298 {
2299 lpmmi->ptMaxSize = w->m_maxsz;
2300 lpmmi->ptMaxTrackSize = w->m_maxsz;
2301 }
2302 if (w->m_minsz.x > 0 && w->m_minsz.y > 0)
2303 {
2304 lpmmi->ptMinTrackSize = w->m_minsz;
2305 }
2306 }
2307 break;
2308 default:
2309 return DefWindowProcW(hwnd, msg, wp, lp);
2310 }
2311 return 0;
2312 });
2313 RegisterClassExW(&wc);
2314 m_window = CreateWindowW(L"webview", L"", WS_OVERLAPPEDWINDOW, CW_USEDEFAULT,
2315 CW_USEDEFAULT, 640, 480, nullptr, nullptr, hInstance,
2316 nullptr);
2317 if (m_window == nullptr)
2318 {
2319 return;
2320 }
2321 SetWindowLongPtr(m_window, GWLP_USERDATA, (LONG_PTR)this);
2322 }
2323 else
2324 {
2325 m_window = *(static_cast<HWND*>(window));
2326 }
2327
2328 ShowWindow(m_window, SW_SHOW);
2329 UpdateWindow(m_window);
2330 SetFocus(m_window);
2331
2332 auto cb = std::bind(&win32_edge_engine::on_message, this, std::placeholders::_1);
2333
2334 embed(m_window, debug, cb);
2335 resize(m_window);
2336 m_controller->MoveFocus(COREWEBVIEW2_MOVE_FOCUS_REASON_PROGRAMMATIC);
2337 }
2338
2339 virtual ~win32_edge_engine()
2340 {
2341 if (m_com_handler)
2342 {
2343 m_com_handler->Release();
2344 m_com_handler = nullptr;
2345 }
2346 if (m_webview)
2347 {
2348 m_webview->Release();
2349 m_webview = nullptr;
2350 }
2351 if (m_controller)
2352 {
2353 m_controller->Release();
2354 m_controller = nullptr;
2355 }
2356 }
2357
2358 win32_edge_engine(const win32_edge_engine& other) = delete;
2359 win32_edge_engine& operator=(const win32_edge_engine& other) = delete;
2360 win32_edge_engine(win32_edge_engine&& other) = delete;
2361 win32_edge_engine& operator=(win32_edge_engine&& other) = delete;
2362
2363 void run()
2364 {
2365 MSG msg;
2366 BOOL res;
2367 while ((res = GetMessage(&msg, nullptr, 0, 0)) != -1)
2368 {
2369 if (msg.hwnd)
2370 {
2371 TranslateMessage(&msg);
2372 DispatchMessage(&msg);
2373 continue;
2374 }
2375 if (msg.message == WM_APP)
2376 {
2377 auto f = (dispatch_fn_t*)(msg.lParam);
2378 (*f)();
2379 delete f;
2380 }
2381 else if (msg.message == WM_QUIT)
2382 {
2383 return;
2384 }
2385 }
2386 }
2387 void* window()
2388 {
2389 return (void*)m_window;
2390 }
2391 void terminate()
2392 {
2393 PostQuitMessage(0);
2394 }
2395 void dispatch(dispatch_fn_t f)
2396 {
2397 PostThreadMessage(m_main_thread, WM_APP, 0, (LPARAM) new dispatch_fn_t(f));
2398 }
2399
2400 void set_title(const std::string& title)
2401 {
2402 SetWindowTextW(m_window, widen_string(title).c_str());
2403 }
2404
2405 void set_size(int width, int height, int hints)
2406 {
2407 auto style = GetWindowLong(m_window, GWL_STYLE);
2408 if (hints == WEBVIEW_HINT_FIXED)
2409 {
2410 style &= ~(WS_THICKFRAME | WS_MAXIMIZEBOX);
2411 }
2412 else
2413 {
2414 style |= (WS_THICKFRAME | WS_MAXIMIZEBOX);
2415 }
2416 SetWindowLong(m_window, GWL_STYLE, style);
2417
2418 if (hints == WEBVIEW_HINT_MAX)
2419 {
2420 m_maxsz.x = width;
2421 m_maxsz.y = height;
2422 }
2423 else if (hints == WEBVIEW_HINT_MIN)
2424 {
2425 m_minsz.x = width;
2426 m_minsz.y = height;
2427 }
2428 else
2429 {
2430 RECT r;
2431 r.left = r.top = 0;
2432 r.right = width;
2433 r.bottom = height;
2434 AdjustWindowRect(&r, WS_OVERLAPPEDWINDOW, 0);
2435 SetWindowPos(m_window, nullptr, r.left, r.top, r.right - r.left,
2436 r.bottom - r.top,
2437 SWP_NOZORDER | SWP_NOACTIVATE | SWP_NOMOVE | SWP_FRAMECHANGED);
2438 resize(m_window);
2439 }
2440 }
2441
2442 void navigate(const std::string& url)
2443 {
2444 auto wurl = widen_string(url);
2445 m_webview->Navigate(wurl.c_str());
2446 }
2447
2448 void init(const std::string& js)
2449 {
2450 auto wjs = widen_string(js);
2451 m_webview->AddScriptToExecuteOnDocumentCreated(wjs.c_str(), nullptr);
2452 }
2453
2454 void eval(const std::string& js)
2455 {
2456 auto wjs = widen_string(js);
2457 m_webview->ExecuteScript(wjs.c_str(), nullptr);
2458 }
2459
2460 void add_navigate_listener(std::function<void(const std::string&, void*)> callback,
2461 void* arg)
2462 {
2463 m_com_handler->add_navigate_listener(callback, arg);
2464 }
2465
2466 void add_scheme_handler(const std::string& scheme,
2467 std::function<void(const std::string&, void*)> callback,
2468 void* arg)
2469 {
2470 // TODO: Implement
2471 }
2472
2473 void set_html(const std::string& html)
2474 {
2475 m_webview->NavigateToString(widen_string(html).c_str());
2476 }
2477
2478 private:
2479 bool embed(HWND wnd, bool debug, msg_cb_t cb)
2480 {
2481 std::atomic_flag flag = ATOMIC_FLAG_INIT;
2482 flag.test_and_set();
2483
2484 wchar_t currentExePath[MAX_PATH];
2485 GetModuleFileNameW(nullptr, currentExePath, MAX_PATH);
2486 wchar_t* currentExeName = PathFindFileNameW(currentExePath);
2487
2488 wchar_t dataPath[MAX_PATH];
2489 if (!SUCCEEDED(SHGetFolderPathW(nullptr, CSIDL_APPDATA, nullptr, 0, dataPath)))
2490 {
2491 return false;
2492 }
2493 wchar_t userDataFolder[MAX_PATH];
2494 PathCombineW(userDataFolder, dataPath, currentExeName);
2495
2496 m_com_handler = new webview2_com_handler(
2497 wnd, cb,
2498 [&](ICoreWebView2Controller* controller, ICoreWebView2* webview)
2499 {
2500 if (!controller || !webview)
2501 {
2502 flag.clear();
2503 return;
2504 }
2505 controller->AddRef();
2506 webview->AddRef();
2507 m_controller = controller;
2508 m_webview = webview;
2509 flag.clear();
2510 });
2511
2512 m_com_handler->set_attempt_handler(
2513 [&]
2514 {
2515 return m_webview2_loader.create_environment_with_options(
2516 nullptr, userDataFolder, nullptr, m_com_handler);
2517 });
2518 m_com_handler->try_create_environment();
2519
2520 MSG msg = {};
2521 while (flag.test_and_set() && GetMessage(&msg, nullptr, 0, 0))
2522 {
2523 TranslateMessage(&msg);
2524 DispatchMessage(&msg);
2525 }
2526 if (!m_controller || !m_webview)
2527 {
2528 return false;
2529 }
2530 ICoreWebView2Settings* settings = nullptr;
2531 auto res = m_webview->get_Settings(&settings);
2532 if (res != S_OK)
2533 {
2534 return false;
2535 }
2536 res = settings->put_AreDevToolsEnabled(debug ? TRUE : FALSE);
2537 if (res != S_OK)
2538 {
2539 return false;
2540 }
2541 init("window.external={invoke:s=>window.chrome.webview.postMessage(s)}");
2542 return true;
2543 }
2544
2545 void resize(HWND wnd)
2546 {
2547 if (m_controller == nullptr)
2548 {
2549 return;
2550 }
2551 RECT bounds;
2552 GetClientRect(wnd, &bounds);
2553 m_controller->put_Bounds(bounds);
2554 }
2555
2556 bool is_webview2_available() const noexcept
2557 {
2558 LPWSTR version_info = nullptr;
2559 auto res =
2560 m_webview2_loader.get_available_browser_version_string(nullptr, &version_info);
2561 // The result will be equal to HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND)
2562 // if the WebView2 runtime is not installed.
2563 auto ok = SUCCEEDED(res) && version_info;
2564 if (version_info)
2565 {
2566 CoTaskMemFree(version_info);
2567 }
2568 return ok;
2569 }
2570
2571 virtual void on_message(const std::string& msg) = 0;
2572
2573 // The app is expected to call CoInitializeEx before
2574 // CreateCoreWebView2EnvironmentWithOptions.
2575 // Source:
2576 // https://docs.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/webview2-idl#createcorewebview2environmentwithoptions
2577 com_init_wrapper m_com_init{ COINIT_APARTMENTTHREADED };
2578 HWND m_window = nullptr;
2579 POINT m_minsz = POINT{ 0, 0 };
2580 POINT m_maxsz = POINT{ 0, 0 };
2581 DWORD m_main_thread = GetCurrentThreadId();
2582 ICoreWebView2* m_webview = nullptr;
2583 ICoreWebView2Controller* m_controller = nullptr;
2584 webview2_com_handler* m_com_handler = nullptr;
2585 mswebview2::loader m_webview2_loader;
2586 };
2587
2588 } // namespace detail
2589
2590 using browser_engine = detail::win32_edge_engine;
2591
2592} // namespace webview
2593
2594#endif /* WEBVIEW_GTK, WEBVIEW_COCOA, WEBVIEW_EDGE */
2595
2596namespace webview
2597{
2598
2599 class webview : public browser_engine
2600 {
2601 public:
2602 webview(bool debug = false, void* wnd = nullptr) : browser_engine(debug, wnd)
2603 {
2604 }
2605
2606 void navigate(const std::string& url)
2607 {
2608 if (url.empty())
2609 {
2610 browser_engine::navigate("about:blank");
2611 return;
2612 }
2613 browser_engine::navigate(url);
2614 }
2615
2616 using binding_t = std::function<void(std::string, std::string, void*)>;
2617 class binding_ctx_t
2618 {
2619 public:
2620 binding_ctx_t(binding_t callback, void* arg) : callback(callback), arg(arg)
2621 {
2622 }
2623 // This function is called upon execution of the bound JS function
2624 binding_t callback;
2625 // This user-supplied argument is passed to the callback
2626 void* arg;
2627 };
2628
2629 using sync_binding_t = std::function<std::string(std::string)>;
2630
2631 // Synchronous bind
2632 void bind(const std::string& name, sync_binding_t fn)
2633 {
2634 auto wrapper = [this, fn](const std::string& seq, const std::string& req, void* /*arg*/)
2635 { resolve(seq, 0, fn(req)); };
2636 bind(name, wrapper, nullptr);
2637 }
2638
2639 // Asynchronous bind
2640 void bind(const std::string& name, binding_t fn, void* arg)
2641 {
2642 if (bindings.count(name) > 0)
2643 {
2644 return;
2645 }
2646 bindings.emplace(name, binding_ctx_t(fn, arg));
2647 auto js = "(function() { var name = '" + name + "';" + R""(
2648 var RPC = window._rpc = (window._rpc || {nextSeq: 1});
2649 window[name] = function() {
2650 var seq = RPC.nextSeq++;
2651 var promise = new Promise(function(resolve, reject) {
2652 RPC[seq] = {
2653 resolve: resolve,
2654 reject: reject,
2655 };
2656 });
2657 window.external.invoke(JSON.stringify({
2658 id: seq,
2659 method: name,
2660 params: Array.prototype.slice.call(arguments),
2661 }));
2662 return promise;
2663 }
2664 })())"";
2665 init(js);
2666 eval(js);
2667 }
2668
2669 void unbind(const std::string& name)
2670 {
2671 auto found = bindings.find(name);
2672 if (found != bindings.end())
2673 {
2674 auto js = "delete window['" + name + "'];";
2675 init(js);
2676 eval(js);
2677 bindings.erase(found);
2678 }
2679 }
2680
2681 void resolve(const std::string& seq, int status, const std::string& result)
2682 {
2683 dispatch(
2684 [seq, status, result, this]()
2685 {
2686 if (status == 0)
2687 {
2688 eval("window._rpc[" + seq + "].resolve(" + result +
2689 "); delete window._rpc[" + seq + "]");
2690 }
2691 else
2692 {
2693 eval("window._rpc[" + seq + "].reject(" + result +
2694 "); delete window._rpc[" + seq + "]");
2695 }
2696 });
2697 }
2698
2699 private:
2700 void on_message(const std::string& msg) override
2701 {
2702 auto seq = detail::json_parse(msg, "id", 0);
2703 auto name = detail::json_parse(msg, "method", 0);
2704 auto args = detail::json_parse(msg, "params", 0);
2705 auto found = bindings.find(name);
2706 if (found == bindings.end())
2707 {
2708 return;
2709 }
2710 const auto& context = found->second;
2711 context.callback(seq, args, context.arg);
2712 }
2713
2714 std::map<std::string, binding_ctx_t> bindings;
2715 };
2716} // namespace webview
2717
2718WEBVIEW_API webview_t webview_create(int debug, void* wnd)
2719{
2720 auto w = new webview::webview(debug, wnd);
2721 if (!w->window())
2722 {
2723 delete w;
2724 return nullptr;
2725 }
2726 return w;
2727}
2728
2729WEBVIEW_API void webview_destroy(webview_t w)
2730{
2731 delete static_cast<webview::webview*>(w);
2732}
2733
2734WEBVIEW_API void webview_run(webview_t w)
2735{
2736 static_cast<webview::webview*>(w)->run();
2737}
2738
2739WEBVIEW_API void webview_terminate(webview_t w)
2740{
2741 static_cast<webview::webview*>(w)->terminate();
2742}
2743
2744WEBVIEW_API void webview_dispatch(webview_t w, void (*fn)(webview_t, void*), void* arg)
2745{
2746 static_cast<webview::webview*>(w)->dispatch([=]() { fn(w, arg); });
2747}
2748
2749WEBVIEW_API void* webview_get_window(webview_t w)
2750{
2751 return static_cast<webview::webview*>(w)->window();
2752}
2753
2754WEBVIEW_API void webview_set_title(webview_t w, const char* title)
2755{
2756 static_cast<webview::webview*>(w)->set_title(title);
2757}
2758
2759WEBVIEW_API void webview_set_size(webview_t w, int width, int height, int hints)
2760{
2761 static_cast<webview::webview*>(w)->set_size(width, height, hints);
2762}
2763
2764WEBVIEW_API void webview_navigate(webview_t w, const char* url)
2765{
2766 static_cast<webview::webview*>(w)->navigate(url);
2767}
2768
2769WEBVIEW_API void webview_set_html(webview_t w, const char* html)
2770{
2771 static_cast<webview::webview*>(w)->set_html(html);
2772}
2773
2774WEBVIEW_API void webview_init(webview_t w, const char* js)
2775{
2776 static_cast<webview::webview*>(w)->init(js);
2777}
2778
2779WEBVIEW_API void webview_eval(webview_t w, const char* js)
2780{
2781 static_cast<webview::webview*>(w)->eval(js);
2782}
2783
2784WEBVIEW_API void webview_bind(webview_t w, const char* name,
2785 void (*fn)(const char* seq, const char* req, void* arg), void* arg)
2786{
2787 static_cast<webview::webview*>(w)->bind(
2788 name,
2789 [=](const std::string& seq, const std::string& req, void* arg)
2790 { fn(seq.c_str(), req.c_str(), arg); },
2791 arg);
2792}
2793
2794WEBVIEW_API void webview_unbind(webview_t w, const char* name)
2795{
2796 static_cast<webview::webview*>(w)->unbind(name);
2797}
2798
2799WEBVIEW_API void webview_return(webview_t w, const char* seq, int status, const char* result)
2800{
2801 static_cast<webview::webview*>(w)->resolve(seq, status, result);
2802}
2803
2804WEBVIEW_API const webview_version_info_t* webview_version()
2805{
2806 return &webview::detail::library_version_info;
2807}
2808
2809#endif /* WEBVIEW_HEADER */
2810#endif /* __cplusplus */
2811#endif /* WEBVIEW_H */