webview 0.12.0
A tiny cross-platform webview library for C/C++ to build modern cross-platform GUIs.
Loading...
Searching...
No Matches
webview.h
Go to the documentation of this file.
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
27
28#ifndef WEBVIEW_H
29#define WEBVIEW_H
30
46#ifndef WEBVIEW_API
47#if defined(WEBVIEW_SHARED) || defined(WEBVIEW_BUILD_SHARED)
48#if defined(_WIN32) || defined(__CYGWIN__)
49#if defined(WEBVIEW_BUILD_SHARED)
50#define WEBVIEW_API __declspec(dllexport)
51#else
52#define WEBVIEW_API __declspec(dllimport)
53#endif
54#else
55#define WEBVIEW_API __attribute__((visibility("default")))
56#endif
57#elif !defined(WEBVIEW_STATIC) && defined(__cplusplus)
58#define WEBVIEW_API inline
59#else
60#define WEBVIEW_API extern
61#endif
62#endif
63
66
67#ifndef WEBVIEW_VERSION_MAJOR
69#define WEBVIEW_VERSION_MAJOR 0
70#endif
71
72#ifndef WEBVIEW_VERSION_MINOR
74#define WEBVIEW_VERSION_MINOR 12
75#endif
76
77#ifndef WEBVIEW_VERSION_PATCH
79#define WEBVIEW_VERSION_PATCH 0
80#endif
81
82#ifndef WEBVIEW_VERSION_PRE_RELEASE
84#define WEBVIEW_VERSION_PRE_RELEASE ""
85#endif
86
87#ifndef WEBVIEW_VERSION_BUILD_METADATA
89#define WEBVIEW_VERSION_BUILD_METADATA ""
90#endif
91
93
96
98#define WEBVIEW_STRINGIFY(x) #x
99
101#define WEBVIEW_EXPAND_AND_STRINGIFY(x) WEBVIEW_STRINGIFY(x)
102
104
107
109#define WEBVIEW_VERSION_NUMBER \
110 WEBVIEW_EXPAND_AND_STRINGIFY(WEBVIEW_VERSION_MAJOR) \
111 "." WEBVIEW_EXPAND_AND_STRINGIFY( \
112 WEBVIEW_VERSION_MINOR) "." WEBVIEW_EXPAND_AND_STRINGIFY(WEBVIEW_VERSION_PATCH)
113
115
117typedef struct {
119 unsigned int major;
121 unsigned int minor;
123 unsigned int patch;
125
138
140typedef void *webview_t;
141
155
167
170
203
206#define WEBVIEW_SUCCEEDED(error) ((int)(error) >= 0)
207
209#define WEBVIEW_FAILED(error) ((int)(error) < 0)
210
212
213#ifdef __cplusplus
214extern "C" {
215#endif
216
240WEBVIEW_API webview_t webview_create(int debug, void *window);
241
248
255
263
273 void (*fn)(webview_t w, void *arg),
274 void *arg);
275
285
296
304
320 webview_hint_t hints);
321
336
349
358
368
385 void (*fn)(const char *id,
386 const char *req, void *arg),
387 void *arg);
388
397
411 int status, const char *result);
412
419
420#ifdef __cplusplus
421}
422
423#ifndef WEBVIEW_HEADER
424
425#if !defined(WEBVIEW_GTK) && !defined(WEBVIEW_COCOA) && !defined(WEBVIEW_EDGE)
426#if defined(__APPLE__)
427#define WEBVIEW_COCOA
428#elif defined(__unix__)
429#define WEBVIEW_GTK
430#elif defined(_WIN32)
431#define WEBVIEW_EDGE
432#else
433#error "please, specify webview backend"
434#endif
435#endif
436
437#ifndef WEBVIEW_DEPRECATED
438#if __cplusplus >= 201402L
439#define WEBVIEW_DEPRECATED(reason) [[deprecated(reason)]]
440#elif defined(_MSC_VER)
441#define WEBVIEW_DEPRECATED(reason) __declspec(deprecated(reason))
442#else
443#define WEBVIEW_DEPRECATED(reason) __attribute__((deprecated(reason)))
444#endif
445#endif
446
447#ifndef WEBVIEW_DEPRECATED_PRIVATE
448#define WEBVIEW_DEPRECATED_PRIVATE \
449 WEBVIEW_DEPRECATED("Private API should not be used")
450#endif
451
452#include <algorithm>
453#include <array>
454#include <atomic>
455#include <cassert>
456#include <cstdint>
457#include <functional>
458#include <future>
459#include <list>
460#include <map>
461#include <new>
462#include <stdexcept>
463#include <string>
464#include <type_traits>
465#include <utility>
466#include <vector>
467
468#include <cstring>
469
470#if defined(_WIN32)
471#ifndef WIN32_LEAN_AND_MEAN
472#define WIN32_LEAN_AND_MEAN
473#endif
474#include <windows.h>
475#else
476#include <dlfcn.h>
477#endif
478
479namespace webview {
480namespace detail {
481
482class bad_access : public std::exception {};
483
484template <typename T> class optional {
485public:
486 // NOLINTNEXTLINE(cppcoreguidelines-pro-type-member-init, hicpp-member-init)
487 optional() = default;
488
489 optional(const T &other) noexcept : m_has_data{true} {
490 new (&m_data) T{other};
491 }
492
493 optional(T &&other) noexcept : m_has_data{true} {
494 new (&m_data) T{std::move(other)};
495 }
496
497 optional(const optional<T> &other) noexcept { *this = other; }
498
499 optional &operator=(const optional<T> &other) noexcept {
500 if (this == &other) {
501 return *this;
502 }
503 m_has_data = other.has_value();
504 if (m_has_data) {
505 // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
506 new (&m_data) T{*reinterpret_cast<const T *>(&other.m_data)};
507 }
508 return *this;
509 }
510
511 optional(optional<T> &&other) noexcept { *this = std::move(other); }
512
513 optional &operator=(optional<T> &&other) noexcept {
514 if (this == &other) {
515 return *this;
516 }
517 m_has_data = other.has_value();
518 if (m_has_data) {
519 // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
520 new (&m_data) T{std::move(*reinterpret_cast<T *>(&other.m_data))};
521 }
522 return *this;
523 }
524
525 ~optional() {
526 if (m_has_data) {
527 // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
528 reinterpret_cast<T *>(&m_data)->~T();
529 }
530 }
531
532 const T &get() const {
533 if (!m_has_data) {
534 throw bad_access{};
535 }
536 // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
537 return *reinterpret_cast<const T *>(&m_data);
538 }
539
540 T &get() {
541 if (!m_has_data) {
542 throw bad_access{};
543 }
544 // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
545 return *reinterpret_cast<T *>(&m_data);
546 }
547
548 bool has_value() const { return m_has_data; }
549
550private:
551 // NOLINTNEXTLINE(bugprone-sizeof-expression): pointer to aggregate is OK
552 typename std::aligned_storage<sizeof(T), alignof(T)>::type m_data;
553 bool m_has_data{};
554};
555
556template <> class optional<void> {};
557
558template <typename Value, typename Error, typename Exception>
559class basic_result {
560public:
561 using value_type = Value;
562 using error_type = Error;
563 using exception_type = Exception;
564
565 basic_result() : basic_result(value_type{}) {}
566
567 basic_result(const value_type &value) : m_value{value} {}
568 basic_result(value_type &&value) : m_value{std::forward<value_type>(value)} {}
569
570 basic_result(const error_type &error) : m_error{error} {}
571 basic_result(error_type &&error) : m_error{std::forward<error_type>(error)} {}
572
573 bool ok() const { return has_value() && !has_error(); }
574 bool has_value() const { return m_value.has_value(); }
575 bool has_error() const { return m_error.has_value(); }
576
577 void ensure_ok() {
578 if (!ok()) {
579 throw exception_type{error()};
580 }
581 }
582
583 const value_type &value() const {
584 if (!has_value()) {
585 throw bad_access{};
586 }
587 return m_value.get();
588 }
589
590 const error_type &error() const {
591 if (!has_error()) {
592 throw bad_access{};
593 }
594 return m_error.get();
595 }
596
597private:
598 optional<value_type> m_value;
599 optional<error_type> m_error;
600};
601
602template <typename Error, typename Exception>
603class basic_result<void, Error, Exception> {
604public:
605 using value_type = void;
606 using error_type = Error;
607 using exception_type = Exception;
608
609 basic_result() = default;
610
611 basic_result(error_type &&error) : m_error{std::forward<error_type>(error)} {}
612
613 bool ok() const { return !has_error(); }
614
615 bool has_error() const { return m_error.has_value(); }
616
617 void ensure_ok() {
618 if (!ok()) {
619 throw exception_type{error()};
620 }
621 }
622
623 const error_type &error() const {
624 if (!has_error()) {
625 throw bad_access{};
626 }
627 return m_error.get();
628 }
629
630private:
631 optional<error_type> m_error;
632};
633
634} // namespace detail
635
636using dispatch_fn_t = std::function<void()>;
637
638class error_info {
639public:
640 error_info(webview_error_t code, const std::string &message = {}) noexcept
641 : m_code{code}, m_message{message} {}
642 error_info() = default;
643
644 webview_error_t code() const { return m_code; }
645 const std::string &message() const { return m_message; }
646
647private:
649 std::string m_message;
650};
651
652class exception : public std::exception {
653public:
654 exception(webview_error_t code, const std::string &message,
655 std::exception_ptr cause) noexcept
656 : exception{error_info{code, message}, cause} {}
657
658 exception(webview_error_t code, const std::string &message) noexcept
659 : exception{error_info{code, message}} {}
660
661 exception(const error_info &error, std::exception_ptr cause) noexcept
662 : m_error{error},
663 // NOLINTNEXTLINE(bugprone-throw-keyword-missing)
664 m_cause{cause} {}
665
666 exception(const error_info &error) noexcept : m_error{error} {}
667
668 exception() = default;
669
670 const error_info &error() const { return m_error; }
671 std::exception_ptr cause() const { return m_cause; }
672
673 const char *what() const noexcept override {
674 return m_error.message().c_str();
675 }
676
677private:
678 error_info m_error{WEBVIEW_ERROR_UNSPECIFIED};
679 std::exception_ptr m_cause;
680};
681
682template <typename T>
683using result = detail::basic_result<T, error_info, exception>;
684
685using noresult = detail::basic_result<void, error_info, exception>;
686
687namespace detail {
688
689// The library's version information.
690constexpr const webview_version_info_t library_version_info{
695
696#if defined(_WIN32)
697// Converts a narrow (UTF-8-encoded) string into a wide (UTF-16-encoded) string.
698inline std::wstring widen_string(const std::string &input) {
699 if (input.empty()) {
700 return std::wstring();
701 }
702 UINT cp = CP_UTF8;
703 DWORD flags = MB_ERR_INVALID_CHARS;
704 auto input_c = input.c_str();
705 auto input_length = static_cast<int>(input.size());
706 auto required_length =
707 MultiByteToWideChar(cp, flags, input_c, input_length, nullptr, 0);
708 if (required_length > 0) {
709 std::wstring output(static_cast<std::size_t>(required_length), L'\0');
710 if (MultiByteToWideChar(cp, flags, input_c, input_length, &output[0],
711 required_length) > 0) {
712 return output;
713 }
714 }
715 // Failed to convert string from UTF-8 to UTF-16
716 return std::wstring();
717}
718
719// Converts a wide (UTF-16-encoded) string into a narrow (UTF-8-encoded) string.
720inline std::string narrow_string(const std::wstring &input) {
721 struct wc_flags {
722 enum TYPE : unsigned int {
723 // WC_ERR_INVALID_CHARS
724 err_invalid_chars = 0x00000080U
725 };
726 };
727 if (input.empty()) {
728 return std::string();
729 }
730 UINT cp = CP_UTF8;
731 DWORD flags = wc_flags::err_invalid_chars;
732 auto input_c = input.c_str();
733 auto input_length = static_cast<int>(input.size());
734 auto required_length = WideCharToMultiByte(cp, flags, input_c, input_length,
735 nullptr, 0, nullptr, nullptr);
736 if (required_length > 0) {
737 std::string output(static_cast<std::size_t>(required_length), '\0');
738 if (WideCharToMultiByte(cp, flags, input_c, input_length, &output[0],
739 required_length, nullptr, nullptr) > 0) {
740 return output;
741 }
742 }
743 // Failed to convert string from UTF-16 to UTF-8
744 return std::string();
745}
746#endif
747
748inline int json_parse_c(const char *s, size_t sz, const char *key, size_t keysz,
749 const char **value, size_t *valuesz) {
750 enum {
751 JSON_STATE_VALUE,
752 JSON_STATE_LITERAL,
753 JSON_STATE_STRING,
754 JSON_STATE_ESCAPE,
755 JSON_STATE_UTF8
756 } state = JSON_STATE_VALUE;
757 const char *k = nullptr;
758 int index = 1;
759 int depth = 0;
760 int utf8_bytes = 0;
761
762 *value = nullptr;
763 *valuesz = 0;
764
765 if (key == nullptr) {
766 index = static_cast<decltype(index)>(keysz);
767 if (index < 0) {
768 return -1;
769 }
770 keysz = 0;
771 }
772
773 for (; sz > 0; s++, sz--) {
774 enum {
775 JSON_ACTION_NONE,
776 JSON_ACTION_START,
777 JSON_ACTION_END,
778 JSON_ACTION_START_STRUCT,
779 JSON_ACTION_END_STRUCT
780 } action = JSON_ACTION_NONE;
781 auto c = static_cast<unsigned char>(*s);
782 switch (state) {
783 case JSON_STATE_VALUE:
784 if (c == ' ' || c == '\t' || c == '\n' || c == '\r' || c == ',' ||
785 c == ':') {
786 continue;
787 } else if (c == '"') {
788 action = JSON_ACTION_START;
789 state = JSON_STATE_STRING;
790 } else if (c == '{' || c == '[') {
791 action = JSON_ACTION_START_STRUCT;
792 } else if (c == '}' || c == ']') {
793 action = JSON_ACTION_END_STRUCT;
794 } else if (c == 't' || c == 'f' || c == 'n' || c == '-' ||
795 (c >= '0' && c <= '9')) {
796 action = JSON_ACTION_START;
797 state = JSON_STATE_LITERAL;
798 } else {
799 return -1;
800 }
801 break;
802 case JSON_STATE_LITERAL:
803 if (c == ' ' || c == '\t' || c == '\n' || c == '\r' || c == ',' ||
804 c == ']' || c == '}' || c == ':') {
805 state = JSON_STATE_VALUE;
806 s--;
807 sz++;
808 action = JSON_ACTION_END;
809 } else if (c < 32 || c > 126) {
810 return -1;
811 } // fallthrough
812 case JSON_STATE_STRING:
813 if (c < 32 || (c > 126 && c < 192)) {
814 return -1;
815 } else if (c == '"') {
816 action = JSON_ACTION_END;
817 state = JSON_STATE_VALUE;
818 } else if (c == '\\') {
819 state = JSON_STATE_ESCAPE;
820 } else if (c >= 192 && c < 224) {
821 utf8_bytes = 1;
822 state = JSON_STATE_UTF8;
823 } else if (c >= 224 && c < 240) {
824 utf8_bytes = 2;
825 state = JSON_STATE_UTF8;
826 } else if (c >= 240 && c < 247) {
827 utf8_bytes = 3;
828 state = JSON_STATE_UTF8;
829 } else if (c >= 128 && c < 192) {
830 return -1;
831 }
832 break;
833 case JSON_STATE_ESCAPE:
834 if (c == '"' || c == '\\' || c == '/' || c == 'b' || c == 'f' ||
835 c == 'n' || c == 'r' || c == 't' || c == 'u') {
836 state = JSON_STATE_STRING;
837 } else {
838 return -1;
839 }
840 break;
841 case JSON_STATE_UTF8:
842 if (c < 128 || c > 191) {
843 return -1;
844 }
845 utf8_bytes--;
846 if (utf8_bytes == 0) {
847 state = JSON_STATE_STRING;
848 }
849 break;
850 default:
851 return -1;
852 }
853
854 if (action == JSON_ACTION_END_STRUCT) {
855 depth--;
856 }
857
858 if (depth == 1) {
859 if (action == JSON_ACTION_START || action == JSON_ACTION_START_STRUCT) {
860 if (index == 0) {
861 *value = s;
862 } else if (keysz > 0 && index == 1) {
863 k = s;
864 } else {
865 index--;
866 }
867 } else if (action == JSON_ACTION_END ||
868 action == JSON_ACTION_END_STRUCT) {
869 if (*value != nullptr && index == 0) {
870 *valuesz = static_cast<size_t>(s + 1 - *value);
871 return 0;
872 } else if (keysz > 0 && k != nullptr) {
873 if (keysz == static_cast<size_t>(s - k - 1) &&
874 memcmp(key, k + 1, keysz) == 0) {
875 index = 0;
876 } else {
877 index = 2;
878 }
879 k = nullptr;
880 }
881 }
882 }
883
884 if (action == JSON_ACTION_START_STRUCT) {
885 depth++;
886 }
887 }
888 return -1;
889}
890
891constexpr bool is_json_special_char(char c) {
892 return c == '"' || c == '\\' || c == '\b' || c == '\f' || c == '\n' ||
893 c == '\r' || c == '\t';
894}
895
896constexpr bool is_ascii_control_char(char c) { return c >= 0 && c <= 0x1f; }
897
898inline std::string json_escape(const std::string &s, bool add_quotes = true) {
899 // Calculate the size of the resulting string.
900 // Add space for the double quotes.
901 size_t required_length = add_quotes ? 2 : 0;
902 for (auto c : s) {
903 if (is_json_special_char(c)) {
904 // '\' and a single following character
905 required_length += 2;
906 continue;
907 }
908 if (is_ascii_control_char(c)) {
909 // '\', 'u', 4 digits
910 required_length += 6;
911 continue;
912 }
913 ++required_length;
914 }
915 // Allocate memory for resulting string only once.
916 std::string result;
917 result.reserve(required_length);
918 if (add_quotes) {
919 result += '"';
920 }
921 // Copy string while escaping characters.
922 for (auto c : s) {
923 if (is_json_special_char(c)) {
924 static constexpr char special_escape_table[256] =
925 "\0\0\0\0\0\0\0\0btn\0fr\0\0"
926 "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"
927 "\0\0\"\0\0\0\0\0\0\0\0\0\0\0\0\0"
928 "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"
929 "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"
930 "\0\0\0\0\0\0\0\0\0\0\0\0\\";
931 result += '\\';
932 // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-constant-array-index)
933 result += special_escape_table[static_cast<unsigned char>(c)];
934 continue;
935 }
936 if (is_ascii_control_char(c)) {
937 // Escape as \u00xx
938 static constexpr char hex_alphabet[]{"0123456789abcdef"};
939 auto uc = static_cast<unsigned char>(c);
940 auto h = (uc >> 4) & 0x0f;
941 auto l = uc & 0x0f;
942 result += "\\u00";
943 // NOLINTBEGIN(cppcoreguidelines-pro-bounds-constant-array-index)
944 result += hex_alphabet[h];
945 result += hex_alphabet[l];
946 // NOLINTEND(cppcoreguidelines-pro-bounds-constant-array-index)
947 continue;
948 }
949 result += c;
950 }
951 if (add_quotes) {
952 result += '"';
953 }
954 // Should have calculated the exact amount of memory needed
955 assert(required_length == result.size());
956 return result;
957}
958
959inline int json_unescape(const char *s, size_t n, char *out) {
960 int r = 0;
961 if (*s++ != '"') {
962 return -1;
963 }
964 while (n > 2) {
965 char c = *s;
966 if (c == '\\') {
967 s++;
968 n--;
969 switch (*s) {
970 case 'b':
971 c = '\b';
972 break;
973 case 'f':
974 c = '\f';
975 break;
976 case 'n':
977 c = '\n';
978 break;
979 case 'r':
980 c = '\r';
981 break;
982 case 't':
983 c = '\t';
984 break;
985 case '\\':
986 c = '\\';
987 break;
988 case '/':
989 c = '/';
990 break;
991 case '\"':
992 c = '\"';
993 break;
994 default: // TODO: support unicode decoding
995 return -1;
996 }
997 }
998 if (out != nullptr) {
999 *out++ = c;
1000 }
1001 s++;
1002 n--;
1003 r++;
1004 }
1005 if (*s != '"') {
1006 return -1;
1007 }
1008 if (out != nullptr) {
1009 *out = '\0';
1010 }
1011 return r;
1012}
1013
1014inline std::string json_parse(const std::string &s, const std::string &key,
1015 const int index) {
1016 const char *value;
1017 size_t value_sz;
1018 if (key.empty()) {
1019 json_parse_c(s.c_str(), s.length(), nullptr, index, &value, &value_sz);
1020 } else {
1021 json_parse_c(s.c_str(), s.length(), key.c_str(), key.length(), &value,
1022 &value_sz);
1023 }
1024 if (value != nullptr) {
1025 if (value[0] != '"') {
1026 return {value, value_sz};
1027 }
1028 int n = json_unescape(value, value_sz, nullptr);
1029 if (n > 0) {
1030 char *decoded = new char[n + 1];
1031 json_unescape(value, value_sz, decoded);
1032 std::string result(decoded, n);
1033 delete[] decoded;
1034 return result;
1035 }
1036 }
1037 return "";
1038}
1039
1040// Holds a symbol name and associated type for code clarity.
1041template <typename T> class library_symbol {
1042public:
1043 using type = T;
1044
1045 constexpr explicit library_symbol(const char *name) : m_name(name) {}
1046 constexpr const char *get_name() const { return m_name; }
1047
1048private:
1049 const char *m_name;
1050};
1051
1052// Loads a native shared library and allows one to get addresses for those
1053// symbols.
1054class native_library {
1055public:
1056 native_library() = default;
1057
1058 explicit native_library(const std::string &name)
1059 : m_handle{load_library(name)} {}
1060
1061#ifdef _WIN32
1062 explicit native_library(const std::wstring &name)
1063 : m_handle{load_library(name)} {}
1064#endif
1065
1066 ~native_library() {
1067 if (m_handle) {
1068#ifdef _WIN32
1069 FreeLibrary(m_handle);
1070#else
1071 dlclose(m_handle);
1072#endif
1073 m_handle = nullptr;
1074 }
1075 }
1076
1077 native_library(const native_library &other) = delete;
1078 native_library &operator=(const native_library &other) = delete;
1079 native_library(native_library &&other) noexcept { *this = std::move(other); }
1080
1081 native_library &operator=(native_library &&other) noexcept {
1082 if (this == &other) {
1083 return *this;
1084 }
1085 m_handle = other.m_handle;
1086 other.m_handle = nullptr;
1087 return *this;
1088 }
1089
1090 // Returns true if the library is currently loaded; otherwise false.
1091 operator bool() const { return is_loaded(); }
1092
1093 // Get the address for the specified symbol or nullptr if not found.
1094 template <typename Symbol>
1095 typename Symbol::type get(const Symbol &symbol) const {
1096 if (is_loaded()) {
1097 // NOLINTBEGIN(cppcoreguidelines-pro-type-reinterpret-cast)
1098#ifdef _WIN32
1099#ifdef __GNUC__
1100#pragma GCC diagnostic push
1101#pragma GCC diagnostic ignored "-Wcast-function-type"
1102#endif
1103 return reinterpret_cast<typename Symbol::type>(
1104 GetProcAddress(m_handle, symbol.get_name()));
1105#ifdef __GNUC__
1106#pragma GCC diagnostic pop
1107#endif
1108#else
1109 return reinterpret_cast<typename Symbol::type>(
1110 dlsym(m_handle, symbol.get_name()));
1111#endif
1112 // NOLINTEND(cppcoreguidelines-pro-type-reinterpret-cast)
1113 }
1114 return nullptr;
1115 }
1116
1117 // Returns true if the library is currently loaded; otherwise false.
1118 bool is_loaded() const { return !!m_handle; }
1119
1120 void detach() { m_handle = nullptr; }
1121
1122 // Returns true if the library by the given name is currently loaded; otherwise false.
1123 static inline bool is_loaded(const std::string &name) {
1124#ifdef _WIN32
1125 auto handle = GetModuleHandleW(widen_string(name).c_str());
1126#else
1127 auto handle = dlopen(name.c_str(), RTLD_NOW | RTLD_NOLOAD);
1128 if (handle) {
1129 dlclose(handle);
1130 }
1131#endif
1132 return !!handle;
1133 }
1134
1135private:
1136#ifdef _WIN32
1137 using mod_handle_t = HMODULE;
1138#else
1139 using mod_handle_t = void *;
1140#endif
1141
1142 static inline mod_handle_t load_library(const std::string &name) {
1143#ifdef _WIN32
1144 return load_library(widen_string(name));
1145#else
1146 return dlopen(name.c_str(), RTLD_NOW);
1147#endif
1148 }
1149
1150#ifdef _WIN32
1151 static inline mod_handle_t load_library(const std::wstring &name) {
1152 return LoadLibraryW(name.c_str());
1153 }
1154#endif
1155
1156 mod_handle_t m_handle{};
1157};
1158
1159template <typename WorkFn, typename ResultFn>
1160webview_error_t api_filter(WorkFn &&do_work, ResultFn &&put_result) noexcept {
1161 try {
1162 auto result = do_work();
1163 if (result.ok()) {
1164 put_result(result.value());
1165 return WEBVIEW_ERROR_OK;
1166 }
1167 return result.error().code();
1168 } catch (const exception &e) {
1169 return e.error().code();
1170 } catch (...) {
1172 }
1173}
1174
1175template <typename WorkFn>
1176webview_error_t api_filter(WorkFn &&do_work) noexcept {
1177 try {
1178 auto result = do_work();
1179 if (result.ok()) {
1180 return WEBVIEW_ERROR_OK;
1181 }
1182 return result.error().code();
1183 } catch (const exception &e) {
1184 return e.error().code();
1185 } catch (...) {
1187 }
1188}
1189
1190class user_script {
1191public:
1192 class impl;
1193
1194 user_script(const std::string &code, std::unique_ptr<impl> &&impl_)
1195 : m_code{code}, m_impl{std::move(impl_)} {}
1196
1197 user_script(const user_script &other) = delete;
1198 user_script &operator=(const user_script &other) = delete;
1199 user_script(user_script &&other) noexcept { *this = std::move(other); }
1200
1201 user_script &operator=(user_script &&other) noexcept {
1202 if (this == &other) {
1203 return *this;
1204 }
1205 m_code = std::move(other.m_code);
1206 m_impl = std::move(other.m_impl);
1207 return *this;
1208 }
1209
1210 const std::string &get_code() const { return m_code; }
1211
1212 impl &get_impl() { return *m_impl; }
1213
1214 const impl &get_impl() const { return *m_impl; }
1215
1216private:
1217 std::string m_code;
1218 std::unique_ptr<impl> m_impl;
1219};
1220
1221class engine_base {
1222public:
1223 virtual ~engine_base() = default;
1224
1225 noresult navigate(const std::string &url) {
1226 if (url.empty()) {
1227 return navigate_impl("about:blank");
1228 }
1229 return navigate_impl(url);
1230 }
1231
1232 using binding_t = std::function<void(std::string, std::string, void *)>;
1233 class binding_ctx_t {
1234 public:
1235 binding_ctx_t(binding_t callback, void *arg)
1236 : m_callback(callback), m_arg(arg) {}
1237 void call(std::string id, std::string args) const {
1238 if (m_callback) {
1239 m_callback(id, args, m_arg);
1240 }
1241 }
1242
1243 private:
1244 // This function is called upon execution of the bound JS function
1245 binding_t m_callback;
1246 // This user-supplied argument is passed to the callback
1247 void *m_arg;
1248 };
1249
1250 using sync_binding_t = std::function<std::string(std::string)>;
1251
1252 // Synchronous bind
1253 noresult bind(const std::string &name, sync_binding_t fn) {
1254 auto wrapper = [this, fn](const std::string &id, const std::string &req,
1255 void * /*arg*/) { resolve(id, 0, fn(req)); };
1256 return bind(name, wrapper, nullptr);
1257 }
1258
1259 // Asynchronous bind
1260 noresult bind(const std::string &name, binding_t fn, void *arg) {
1261 // NOLINTNEXTLINE(readability-container-contains): contains() requires C++20
1262 if (bindings.count(name) > 0) {
1263 return error_info{WEBVIEW_ERROR_DUPLICATE};
1264 }
1265 bindings.emplace(name, binding_ctx_t(fn, arg));
1266 replace_bind_script();
1267 // Notify that a binding was created if the init script has already
1268 // set things up.
1269 eval("if (window.__webview__) {\n\
1270window.__webview__.onBind(" +
1271 json_escape(name) + ")\n\
1272}");
1273 return {};
1274 }
1275
1276 noresult unbind(const std::string &name) {
1277 auto found = bindings.find(name);
1278 if (found == bindings.end()) {
1279 return error_info{WEBVIEW_ERROR_NOT_FOUND};
1280 }
1281 bindings.erase(found);
1282 replace_bind_script();
1283 // Notify that a binding was created if the init script has already
1284 // set things up.
1285 eval("if (window.__webview__) {\n\
1286window.__webview__.onUnbind(" +
1287 json_escape(name) + ")\n\
1288}");
1289 return {};
1290 }
1291
1292 noresult resolve(const std::string &id, int status,
1293 const std::string &result) {
1294 // NOLINTNEXTLINE(modernize-avoid-bind): Lambda with move requires C++14
1295 return dispatch(std::bind(
1296 [id, status, this](std::string escaped_result) {
1297 std::string js = "window.__webview__.onReply(" + json_escape(id) +
1298 ", " + std::to_string(status) + ", " +
1299 escaped_result + ")";
1300 eval(js);
1301 },
1302 result.empty() ? "undefined" : json_escape(result)));
1303 }
1304
1305 result<void *> window() { return window_impl(); }
1306 result<void *> widget() { return widget_impl(); }
1307 result<void *> browser_controller() { return browser_controller_impl(); }
1308 noresult run() { return run_impl(); }
1309 noresult terminate() { return terminate_impl(); }
1310 noresult dispatch(std::function<void()> f) { return dispatch_impl(f); }
1311 noresult set_title(const std::string &title) { return set_title_impl(title); }
1312
1313 noresult set_size(int width, int height, webview_hint_t hints) {
1314 return set_size_impl(width, height, hints);
1315 }
1316
1317 noresult set_html(const std::string &html) { return set_html_impl(html); }
1318
1319 noresult init(const std::string &js) {
1320 add_user_script(js);
1321 return {};
1322 }
1323
1324 noresult eval(const std::string &js) { return eval_impl(js); }
1325
1326protected:
1327 virtual noresult navigate_impl(const std::string &url) = 0;
1328 virtual result<void *> window_impl() = 0;
1329 virtual result<void *> widget_impl() = 0;
1330 virtual result<void *> browser_controller_impl() = 0;
1331 virtual noresult run_impl() = 0;
1332 virtual noresult terminate_impl() = 0;
1333 virtual noresult dispatch_impl(std::function<void()> f) = 0;
1334 virtual noresult set_title_impl(const std::string &title) = 0;
1335 virtual noresult set_size_impl(int width, int height,
1336 webview_hint_t hints) = 0;
1337 virtual noresult set_html_impl(const std::string &html) = 0;
1338 virtual noresult eval_impl(const std::string &js) = 0;
1339
1340 virtual user_script *add_user_script(const std::string &js) {
1341 return std::addressof(*m_user_scripts.emplace(m_user_scripts.end(),
1342 add_user_script_impl(js)));
1343 }
1344
1345 virtual user_script add_user_script_impl(const std::string &js) = 0;
1346
1347 virtual void
1348 remove_all_user_scripts_impl(const std::list<user_script> &scripts) = 0;
1349
1350 virtual bool are_user_scripts_equal_impl(const user_script &first,
1351 const user_script &second) = 0;
1352
1353 virtual user_script *replace_user_script(const user_script &old_script,
1354 const std::string &new_script_code) {
1355 remove_all_user_scripts_impl(m_user_scripts);
1356 user_script *old_script_ptr{};
1357 for (auto &script : m_user_scripts) {
1358 auto is_old_script = are_user_scripts_equal_impl(script, old_script);
1359 script = add_user_script_impl(is_old_script ? new_script_code
1360 : script.get_code());
1361 if (is_old_script) {
1362 old_script_ptr = std::addressof(script);
1363 }
1364 }
1365 return old_script_ptr;
1366 }
1367
1368 void replace_bind_script() {
1369 if (m_bind_script) {
1370 m_bind_script = replace_user_script(*m_bind_script, create_bind_script());
1371 } else {
1372 m_bind_script = add_user_script(create_bind_script());
1373 }
1374 }
1375
1376 void add_init_script(const std::string &post_fn) {
1377 add_user_script(create_init_script(post_fn));
1378 }
1379
1380 std::string create_init_script(const std::string &post_fn) {
1381 auto js = std::string{} + "(function() {\n\
1382 'use strict';\n\
1383 function generateId() {\n\
1384 var crypto = window.crypto || window.msCrypto;\n\
1385 var bytes = new Uint8Array(16);\n\
1386 crypto.getRandomValues(bytes);\n\
1387 return Array.prototype.slice.call(bytes).map(function(n) {\n\
1388 return n.toString(16).padStart(2, '0');\n\
1389 }).join('');\n\
1390 }\n\
1391 var Webview = (function() {\n\
1392 var _promises = {};\n\
1393 function Webview_() {}\n\
1394 Webview_.prototype.post = function(message) {\n\
1395 return (" +
1396 post_fn + ")(message);\n\
1397 };\n\
1398 Webview_.prototype.call = function(method) {\n\
1399 var _id = generateId();\n\
1400 var _params = Array.prototype.slice.call(arguments, 1);\n\
1401 var promise = new Promise(function(resolve, reject) {\n\
1402 _promises[_id] = { resolve, reject };\n\
1403 });\n\
1404 this.post(JSON.stringify({\n\
1405 id: _id,\n\
1406 method: method,\n\
1407 params: _params\n\
1408 }));\n\
1409 return promise;\n\
1410 };\n\
1411 Webview_.prototype.onReply = function(id, status, result) {\n\
1412 var promise = _promises[id];\n\
1413 if (result !== undefined) {\n\
1414 try {\n\
1415 result = JSON.parse(result);\n\
1416 } catch {\n\
1417 promise.reject(new Error(\"Failed to parse binding result as JSON\"));\n\
1418 return;\n\
1419 }\n\
1420 }\n\
1421 if (status === 0) {\n\
1422 promise.resolve(result);\n\
1423 } else {\n\
1424 promise.reject(result);\n\
1425 }\n\
1426 };\n\
1427 Webview_.prototype.onBind = function(name) {\n\
1428 if (Object.hasOwn(window, name)) {\n\
1429 throw new Error('Property \"' + name + '\" already exists');\n\
1430 }\n\
1431 window[name] = (function() {\n\
1432 var params = [name].concat(Array.prototype.slice.call(arguments));\n\
1433 return Webview_.prototype.call.apply(this, params);\n\
1434 }).bind(this);\n\
1435 };\n\
1436 Webview_.prototype.onUnbind = function(name) {\n\
1437 if (!Object.hasOwn(window, name)) {\n\
1438 throw new Error('Property \"' + name + '\" does not exist');\n\
1439 }\n\
1440 delete window[name];\n\
1441 };\n\
1442 return Webview_;\n\
1443 })();\n\
1444 window.__webview__ = new Webview();\n\
1445})()";
1446 return js;
1447 }
1448
1449 std::string create_bind_script() {
1450 std::string js_names = "[";
1451 bool first = true;
1452 for (const auto &binding : bindings) {
1453 if (first) {
1454 first = false;
1455 } else {
1456 js_names += ",";
1457 }
1458 js_names += json_escape(binding.first);
1459 }
1460 js_names += "]";
1461
1462 auto js = std::string{} + "(function() {\n\
1463 'use strict';\n\
1464 var methods = " +
1465 js_names + ";\n\
1466 methods.forEach(function(name) {\n\
1467 window.__webview__.onBind(name);\n\
1468 });\n\
1469})()";
1470 return js;
1471 }
1472
1473 virtual void on_message(const std::string &msg) {
1474 auto id = json_parse(msg, "id", 0);
1475 auto name = json_parse(msg, "method", 0);
1476 auto args = json_parse(msg, "params", 0);
1477 auto found = bindings.find(name);
1478 if (found == bindings.end()) {
1479 return;
1480 }
1481 const auto &context = found->second;
1482 dispatch([=] { context.call(id, args); });
1483 }
1484
1485 virtual void on_window_created() { inc_window_count(); }
1486
1487 virtual void on_window_destroyed(bool skip_termination = false) {
1488 if (dec_window_count() <= 0) {
1489 if (!skip_termination) {
1490 terminate();
1491 }
1492 }
1493 }
1494
1495private:
1496 static std::atomic_uint &window_ref_count() {
1497 static std::atomic_uint ref_count{0};
1498 return ref_count;
1499 }
1500
1501 static unsigned int inc_window_count() { return ++window_ref_count(); }
1502
1503 static unsigned int dec_window_count() {
1504 auto &count = window_ref_count();
1505 if (count > 0) {
1506 return --count;
1507 }
1508 return 0;
1509 }
1510
1511 std::map<std::string, binding_ctx_t> bindings;
1512 user_script *m_bind_script{};
1513 std::list<user_script> m_user_scripts;
1514};
1515
1516} // namespace detail
1517
1518WEBVIEW_DEPRECATED_PRIVATE
1519inline int json_parse_c(const char *s, size_t sz, const char *key, size_t keysz,
1520 const char **value, size_t *valuesz) {
1521 return detail::json_parse_c(s, sz, key, keysz, value, valuesz);
1522}
1523
1524WEBVIEW_DEPRECATED_PRIVATE
1525inline std::string json_escape(const std::string &s) {
1526 return detail::json_escape(s);
1527}
1528
1529WEBVIEW_DEPRECATED_PRIVATE
1530inline int json_unescape(const char *s, size_t n, char *out) {
1531 return detail::json_unescape(s, n, out);
1532}
1533
1534WEBVIEW_DEPRECATED_PRIVATE
1535inline std::string json_parse(const std::string &s, const std::string &key,
1536 const int index) {
1537 return detail::json_parse(s, key, index);
1538}
1539
1540} // namespace webview
1541
1542#if defined(WEBVIEW_GTK)
1543//
1544// ====================================================================
1545//
1546// This implementation uses webkit2gtk backend. It requires GTK and
1547// WebKitGTK libraries. Proper compiler flags can be retrieved via:
1548//
1549// pkg-config --cflags --libs gtk4 webkitgtk-6.0
1550// pkg-config --cflags --libs gtk+-3.0 webkit2gtk-4.1
1551// pkg-config --cflags --libs gtk+-3.0 webkit2gtk-4.0
1552//
1553// ====================================================================
1554//
1555#include <cstdlib>
1556
1557#include <gtk/gtk.h>
1558
1559#if GTK_MAJOR_VERSION >= 4
1560
1561#include <jsc/jsc.h>
1562#include <webkit/webkit.h>
1563
1564#ifdef GDK_WINDOWING_X11
1565#include <gdk/x11/gdkx.h>
1566#endif
1567
1568#elif GTK_MAJOR_VERSION >= 3
1569
1570#include <JavaScriptCore/JavaScript.h>
1571#include <webkit2/webkit2.h>
1572
1573#ifdef GDK_WINDOWING_X11
1574#include <gdk/gdkx.h>
1575#endif
1576
1577#endif
1578
1579#include <fcntl.h>
1580#include <sys/stat.h>
1581
1582namespace webview {
1583namespace detail {
1584
1585// Namespace containing workaround for WebKit 2.42 when using NVIDIA GPU
1586// driver.
1587// See WebKit bug: https://bugs.webkit.org/show_bug.cgi?id=261874
1588// Please remove all of the code in this namespace when it's no longer needed.
1589namespace webkit_dmabuf {
1590
1591// Get environment variable. Not thread-safe.
1592static inline std::string get_env(const std::string &name) {
1593 auto *value = std::getenv(name.c_str());
1594 if (value) {
1595 return {value};
1596 }
1597 return {};
1598}
1599
1600// Set environment variable. Not thread-safe.
1601static inline void set_env(const std::string &name, const std::string &value) {
1602 ::setenv(name.c_str(), value.c_str(), 1);
1603}
1604
1605// Checks whether the NVIDIA GPU driver is used based on whether the kernel
1606// module is loaded.
1607static inline bool is_using_nvidia_driver() {
1608 struct ::stat buffer {};
1609 if (::stat("/sys/module/nvidia", &buffer) != 0) {
1610 return false;
1611 }
1612 return S_ISDIR(buffer.st_mode);
1613}
1614
1615// Checks whether the windowing system is Wayland.
1616static inline bool is_wayland_display() {
1617 if (!get_env("WAYLAND_DISPLAY").empty()) {
1618 return true;
1619 }
1620 if (get_env("XDG_SESSION_TYPE") == "wayland") {
1621 return true;
1622 }
1623 if (get_env("DESKTOP_SESSION").find("wayland") != std::string::npos) {
1624 return true;
1625 }
1626 return false;
1627}
1628
1629// Checks whether the GDK X11 backend is used.
1630// See: https://docs.gtk.org/gdk3/class.DisplayManager.html
1631static inline bool is_gdk_x11_backend() {
1632#ifdef GDK_WINDOWING_X11
1633 auto *gdk_display = gdk_display_get_default();
1634 return GDK_IS_X11_DISPLAY(gdk_display); // NOLINT(misc-const-correctness)
1635#else
1636 return false;
1637#endif
1638}
1639
1640// Checks whether WebKit is affected by bug when using DMA-BUF renderer.
1641// Returns true if all of the following conditions are met:
1642// - WebKit version is >= 2.42 (please narrow this down when there's a fix).
1643// - Environment variables are empty or not set:
1644// - WEBKIT_DISABLE_DMABUF_RENDERER
1645// - Windowing system is not Wayland.
1646// - GDK backend is X11.
1647// - NVIDIA GPU driver is used.
1648static inline bool is_webkit_dmabuf_bugged() {
1649 auto wk_major = webkit_get_major_version();
1650 auto wk_minor = webkit_get_minor_version();
1651 // TODO: Narrow down affected WebKit version when there's a fixed version
1652 auto is_affected_wk_version = wk_major == 2 && wk_minor >= 42;
1653 if (!is_affected_wk_version) {
1654 return false;
1655 }
1656 if (!get_env("WEBKIT_DISABLE_DMABUF_RENDERER").empty()) {
1657 return false;
1658 }
1659 if (is_wayland_display()) {
1660 return false;
1661 }
1662 if (!is_gdk_x11_backend()) {
1663 return false;
1664 }
1665 if (!is_using_nvidia_driver()) {
1666 return false;
1667 }
1668 return true;
1669}
1670
1671// Applies workaround for WebKit DMA-BUF bug if needed.
1672// See WebKit bug: https://bugs.webkit.org/show_bug.cgi?id=261874
1673static inline void apply_webkit_dmabuf_workaround() {
1674 if (!is_webkit_dmabuf_bugged()) {
1675 return;
1676 }
1677 set_env("WEBKIT_DISABLE_DMABUF_RENDERER", "1");
1678}
1679} // namespace webkit_dmabuf
1680
1681class user_script::impl {
1682public:
1683 impl(WebKitUserScript *script) : m_script{script} {
1684 webkit_user_script_ref(script);
1685 }
1686
1687 ~impl() { webkit_user_script_unref(m_script); }
1688
1689 impl(const impl &) = delete;
1690 impl &operator=(const impl &) = delete;
1691 impl(impl &&) = delete;
1692 impl &operator=(impl &&) = delete;
1693
1694 WebKitUserScript *get_native() const { return m_script; }
1695
1696private:
1697 WebKitUserScript *m_script{};
1698};
1699
1703class gtk_compat {
1704public:
1705 static gboolean init_check() {
1706#if GTK_MAJOR_VERSION >= 4
1707 return gtk_init_check();
1708#else
1709 return gtk_init_check(nullptr, nullptr);
1710#endif
1711 }
1712
1713 static GtkWidget *window_new() {
1714#if GTK_MAJOR_VERSION >= 4
1715 return gtk_window_new();
1716#else
1717 return gtk_window_new(GTK_WINDOW_TOPLEVEL);
1718#endif
1719 }
1720
1721 static void window_set_child(GtkWindow *window, GtkWidget *widget) {
1722#if GTK_MAJOR_VERSION >= 4
1723 gtk_window_set_child(window, widget);
1724#else
1725 gtk_container_add(GTK_CONTAINER(window), widget);
1726#endif
1727 }
1728
1729 static void window_remove_child(GtkWindow *window, GtkWidget *widget) {
1730#if GTK_MAJOR_VERSION >= 4
1731 if (gtk_window_get_child(window) == widget) {
1732 gtk_window_set_child(window, nullptr);
1733 }
1734#else
1735 gtk_container_remove(GTK_CONTAINER(window), widget);
1736#endif
1737 }
1738
1739 static void widget_set_visible(GtkWidget *widget, bool visible) {
1740#if GTK_MAJOR_VERSION >= 4
1741 gtk_widget_set_visible(widget, visible ? TRUE : FALSE);
1742#else
1743 if (visible) {
1744 gtk_widget_show(widget);
1745 } else {
1746 gtk_widget_hide(widget);
1747 }
1748#endif
1749 }
1750
1751 static void window_set_size(GtkWindow *window, int width, int height) {
1752#if GTK_MAJOR_VERSION >= 4
1753 gtk_window_set_default_size(window, width, height);
1754#else
1755 gtk_window_resize(window, width, height);
1756#endif
1757 }
1758
1759 static void window_set_max_size(GtkWindow *window, int width, int height) {
1760// X11-specific features are available in GTK 3 but not GTK 4
1761#if GTK_MAJOR_VERSION < 4
1762 GdkGeometry g{};
1763 g.max_width = width;
1764 g.max_height = height;
1765 GdkWindowHints h = GDK_HINT_MAX_SIZE;
1766 gtk_window_set_geometry_hints(GTK_WINDOW(window), nullptr, &g, h);
1767#else
1768 // Avoid "unused parameter" warnings
1769 (void)window;
1770 (void)width;
1771 (void)height;
1772#endif
1773 }
1774};
1775
1779class webkitgtk_compat {
1780public:
1781#if GTK_MAJOR_VERSION >= 4
1782 using wk_handler_js_value_t = JSCValue;
1783#else
1784 using wk_handler_js_value_t = WebKitJavascriptResult;
1785#endif
1786
1787 using on_script_message_received_t =
1788 std::function<void(WebKitUserContentManager *, const std::string &)>;
1789 static void
1790 connect_script_message_received(WebKitUserContentManager *manager,
1791 const std::string &handler_name,
1792 on_script_message_received_t handler) {
1793 std::string signal_name = "script-message-received::";
1794 signal_name += handler_name;
1795
1796 auto callback = +[](WebKitUserContentManager *manager,
1797 wk_handler_js_value_t *r, gpointer arg) {
1798 auto *handler = static_cast<on_script_message_received_t *>(arg);
1799 (*handler)(manager, get_string_from_js_result(r));
1800 };
1801
1802 auto deleter = +[](gpointer data, GClosure *) {
1803 delete static_cast<on_script_message_received_t *>(data);
1804 };
1805
1806 g_signal_connect_data(manager, signal_name.c_str(), G_CALLBACK(callback),
1807 new on_script_message_received_t{handler}, deleter,
1808 static_cast<GConnectFlags>(0) /*G_CONNECT_DEFAULT*/);
1809 }
1810
1811 static std::string get_string_from_js_result(JSCValue *r) {
1812 char *cs = jsc_value_to_string(r);
1813 std::string s{cs};
1814 g_free(cs);
1815 return s;
1816 }
1817
1818#if GTK_MAJOR_VERSION < 4
1819 static std::string get_string_from_js_result(WebKitJavascriptResult *r) {
1820#if (WEBKIT_MAJOR_VERSION == 2 && WEBKIT_MINOR_VERSION >= 22) || \
1821 WEBKIT_MAJOR_VERSION > 2
1822 JSCValue *value = webkit_javascript_result_get_js_value(r);
1823 return get_string_from_js_result(value);
1824#else
1825 JSGlobalContextRef ctx = webkit_javascript_result_get_global_context(r);
1826 JSValueRef value = webkit_javascript_result_get_value(r);
1827 JSStringRef js = JSValueToStringCopy(ctx, value, nullptr);
1828 size_t n = JSStringGetMaximumUTF8CStringSize(js);
1829 char *cs = g_new(char, n);
1830 JSStringGetUTF8CString(js, cs, n);
1831 JSStringRelease(js);
1832 std::string s{cs};
1833 g_free(cs);
1834 return s;
1835#endif
1836 }
1837#endif
1838
1839 static void user_content_manager_register_script_message_handler(
1840 WebKitUserContentManager *manager, const gchar *name) {
1841#if GTK_MAJOR_VERSION >= 4
1842 webkit_user_content_manager_register_script_message_handler(manager, name,
1843 nullptr);
1844#else
1845 webkit_user_content_manager_register_script_message_handler(manager, name);
1846#endif
1847 }
1848};
1849
1850class gtk_webkit_engine : public engine_base {
1851public:
1852 gtk_webkit_engine(bool debug, void *window)
1853 : m_owns_window{!window}, m_window(static_cast<GtkWidget *>(window)) {
1854 if (m_owns_window) {
1855 if (!gtk_compat::init_check()) {
1856 throw exception{WEBVIEW_ERROR_UNSPECIFIED, "GTK init failed"};
1857 }
1858 m_window = gtk_compat::window_new();
1859 on_window_created();
1860 auto on_window_destroy = +[](GtkWidget *, gpointer arg) {
1861 auto *w = static_cast<gtk_webkit_engine *>(arg);
1862 w->m_window = nullptr;
1863 w->on_window_destroyed();
1864 };
1865 g_signal_connect(G_OBJECT(m_window), "destroy",
1866 G_CALLBACK(on_window_destroy), this);
1867 }
1868 webkit_dmabuf::apply_webkit_dmabuf_workaround();
1869 // Initialize webview widget
1870 m_webview = webkit_web_view_new();
1871 g_object_ref_sink(m_webview);
1872 WebKitUserContentManager *manager = m_user_content_manager =
1873 webkit_web_view_get_user_content_manager(WEBKIT_WEB_VIEW(m_webview));
1874 webkitgtk_compat::connect_script_message_received(
1875 manager, "__webview__",
1876 [this](WebKitUserContentManager *, const std::string &r) {
1877 on_message(r);
1878 });
1879 webkitgtk_compat::user_content_manager_register_script_message_handler(
1880 manager, "__webview__");
1881 add_init_script("function(message) {\n\
1882 return window.webkit.messageHandlers.__webview__.postMessage(message);\n\
1883}");
1884
1885 gtk_compat::window_set_child(GTK_WINDOW(m_window), GTK_WIDGET(m_webview));
1886 gtk_compat::widget_set_visible(GTK_WIDGET(m_webview), true);
1887
1888 WebKitSettings *settings =
1889 webkit_web_view_get_settings(WEBKIT_WEB_VIEW(m_webview));
1890 webkit_settings_set_javascript_can_access_clipboard(settings, true);
1891 if (debug) {
1892 webkit_settings_set_enable_write_console_messages_to_stdout(settings,
1893 true);
1894 webkit_settings_set_enable_developer_extras(settings, true);
1895 }
1896
1897 if (m_owns_window) {
1898 gtk_widget_grab_focus(GTK_WIDGET(m_webview));
1899 gtk_compat::widget_set_visible(GTK_WIDGET(m_window), true);
1900 }
1901 }
1902
1903 gtk_webkit_engine(const gtk_webkit_engine &) = delete;
1904 gtk_webkit_engine &operator=(const gtk_webkit_engine &) = delete;
1905 gtk_webkit_engine(gtk_webkit_engine &&) = delete;
1906 gtk_webkit_engine &operator=(gtk_webkit_engine &&) = delete;
1907
1908 virtual ~gtk_webkit_engine() {
1909 if (m_window) {
1910 if (m_owns_window) {
1911 // Disconnect handlers to avoid callbacks invoked during destruction.
1912 g_signal_handlers_disconnect_by_data(GTK_WINDOW(m_window), this);
1913 gtk_window_close(GTK_WINDOW(m_window));
1914 on_window_destroyed(true);
1915 } else {
1916 gtk_compat::window_remove_child(GTK_WINDOW(m_window),
1917 GTK_WIDGET(m_webview));
1918 }
1919 }
1920 if (m_webview) {
1921 g_object_unref(m_webview);
1922 }
1923 if (m_owns_window) {
1924 // Needed for the window to close immediately.
1925 deplete_run_loop_event_queue();
1926 }
1927 }
1928
1929protected:
1930 result<void *> window_impl() override {
1931 if (m_window) {
1932 return m_window;
1933 }
1934 return error_info{WEBVIEW_ERROR_INVALID_STATE};
1935 }
1936
1937 result<void *> widget_impl() override {
1938 if (m_webview) {
1939 return m_webview;
1940 }
1941 return error_info{WEBVIEW_ERROR_INVALID_STATE};
1942 }
1943
1944 result<void *> browser_controller_impl() override {
1945 if (m_webview) {
1946 return m_webview;
1947 }
1948 return error_info{WEBVIEW_ERROR_INVALID_STATE};
1949 }
1950
1951 noresult run_impl() override {
1952 m_stop_run_loop = false;
1953 while (!m_stop_run_loop) {
1954 g_main_context_iteration(nullptr, TRUE);
1955 }
1956 return {};
1957 }
1958
1959 noresult terminate_impl() override {
1960 return dispatch_impl([&] { m_stop_run_loop = true; });
1961 }
1962
1963 noresult dispatch_impl(std::function<void()> f) override {
1964 g_idle_add_full(G_PRIORITY_HIGH_IDLE, (GSourceFunc)([](void *fn) -> int {
1965 (*static_cast<dispatch_fn_t *>(fn))();
1966 return G_SOURCE_REMOVE;
1967 }),
1968 new std::function<void()>(f),
1969 [](void *fn) { delete static_cast<dispatch_fn_t *>(fn); });
1970 return {};
1971 }
1972
1973 noresult set_title_impl(const std::string &title) override {
1974 gtk_window_set_title(GTK_WINDOW(m_window), title.c_str());
1975 return {};
1976 }
1977
1978 noresult set_size_impl(int width, int height, webview_hint_t hints) override {
1979 gtk_window_set_resizable(GTK_WINDOW(m_window), hints != WEBVIEW_HINT_FIXED);
1980 if (hints == WEBVIEW_HINT_NONE) {
1981 gtk_compat::window_set_size(GTK_WINDOW(m_window), width, height);
1982 } else if (hints == WEBVIEW_HINT_FIXED || hints == WEBVIEW_HINT_MIN) {
1983 gtk_widget_set_size_request(m_window, width, height);
1984 } else if (hints == WEBVIEW_HINT_MAX) {
1985 gtk_compat::window_set_max_size(GTK_WINDOW(m_window), width, height);
1986 }
1987 return error_info{WEBVIEW_ERROR_INVALID_ARGUMENT, "Invalid hint"};
1988 }
1989
1990 noresult navigate_impl(const std::string &url) override {
1991 webkit_web_view_load_uri(WEBKIT_WEB_VIEW(m_webview), url.c_str());
1992 return {};
1993 }
1994
1995 noresult set_html_impl(const std::string &html) override {
1996 webkit_web_view_load_html(WEBKIT_WEB_VIEW(m_webview), html.c_str(),
1997 nullptr);
1998 return {};
1999 }
2000
2001 noresult eval_impl(const std::string &js) override {
2002 // URI is null before content has begun loading.
2003 if (!webkit_web_view_get_uri(WEBKIT_WEB_VIEW(m_webview))) {
2004 return {};
2005 }
2006#if (WEBKIT_MAJOR_VERSION == 2 && WEBKIT_MINOR_VERSION >= 40) || \
2007 WEBKIT_MAJOR_VERSION > 2
2008 webkit_web_view_evaluate_javascript(WEBKIT_WEB_VIEW(m_webview), js.c_str(),
2009 static_cast<gssize>(js.size()), nullptr,
2010 nullptr, nullptr, nullptr, nullptr);
2011#else
2012 webkit_web_view_run_javascript(WEBKIT_WEB_VIEW(m_webview), js.c_str(),
2013 nullptr, nullptr, nullptr);
2014#endif
2015 return {};
2016 }
2017
2018 user_script add_user_script_impl(const std::string &js) override {
2019 auto *wk_script = webkit_user_script_new(
2020 js.c_str(), WEBKIT_USER_CONTENT_INJECT_TOP_FRAME,
2021 WEBKIT_USER_SCRIPT_INJECT_AT_DOCUMENT_START, nullptr, nullptr);
2022 webkit_user_content_manager_add_script(m_user_content_manager, wk_script);
2023 user_script script{js, std::unique_ptr<user_script::impl>{
2024 new user_script::impl{wk_script}}};
2025 webkit_user_script_unref(wk_script);
2026 return script;
2027 }
2028
2029 void remove_all_user_scripts_impl(
2030 const std::list<user_script> & /*scripts*/) override {
2031 webkit_user_content_manager_remove_all_scripts(m_user_content_manager);
2032 }
2033
2034 bool are_user_scripts_equal_impl(const user_script &first,
2035 const user_script &second) override {
2036 auto *wk_first = first.get_impl().get_native();
2037 auto *wk_second = second.get_impl().get_native();
2038 return wk_first == wk_second;
2039 }
2040
2041private:
2042#if GTK_MAJOR_VERSION >= 4
2043 static char *get_string_from_js_result(JSCValue *r) {
2044 return jsc_value_to_string(r);
2045 }
2046#else
2047 static char *get_string_from_js_result(WebKitJavascriptResult *r) {
2048 char *s;
2049#if (WEBKIT_MAJOR_VERSION == 2 && WEBKIT_MINOR_VERSION >= 22) || \
2050 WEBKIT_MAJOR_VERSION > 2
2051 JSCValue *value = webkit_javascript_result_get_js_value(r);
2052 s = jsc_value_to_string(value);
2053#else
2054 JSGlobalContextRef ctx = webkit_javascript_result_get_global_context(r);
2055 JSValueRef value = webkit_javascript_result_get_value(r);
2056 JSStringRef js = JSValueToStringCopy(ctx, value, nullptr);
2057 size_t n = JSStringGetMaximumUTF8CStringSize(js);
2058 s = g_new(char, n);
2059 JSStringGetUTF8CString(js, s, n);
2060 JSStringRelease(js);
2061#endif
2062 return s;
2063 }
2064#endif
2065
2066 // Blocks while depleting the run loop of events.
2067 void deplete_run_loop_event_queue() {
2068 bool done{};
2069 dispatch([&] { done = true; });
2070 while (!done) {
2071 g_main_context_iteration(nullptr, TRUE);
2072 }
2073 }
2074
2075 bool m_owns_window{};
2076 GtkWidget *m_window{};
2077 GtkWidget *m_webview{};
2078 WebKitUserContentManager *m_user_content_manager{};
2079 bool m_stop_run_loop{};
2080};
2081
2082} // namespace detail
2083
2084using browser_engine = detail::gtk_webkit_engine;
2085
2086} // namespace webview
2087
2088#elif defined(WEBVIEW_COCOA)
2089
2090//
2091// ====================================================================
2092//
2093// This implementation uses Cocoa WKWebView backend on macOS. It is
2094// written using ObjC runtime and uses WKWebView class as a browser runtime.
2095// You should pass "-framework Webkit" flag to the compiler.
2096//
2097// ====================================================================
2098//
2099
2100#include <CoreGraphics/CoreGraphics.h>
2101#include <objc/NSObjCRuntime.h>
2102#include <objc/objc-runtime.h>
2103
2104namespace webview {
2105namespace detail {
2106namespace objc {
2107
2108// A convenient template function for unconditionally casting the specified
2109// C-like function into a function that can be called with the given return
2110// type and arguments. Caller takes full responsibility for ensuring that
2111// the function call is valid. It is assumed that the function will not
2112// throw exceptions.
2113template <typename Result, typename Callable, typename... Args>
2114Result invoke(Callable callable, Args... args) noexcept {
2115 return reinterpret_cast<Result (*)(Args...)>(callable)(args...);
2116}
2117
2118// Calls objc_msgSend.
2119template <typename Result, typename... Args>
2120Result msg_send(Args... args) noexcept {
2121 return invoke<Result>(objc_msgSend, args...);
2122}
2123
2124// Wrapper around NSAutoreleasePool that drains the pool on destruction.
2125class autoreleasepool {
2126public:
2127 autoreleasepool()
2128 : m_pool(msg_send<id>(objc_getClass("NSAutoreleasePool"),
2129 sel_registerName("new"))) {}
2130
2131 ~autoreleasepool() {
2132 if (m_pool) {
2133 msg_send<void>(m_pool, sel_registerName("drain"));
2134 }
2135 }
2136
2137 autoreleasepool(const autoreleasepool &) = delete;
2138 autoreleasepool &operator=(const autoreleasepool &) = delete;
2139 autoreleasepool(autoreleasepool &&) = delete;
2140 autoreleasepool &operator=(autoreleasepool &&) = delete;
2141
2142private:
2143 id m_pool{};
2144};
2145
2146inline id autoreleased(id object) {
2147 msg_send<void>(object, sel_registerName("autorelease"));
2148 return object;
2149}
2150
2151} // namespace objc
2152
2153enum NSBackingStoreType : NSUInteger { NSBackingStoreBuffered = 2 };
2154
2155enum NSWindowStyleMask : NSUInteger {
2156 NSWindowStyleMaskTitled = 1,
2157 NSWindowStyleMaskClosable = 2,
2158 NSWindowStyleMaskMiniaturizable = 4,
2159 NSWindowStyleMaskResizable = 8
2160};
2161
2162enum NSApplicationActivationPolicy : NSInteger {
2163 NSApplicationActivationPolicyRegular = 0
2164};
2165
2166enum WKUserScriptInjectionTime : NSInteger {
2167 WKUserScriptInjectionTimeAtDocumentStart = 0
2168};
2169
2170enum NSModalResponse : NSInteger { NSModalResponseOK = 1 };
2171
2172// Convenient conversion of string literals.
2173inline id operator"" _cls(const char *s, std::size_t) {
2174 return (id)objc_getClass(s);
2175}
2176inline SEL operator"" _sel(const char *s, std::size_t) {
2177 return sel_registerName(s);
2178}
2179inline id operator"" _str(const char *s, std::size_t) {
2180 return objc::msg_send<id>("NSString"_cls, "stringWithUTF8String:"_sel, s);
2181}
2182
2183class user_script::impl {
2184public:
2185 impl(id script) : m_script{script} {
2186 objc::msg_send<void>(script, "retain"_sel);
2187 }
2188
2189 ~impl() { objc::msg_send<void>(m_script, "release"_sel); }
2190
2191 impl(const impl &) = delete;
2192 impl &operator=(const impl &) = delete;
2193 impl(impl &&) = delete;
2194 impl &operator=(impl &&) = delete;
2195
2196 id get_native() const { return m_script; }
2197
2198private:
2199 id m_script{};
2200};
2201
2202class cocoa_wkwebview_engine : public engine_base {
2203public:
2204 cocoa_wkwebview_engine(bool debug, void *window)
2205 : m_debug{debug},
2206 m_window{static_cast<id>(window)},
2207 m_owns_window{!window} {
2208 auto app = get_shared_application();
2209 // See comments related to application lifecycle in create_app_delegate().
2210 if (!m_owns_window) {
2211 set_up_window();
2212 } else {
2213 // Only set the app delegate if it hasn't already been set.
2214 auto delegate = objc::msg_send<id>(app, "delegate"_sel);
2215 if (delegate) {
2216 set_up_window();
2217 } else {
2218 m_app_delegate = create_app_delegate();
2219 objc_setAssociatedObject(m_app_delegate, "webview", (id)this,
2220 OBJC_ASSOCIATION_ASSIGN);
2221 objc::msg_send<void>(app, "setDelegate:"_sel, m_app_delegate);
2222
2223 // Start the main run loop so that the app delegate gets the
2224 // NSApplicationDidFinishLaunchingNotification notification after the run
2225 // loop has started in order to perform further initialization.
2226 // We need to return from this constructor so this run loop is only
2227 // temporary.
2228 // Skip the main loop if this isn't the first instance of this class
2229 // because the launch event is only sent once. Instead, proceed to
2230 // create a window.
2231 if (get_and_set_is_first_instance()) {
2232 objc::msg_send<void>(app, "run"_sel);
2233 } else {
2234 set_up_window();
2235 }
2236 }
2237 }
2238 }
2239
2240 cocoa_wkwebview_engine(const cocoa_wkwebview_engine &) = delete;
2241 cocoa_wkwebview_engine &operator=(const cocoa_wkwebview_engine &) = delete;
2242 cocoa_wkwebview_engine(cocoa_wkwebview_engine &&) = delete;
2243 cocoa_wkwebview_engine &operator=(cocoa_wkwebview_engine &&) = delete;
2244
2245 virtual ~cocoa_wkwebview_engine() {
2246 objc::autoreleasepool arp;
2247 if (m_window) {
2248 if (m_webview) {
2249 if (auto ui_delegate =
2250 objc::msg_send<id>(m_webview, "UIDelegate"_sel)) {
2251 objc::msg_send<void>(m_webview, "setUIDelegate:"_sel, nullptr);
2252 objc::msg_send<void>(ui_delegate, "release"_sel);
2253 }
2254 if (m_webview == objc::msg_send<id>(m_window, "contentView"_sel)) {
2255 objc::msg_send<void>(m_window, "setContentView:"_sel, nullptr);
2256 }
2257 objc::msg_send<void>(m_webview, "release"_sel);
2258 m_webview = nullptr;
2259 }
2260 if (m_owns_window) {
2261 // Replace delegate to avoid callbacks and other bad things during
2262 // destruction.
2263 objc::msg_send<void>(m_window, "setDelegate:"_sel, nullptr);
2264 objc::msg_send<void>(m_window, "close"_sel);
2265 on_window_destroyed(true);
2266 }
2267 m_window = nullptr;
2268 }
2269 if (m_window_delegate) {
2270 objc::msg_send<void>(m_window_delegate, "release"_sel);
2271 m_window_delegate = nullptr;
2272 }
2273 if (m_app_delegate) {
2274 auto app = get_shared_application();
2275 objc::msg_send<void>(app, "setDelegate:"_sel, nullptr);
2276 // Make sure to release the delegate we created.
2277 objc::msg_send<void>(m_app_delegate, "release"_sel);
2278 m_app_delegate = nullptr;
2279 }
2280 if (m_owns_window) {
2281 // Needed for the window to close immediately.
2282 deplete_run_loop_event_queue();
2283 }
2284 // TODO: Figure out why m_manager is still alive after the autoreleasepool
2285 // has been drained.
2286 }
2287
2288protected:
2289 result<void *> window_impl() override {
2290 if (m_window) {
2291 return m_window;
2292 }
2293 return error_info{WEBVIEW_ERROR_INVALID_STATE};
2294 }
2295
2296 result<void *> widget_impl() override {
2297 if (m_webview) {
2298 return m_webview;
2299 }
2300 return error_info{WEBVIEW_ERROR_INVALID_STATE};
2301 }
2302
2303 result<void *> browser_controller_impl() override {
2304 if (m_webview) {
2305 return m_webview;
2306 }
2307 return error_info{WEBVIEW_ERROR_INVALID_STATE};
2308 }
2309
2310 noresult terminate_impl() override {
2311 stop_run_loop();
2312 return {};
2313 }
2314
2315 noresult run_impl() override {
2316 auto app = get_shared_application();
2317 objc::msg_send<void>(app, "run"_sel);
2318 return {};
2319 }
2320
2321 noresult dispatch_impl(std::function<void()> f) override {
2322 dispatch_async_f(dispatch_get_main_queue(), new dispatch_fn_t(f),
2323 (dispatch_function_t)([](void *arg) {
2324 auto f = static_cast<dispatch_fn_t *>(arg);
2325 (*f)();
2326 delete f;
2327 }));
2328 return {};
2329 }
2330
2331 noresult set_title_impl(const std::string &title) override {
2332 objc::autoreleasepool arp;
2333
2334 objc::msg_send<void>(m_window, "setTitle:"_sel,
2335 objc::msg_send<id>("NSString"_cls,
2336 "stringWithUTF8String:"_sel,
2337 title.c_str()));
2338
2339 return {};
2340 }
2341 noresult set_size_impl(int width, int height, webview_hint_t hints) override {
2342 objc::autoreleasepool arp;
2343
2344 auto style = static_cast<NSWindowStyleMask>(
2345 NSWindowStyleMaskTitled | NSWindowStyleMaskClosable |
2346 NSWindowStyleMaskMiniaturizable);
2347 if (hints != WEBVIEW_HINT_FIXED) {
2348 style =
2349 static_cast<NSWindowStyleMask>(style | NSWindowStyleMaskResizable);
2350 }
2351 objc::msg_send<void>(m_window, "setStyleMask:"_sel, style);
2352
2353 if (hints == WEBVIEW_HINT_MIN) {
2354 objc::msg_send<void>(m_window, "setContentMinSize:"_sel,
2355 CGSizeMake(width, height));
2356 } else if (hints == WEBVIEW_HINT_MAX) {
2357 objc::msg_send<void>(m_window, "setContentMaxSize:"_sel,
2358 CGSizeMake(width, height));
2359 } else {
2360 objc::msg_send<void>(m_window, "setFrame:display:animate:"_sel,
2361 CGRectMake(0, 0, width, height), YES, NO);
2362 }
2363 objc::msg_send<void>(m_window, "center"_sel);
2364
2365 return {};
2366 }
2367 noresult navigate_impl(const std::string &url) override {
2368 objc::autoreleasepool arp;
2369
2370 auto nsurl = objc::msg_send<id>(
2371 "NSURL"_cls, "URLWithString:"_sel,
2372 objc::msg_send<id>("NSString"_cls, "stringWithUTF8String:"_sel,
2373 url.c_str()));
2374
2375 objc::msg_send<void>(
2376 m_webview, "loadRequest:"_sel,
2377 objc::msg_send<id>("NSURLRequest"_cls, "requestWithURL:"_sel, nsurl));
2378
2379 return {};
2380 }
2381 noresult set_html_impl(const std::string &html) override {
2382 objc::autoreleasepool arp;
2383 objc::msg_send<void>(m_webview, "loadHTMLString:baseURL:"_sel,
2384 objc::msg_send<id>("NSString"_cls,
2385 "stringWithUTF8String:"_sel,
2386 html.c_str()),
2387 nullptr);
2388 return {};
2389 }
2390 noresult eval_impl(const std::string &js) override {
2391 objc::autoreleasepool arp;
2392 // URI is null before content has begun loading.
2393 auto nsurl = objc::msg_send<id>(m_webview, "URL"_sel);
2394 if (!nsurl) {
2395 return {};
2396 }
2397 objc::msg_send<void>(m_webview, "evaluateJavaScript:completionHandler:"_sel,
2398 objc::msg_send<id>("NSString"_cls,
2399 "stringWithUTF8String:"_sel,
2400 js.c_str()),
2401 nullptr);
2402 return {};
2403 }
2404
2405 user_script add_user_script_impl(const std::string &js) override {
2406 objc::autoreleasepool arp;
2407 auto wk_script = objc::msg_send<id>(
2408 objc::msg_send<id>("WKUserScript"_cls, "alloc"_sel),
2409 "initWithSource:injectionTime:forMainFrameOnly:"_sel,
2410 objc::msg_send<id>("NSString"_cls, "stringWithUTF8String:"_sel,
2411 js.c_str()),
2412 WKUserScriptInjectionTimeAtDocumentStart, YES);
2413 // Script is retained when added.
2414 objc::msg_send<void>(m_manager, "addUserScript:"_sel, wk_script);
2415 user_script script{js, std::unique_ptr<user_script::impl>{
2416 new user_script::impl{wk_script}}};
2417 objc::msg_send<void>(wk_script, "release"_sel);
2418 return script;
2419 }
2420
2421 void remove_all_user_scripts_impl(
2422 const std::list<user_script> & /*scripts*/) override {
2423 objc::autoreleasepool arp;
2424 // Removing scripts decreases the retain count of each script.
2425 objc::msg_send<id>(m_manager, "removeAllUserScripts"_sel);
2426 }
2427
2428 bool are_user_scripts_equal_impl(const user_script &first,
2429 const user_script &second) override {
2430 auto *wk_first = first.get_impl().get_native();
2431 auto *wk_second = second.get_impl().get_native();
2432 return wk_first == wk_second;
2433 }
2434
2435private:
2436 id create_app_delegate() {
2437 objc::autoreleasepool arp;
2438 constexpr auto class_name = "WebviewAppDelegate";
2439 // Avoid crash due to registering same class twice
2440 auto cls = objc_lookUpClass(class_name);
2441 if (!cls) {
2442 // Note: Avoid registering the class name "AppDelegate" as it is the
2443 // default name in projects created with Xcode, and using the same name
2444 // causes objc_registerClassPair to crash.
2445 cls = objc_allocateClassPair((Class) "NSResponder"_cls, class_name, 0);
2446 class_addProtocol(cls, objc_getProtocol("NSTouchBarProvider"));
2447 class_addMethod(cls,
2448 "applicationShouldTerminateAfterLastWindowClosed:"_sel,
2449 (IMP)(+[](id, SEL, id) -> BOOL { return NO; }), "c@:@");
2450 class_addMethod(cls, "applicationDidFinishLaunching:"_sel,
2451 (IMP)(+[](id self, SEL, id notification) {
2452 auto app =
2453 objc::msg_send<id>(notification, "object"_sel);
2454 auto w = get_associated_webview(self);
2455 w->on_application_did_finish_launching(self, app);
2456 }),
2457 "v@:@");
2458 objc_registerClassPair(cls);
2459 }
2460 return objc::msg_send<id>((id)cls, "new"_sel);
2461 }
2462 id create_script_message_handler() {
2463 objc::autoreleasepool arp;
2464 constexpr auto class_name = "WebviewWKScriptMessageHandler";
2465 // Avoid crash due to registering same class twice
2466 auto cls = objc_lookUpClass(class_name);
2467 if (!cls) {
2468 cls = objc_allocateClassPair((Class) "NSResponder"_cls, class_name, 0);
2469 class_addProtocol(cls, objc_getProtocol("WKScriptMessageHandler"));
2470 class_addMethod(
2471 cls, "userContentController:didReceiveScriptMessage:"_sel,
2472 (IMP)(+[](id self, SEL, id, id msg) {
2473 auto w = get_associated_webview(self);
2474 w->on_message(objc::msg_send<const char *>(
2475 objc::msg_send<id>(msg, "body"_sel), "UTF8String"_sel));
2476 }),
2477 "v@:@@");
2478 objc_registerClassPair(cls);
2479 }
2480 auto instance = objc::msg_send<id>((id)cls, "new"_sel);
2481 objc_setAssociatedObject(instance, "webview", (id)this,
2482 OBJC_ASSOCIATION_ASSIGN);
2483 return instance;
2484 }
2485 static id create_webkit_ui_delegate() {
2486 objc::autoreleasepool arp;
2487 constexpr auto class_name = "WebviewWKUIDelegate";
2488 // Avoid crash due to registering same class twice
2489 auto cls = objc_lookUpClass(class_name);
2490 if (!cls) {
2491 cls = objc_allocateClassPair((Class) "NSObject"_cls, class_name, 0);
2492 class_addProtocol(cls, objc_getProtocol("WKUIDelegate"));
2493 class_addMethod(
2494 cls,
2495 "webView:runOpenPanelWithParameters:initiatedByFrame:completionHandler:"_sel,
2496 (IMP)(+[](id, SEL, id, id parameters, id, id completion_handler) {
2497 auto allows_multiple_selection =
2498 objc::msg_send<BOOL>(parameters, "allowsMultipleSelection"_sel);
2499 auto allows_directories =
2500 objc::msg_send<BOOL>(parameters, "allowsDirectories"_sel);
2501
2502 // Show a panel for selecting files.
2503 auto panel = objc::msg_send<id>("NSOpenPanel"_cls, "openPanel"_sel);
2504 objc::msg_send<void>(panel, "setCanChooseFiles:"_sel, YES);
2505 objc::msg_send<void>(panel, "setCanChooseDirectories:"_sel,
2506 allows_directories);
2507 objc::msg_send<void>(panel, "setAllowsMultipleSelection:"_sel,
2508 allows_multiple_selection);
2509 auto modal_response =
2510 objc::msg_send<NSModalResponse>(panel, "runModal"_sel);
2511
2512 // Get the URLs for the selected files. If the modal was canceled
2513 // then we pass null to the completion handler to signify
2514 // cancellation.
2515 id urls = modal_response == NSModalResponseOK
2516 ? objc::msg_send<id>(panel, "URLs"_sel)
2517 : nullptr;
2518
2519 // Invoke the completion handler block.
2520 auto sig = objc::msg_send<id>(
2521 "NSMethodSignature"_cls, "signatureWithObjCTypes:"_sel, "v@?@");
2522 auto invocation = objc::msg_send<id>(
2523 "NSInvocation"_cls, "invocationWithMethodSignature:"_sel, sig);
2524 objc::msg_send<void>(invocation, "setTarget:"_sel,
2525 completion_handler);
2526 objc::msg_send<void>(invocation, "setArgument:atIndex:"_sel, &urls,
2527 1);
2528 objc::msg_send<void>(invocation, "invoke"_sel);
2529 }),
2530 "v@:@@@@");
2531 objc_registerClassPair(cls);
2532 }
2533 return objc::msg_send<id>((id)cls, "new"_sel);
2534 }
2535 static id create_window_delegate() {
2536 objc::autoreleasepool arp;
2537 constexpr auto class_name = "WebviewNSWindowDelegate";
2538 // Avoid crash due to registering same class twice
2539 auto cls = objc_lookUpClass(class_name);
2540 if (!cls) {
2541 cls = objc_allocateClassPair((Class) "NSObject"_cls, class_name, 0);
2542 class_addProtocol(cls, objc_getProtocol("NSWindowDelegate"));
2543 class_addMethod(cls, "windowWillClose:"_sel,
2544 (IMP)(+[](id self, SEL, id notification) {
2545 auto window =
2546 objc::msg_send<id>(notification, "object"_sel);
2547 auto w = get_associated_webview(self);
2548 w->on_window_will_close(self, window);
2549 }),
2550 "v@:@");
2551 objc_registerClassPair(cls);
2552 }
2553 return objc::msg_send<id>((id)cls, "new"_sel);
2554 }
2555 static id get_shared_application() {
2556 return objc::msg_send<id>("NSApplication"_cls, "sharedApplication"_sel);
2557 }
2558 static cocoa_wkwebview_engine *get_associated_webview(id object) {
2559 auto w =
2560 (cocoa_wkwebview_engine *)objc_getAssociatedObject(object, "webview");
2561 assert(w);
2562 return w;
2563 }
2564 static id get_main_bundle() noexcept {
2565 return objc::msg_send<id>("NSBundle"_cls, "mainBundle"_sel);
2566 }
2567 static bool is_app_bundled() noexcept {
2568 auto bundle = get_main_bundle();
2569 if (!bundle) {
2570 return false;
2571 }
2572 auto bundle_path = objc::msg_send<id>(bundle, "bundlePath"_sel);
2573 auto bundled =
2574 objc::msg_send<BOOL>(bundle_path, "hasSuffix:"_sel, ".app"_str);
2575 return !!bundled;
2576 }
2577 void on_application_did_finish_launching(id /*delegate*/, id app) {
2578 // See comments related to application lifecycle in create_app_delegate().
2579 if (m_owns_window) {
2580 // Stop the main run loop so that we can return
2581 // from the constructor.
2582 stop_run_loop();
2583 }
2584
2585 // Activate the app if it is not bundled.
2586 // Bundled apps launched from Finder are activated automatically but
2587 // otherwise not. Activating the app even when it has been launched from
2588 // Finder does not seem to be harmful but calling this function is rarely
2589 // needed as proper activation is normally taken care of for us.
2590 // Bundled apps have a default activation policy of
2591 // NSApplicationActivationPolicyRegular while non-bundled apps have a
2592 // default activation policy of NSApplicationActivationPolicyProhibited.
2593 if (!is_app_bundled()) {
2594 // "setActivationPolicy:" must be invoked before
2595 // "activateIgnoringOtherApps:" for activation to work.
2596 objc::msg_send<void>(app, "setActivationPolicy:"_sel,
2597 NSApplicationActivationPolicyRegular);
2598 // Activate the app regardless of other active apps.
2599 // This can be obtrusive so we only do it when necessary.
2600 objc::msg_send<void>(app, "activateIgnoringOtherApps:"_sel, YES);
2601 }
2602
2603 set_up_window();
2604 }
2605 void on_window_will_close(id /*delegate*/, id /*window*/) {
2606 // Widget destroyed along with window.
2607 m_webview = nullptr;
2608 m_window = nullptr;
2609 dispatch([this] { on_window_destroyed(); });
2610 }
2611 void set_up_window() {
2612 objc::autoreleasepool arp;
2613
2614 // Main window
2615 if (m_owns_window) {
2616 m_window = objc::msg_send<id>("NSWindow"_cls, "alloc"_sel);
2617 auto style = NSWindowStyleMaskTitled;
2618 m_window = objc::msg_send<id>(
2619 m_window, "initWithContentRect:styleMask:backing:defer:"_sel,
2620 CGRectMake(0, 0, 0, 0), style, NSBackingStoreBuffered, NO);
2621
2622 m_window_delegate = create_window_delegate();
2623 objc_setAssociatedObject(m_window_delegate, "webview", (id)this,
2624 OBJC_ASSOCIATION_ASSIGN);
2625 objc::msg_send<void>(m_window, "setDelegate:"_sel, m_window_delegate);
2626
2627 on_window_created();
2628 }
2629
2630 set_up_web_view();
2631
2632 objc::msg_send<void>(m_window, "setContentView:"_sel, m_webview);
2633
2634 if (m_owns_window) {
2635 objc::msg_send<void>(m_window, "makeKeyAndOrderFront:"_sel, nullptr);
2636 }
2637 }
2638 void set_up_web_view() {
2639 objc::autoreleasepool arp;
2640
2641 auto config = objc::autoreleased(
2642 objc::msg_send<id>("WKWebViewConfiguration"_cls, "new"_sel));
2643
2644 m_manager = objc::msg_send<id>(config, "userContentController"_sel);
2645 m_webview = objc::msg_send<id>("WKWebView"_cls, "alloc"_sel);
2646
2647 auto preferences = objc::msg_send<id>(config, "preferences"_sel);
2648 auto yes_value =
2649 objc::msg_send<id>("NSNumber"_cls, "numberWithBool:"_sel, YES);
2650
2651 if (m_debug) {
2652 // Equivalent Obj-C:
2653 // [[config preferences] setValue:@YES forKey:@"developerExtrasEnabled"];
2654 objc::msg_send<id>(preferences, "setValue:forKey:"_sel, yes_value,
2655 "developerExtrasEnabled"_str);
2656 }
2657
2658 // Equivalent Obj-C:
2659 // [[config preferences] setValue:@YES forKey:@"fullScreenEnabled"];
2660 objc::msg_send<id>(preferences, "setValue:forKey:"_sel, yes_value,
2661 "fullScreenEnabled"_str);
2662
2663 // Equivalent Obj-C:
2664 // [[config preferences] setValue:@YES forKey:@"javaScriptCanAccessClipboard"];
2665 objc::msg_send<id>(preferences, "setValue:forKey:"_sel, yes_value,
2666 "javaScriptCanAccessClipboard"_str);
2667
2668 // Equivalent Obj-C:
2669 // [[config preferences] setValue:@YES forKey:@"DOMPasteAllowed"];
2670 objc::msg_send<id>(preferences, "setValue:forKey:"_sel, yes_value,
2671 "DOMPasteAllowed"_str);
2672
2673 auto ui_delegate = create_webkit_ui_delegate();
2674 objc::msg_send<void>(m_webview, "initWithFrame:configuration:"_sel,
2675 CGRectMake(0, 0, 0, 0), config);
2676 objc_setAssociatedObject(ui_delegate, "webview", (id)this,
2677 OBJC_ASSOCIATION_ASSIGN);
2678 objc::msg_send<void>(m_webview, "setUIDelegate:"_sel, ui_delegate);
2679
2680 if (m_debug) {
2681 // Explicitly make WKWebView inspectable via Safari on OS versions that
2682 // disable the feature by default (macOS 13.3 and later) and support
2683 // enabling it. According to Apple, the behavior on older OS versions is
2684 // for content to always be inspectable in "debug builds".
2685 // Testing shows that this is true for macOS 12.6 but somehow not 10.15.
2686 // https://webkit.org/blog/13936/enabling-the-inspection-of-web-content-in-apps/
2687#if defined(__has_builtin)
2688#if __has_builtin(__builtin_available)
2689 if (__builtin_available(macOS 13.3, iOS 16.4, tvOS 16.4, *)) {
2690 objc::msg_send<void>(
2691 m_webview, "setInspectable:"_sel,
2692 objc::msg_send<id>("NSNumber"_cls, "numberWithBool:"_sel, YES));
2693 }
2694#else
2695#error __builtin_available not supported by compiler
2696#endif
2697#else
2698#error __has_builtin not supported by compiler
2699#endif
2700 }
2701
2702 auto script_message_handler =
2703 objc::autoreleased(create_script_message_handler());
2704 objc::msg_send<void>(m_manager, "addScriptMessageHandler:name:"_sel,
2705 script_message_handler, "__webview__"_str);
2706
2707 add_init_script("function(message) {\n\
2708 return window.webkit.messageHandlers.__webview__.postMessage(message);\n\
2709}");
2710 }
2711 void stop_run_loop() {
2712 objc::autoreleasepool arp;
2713 auto app = get_shared_application();
2714 // Request the run loop to stop. This doesn't immediately stop the loop.
2715 objc::msg_send<void>(app, "stop:"_sel, nullptr);
2716 // The run loop will stop after processing an NSEvent.
2717 // Event type: NSEventTypeApplicationDefined (macOS 10.12+),
2718 // NSApplicationDefined (macOS 10.0–10.12)
2719 int type = 15;
2720 auto event = objc::msg_send<id>(
2721 "NSEvent"_cls,
2722 "otherEventWithType:location:modifierFlags:timestamp:windowNumber:context:subtype:data1:data2:"_sel,
2723 type, CGPointMake(0, 0), 0, 0, 0, nullptr, 0, 0, 0);
2724 objc::msg_send<void>(app, "postEvent:atStart:"_sel, event, YES);
2725 }
2726 static bool get_and_set_is_first_instance() noexcept {
2727 static std::atomic_bool first{true};
2728 bool temp = first;
2729 if (temp) {
2730 first = false;
2731 }
2732 return temp;
2733 }
2734
2735 // Blocks while depleting the run loop of events.
2736 void deplete_run_loop_event_queue() {
2737 objc::autoreleasepool arp;
2738 auto app = get_shared_application();
2739 bool done{};
2740 dispatch([&] { done = true; });
2741 auto mask = NSUIntegerMax; // NSEventMaskAny
2742 // NSDefaultRunLoopMode
2743 auto mode = objc::msg_send<id>("NSString"_cls, "stringWithUTF8String:"_sel,
2744 "kCFRunLoopDefaultMode");
2745 while (!done) {
2746 objc::autoreleasepool arp2;
2747 auto event = objc::msg_send<id>(
2748 app, "nextEventMatchingMask:untilDate:inMode:dequeue:"_sel, mask,
2749 nullptr, mode, YES);
2750 if (event) {
2751 objc::msg_send<void>(app, "sendEvent:"_sel, event);
2752 }
2753 }
2754 }
2755
2756 bool m_debug{};
2757 id m_app_delegate{};
2758 id m_window_delegate{};
2759 id m_window{};
2760 id m_webview{};
2761 id m_manager{};
2762 bool m_owns_window{};
2763};
2764
2765} // namespace detail
2766
2767using browser_engine = detail::cocoa_wkwebview_engine;
2768
2769} // namespace webview
2770
2771#elif defined(WEBVIEW_EDGE)
2772
2773//
2774// ====================================================================
2775//
2776// This implementation uses Win32 API to create a native window. It
2777// uses Edge/Chromium webview2 backend as a browser engine.
2778//
2779// ====================================================================
2780//
2781
2782#define WIN32_LEAN_AND_MEAN
2783#include <shlobj.h>
2784#include <shlwapi.h>
2785#include <stdlib.h>
2786#include <windows.h>
2787
2788#include "WebView2.h"
2789
2790#ifdef _MSC_VER
2791#pragma comment(lib, "advapi32.lib")
2792#pragma comment(lib, "ole32.lib")
2793#pragma comment(lib, "shell32.lib")
2794#pragma comment(lib, "shlwapi.lib")
2795#pragma comment(lib, "user32.lib")
2796#pragma comment(lib, "version.lib")
2797#endif
2798
2799namespace webview {
2800namespace detail {
2801
2802using msg_cb_t = std::function<void(const std::string)>;
2803
2804// Parses a version string with 1-4 integral components, e.g. "1.2.3.4".
2805// Missing or invalid components default to 0, and excess components are ignored.
2806template <typename T>
2807std::array<unsigned int, 4>
2808parse_version(const std::basic_string<T> &version) noexcept {
2809 auto parse_component = [](auto sb, auto se) -> unsigned int {
2810 try {
2811 auto n = std::stol(std::basic_string<T>(sb, se));
2812 return n < 0 ? 0 : n;
2813 } catch (std::exception &) {
2814 return 0;
2815 }
2816 };
2817 auto end = version.end();
2818 auto sb = version.begin(); // subrange begin
2819 auto se = sb; // subrange end
2820 unsigned int ci = 0; // component index
2821 std::array<unsigned int, 4> components{};
2822 while (sb != end && se != end && ci < components.size()) {
2823 if (*se == static_cast<T>('.')) {
2824 components[ci++] = parse_component(sb, se);
2825 sb = ++se;
2826 continue;
2827 }
2828 ++se;
2829 }
2830 if (sb < se && ci < components.size()) {
2831 components[ci] = parse_component(sb, se);
2832 }
2833 return components;
2834}
2835
2836template <typename T, std::size_t Length>
2837auto parse_version(const T (&version)[Length]) noexcept {
2838 return parse_version(std::basic_string<T>(version, Length));
2839}
2840
2841inline std::wstring
2842get_file_version_string(const std::wstring &file_path) noexcept {
2843 DWORD dummy_handle; // Unused
2844 DWORD info_buffer_length =
2845 GetFileVersionInfoSizeW(file_path.c_str(), &dummy_handle);
2846 if (info_buffer_length == 0) {
2847 return std::wstring();
2848 }
2849 std::vector<char> info_buffer;
2850 info_buffer.reserve(info_buffer_length);
2851 if (!GetFileVersionInfoW(file_path.c_str(), 0, info_buffer_length,
2852 info_buffer.data())) {
2853 return std::wstring();
2854 }
2855 auto sub_block = L"\\StringFileInfo\\040904B0\\ProductVersion";
2856 LPWSTR version = nullptr;
2857 unsigned int version_length = 0;
2858 if (!VerQueryValueW(info_buffer.data(), sub_block,
2859 reinterpret_cast<LPVOID *>(&version), &version_length)) {
2860 return std::wstring();
2861 }
2862 if (!version || version_length == 0) {
2863 return std::wstring();
2864 }
2865 return std::wstring(version, version_length);
2866}
2867
2875class com_init_wrapper {
2876public:
2877 com_init_wrapper() = default;
2878
2879 com_init_wrapper(DWORD dwCoInit) {
2880 // We can safely continue as long as COM was either successfully
2881 // initialized or already initialized.
2882 // RPC_E_CHANGED_MODE means that CoInitializeEx was already called with
2883 // a different concurrency model.
2884 switch (CoInitializeEx(nullptr, dwCoInit)) {
2885 case S_OK:
2886 case S_FALSE:
2887 m_initialized = true;
2888 break;
2889 case RPC_E_CHANGED_MODE:
2890 throw exception{
2892 "CoInitializeEx already called with a different concurrency model"};
2893 default:
2894 throw exception{WEBVIEW_ERROR_UNSPECIFIED,
2895 "Unexpected result from CoInitializeEx"};
2896 }
2897 }
2898
2899 ~com_init_wrapper() {
2900 if (m_initialized) {
2901 CoUninitialize();
2902 m_initialized = false;
2903 }
2904 }
2905
2906 com_init_wrapper(const com_init_wrapper &other) = delete;
2907 com_init_wrapper &operator=(const com_init_wrapper &other) = delete;
2908 com_init_wrapper(com_init_wrapper &&other) { *this = std::move(other); }
2909
2910 com_init_wrapper &operator=(com_init_wrapper &&other) {
2911 if (this == &other) {
2912 return *this;
2913 }
2914 m_initialized = std::exchange(other.m_initialized, false);
2915 return *this;
2916 }
2917
2918private:
2919 bool m_initialized = false;
2920};
2921
2922namespace ntdll_symbols {
2923using RtlGetVersion_t =
2924 unsigned int /*NTSTATUS*/ (WINAPI *)(RTL_OSVERSIONINFOW *);
2925
2926constexpr auto RtlGetVersion = library_symbol<RtlGetVersion_t>("RtlGetVersion");
2927} // namespace ntdll_symbols
2928
2929namespace user32_symbols {
2930using DPI_AWARENESS_CONTEXT = HANDLE;
2931using SetProcessDpiAwarenessContext_t = BOOL(WINAPI *)(DPI_AWARENESS_CONTEXT);
2932using SetProcessDPIAware_t = BOOL(WINAPI *)();
2933using GetDpiForWindow_t = UINT(WINAPI *)(HWND);
2934using EnableNonClientDpiScaling_t = BOOL(WINAPI *)(HWND);
2935using AdjustWindowRectExForDpi_t = BOOL(WINAPI *)(LPRECT, DWORD, BOOL, DWORD,
2936 UINT);
2937using GetWindowDpiAwarenessContext_t = DPI_AWARENESS_CONTEXT(WINAPI *)(HWND);
2938using AreDpiAwarenessContextsEqual_t = BOOL(WINAPI *)(DPI_AWARENESS_CONTEXT,
2939 DPI_AWARENESS_CONTEXT);
2940
2941// Use intptr_t as the underlying type because we need to
2942// reinterpret_cast<DPI_AWARENESS_CONTEXT> which is a pointer.
2943// Available since Windows 10, version 1607
2944enum class dpi_awareness : intptr_t {
2945 per_monitor_v2_aware = -4, // Available since Windows 10, version 1703
2946 per_monitor_aware = -3
2947};
2948
2949constexpr auto SetProcessDpiAwarenessContext =
2950 library_symbol<SetProcessDpiAwarenessContext_t>(
2951 "SetProcessDpiAwarenessContext");
2952constexpr auto SetProcessDPIAware =
2953 library_symbol<SetProcessDPIAware_t>("SetProcessDPIAware");
2954constexpr auto GetDpiForWindow =
2955 library_symbol<GetDpiForWindow_t>("GetDpiForWindow");
2956constexpr auto EnableNonClientDpiScaling =
2957 library_symbol<EnableNonClientDpiScaling_t>("EnableNonClientDpiScaling");
2958constexpr auto AdjustWindowRectExForDpi =
2959 library_symbol<AdjustWindowRectExForDpi_t>("AdjustWindowRectExForDpi");
2960constexpr auto GetWindowDpiAwarenessContext =
2961 library_symbol<GetWindowDpiAwarenessContext_t>(
2962 "GetWindowDpiAwarenessContext");
2963constexpr auto AreDpiAwarenessContextsEqual =
2964 library_symbol<AreDpiAwarenessContextsEqual_t>(
2965 "AreDpiAwarenessContextsEqual");
2966} // namespace user32_symbols
2967
2968namespace dwmapi_symbols {
2969typedef enum {
2970 // This undocumented value is used instead of DWMWA_USE_IMMERSIVE_DARK_MODE
2971 // on Windows 10 older than build 19041 (2004/20H1).
2972 DWMWA_USE_IMMERSIVE_DARK_MODE_BEFORE_V10_0_19041 = 19,
2973 // Documented as being supported since Windows 11 build 22000 (21H2) but it
2974 // works since Windows 10 build 19041 (2004/20H1).
2975 DWMWA_USE_IMMERSIVE_DARK_MODE = 20
2976} DWMWINDOWATTRIBUTE;
2977using DwmSetWindowAttribute_t = HRESULT(WINAPI *)(HWND, DWORD, LPCVOID, DWORD);
2978
2979constexpr auto DwmSetWindowAttribute =
2980 library_symbol<DwmSetWindowAttribute_t>("DwmSetWindowAttribute");
2981} // namespace dwmapi_symbols
2982
2983namespace shcore_symbols {
2984typedef enum { PROCESS_PER_MONITOR_DPI_AWARE = 2 } PROCESS_DPI_AWARENESS;
2985using SetProcessDpiAwareness_t = HRESULT(WINAPI *)(PROCESS_DPI_AWARENESS);
2986
2987constexpr auto SetProcessDpiAwareness =
2988 library_symbol<SetProcessDpiAwareness_t>("SetProcessDpiAwareness");
2989} // namespace shcore_symbols
2990
2991class reg_key {
2992public:
2993 explicit reg_key(HKEY root_key, const wchar_t *sub_key, DWORD options,
2994 REGSAM sam_desired) {
2995 HKEY handle;
2996 auto status =
2997 RegOpenKeyExW(root_key, sub_key, options, sam_desired, &handle);
2998 if (status == ERROR_SUCCESS) {
2999 m_handle = handle;
3000 }
3001 }
3002
3003 explicit reg_key(HKEY root_key, const std::wstring &sub_key, DWORD options,
3004 REGSAM sam_desired)
3005 : reg_key(root_key, sub_key.c_str(), options, sam_desired) {}
3006
3007 virtual ~reg_key() {
3008 if (m_handle) {
3009 RegCloseKey(m_handle);
3010 m_handle = nullptr;
3011 }
3012 }
3013
3014 reg_key(const reg_key &other) = delete;
3015 reg_key &operator=(const reg_key &other) = delete;
3016 reg_key(reg_key &&other) = delete;
3017 reg_key &operator=(reg_key &&other) = delete;
3018
3019 bool is_open() const { return !!m_handle; }
3020 bool get_handle() const { return m_handle; }
3021
3022 template <typename Container>
3023 void query_bytes(const wchar_t *name, Container &result) const {
3024 DWORD buf_length = 0;
3025 // Get the size of the data in bytes.
3026 auto status = RegQueryValueExW(m_handle, name, nullptr, nullptr, nullptr,
3027 &buf_length);
3028 if (status != ERROR_SUCCESS && status != ERROR_MORE_DATA) {
3029 result.resize(0);
3030 return;
3031 }
3032 // Read the data.
3033 result.resize(buf_length / sizeof(typename Container::value_type));
3034 auto *buf = reinterpret_cast<LPBYTE>(&result[0]);
3035 status =
3036 RegQueryValueExW(m_handle, name, nullptr, nullptr, buf, &buf_length);
3037 if (status != ERROR_SUCCESS) {
3038 result.resize(0);
3039 return;
3040 }
3041 }
3042
3043 std::wstring query_string(const wchar_t *name) const {
3044 std::wstring result;
3045 query_bytes(name, result);
3046 // Remove trailing null-characters.
3047 for (std::size_t length = result.size(); length > 0; --length) {
3048 if (result[length - 1] != 0) {
3049 result.resize(length);
3050 break;
3051 }
3052 }
3053 return result;
3054 }
3055
3056 unsigned int query_uint(const wchar_t *name,
3057 unsigned int default_value) const {
3058 std::vector<char> data;
3059 query_bytes(name, data);
3060 if (data.size() < sizeof(DWORD)) {
3061 return default_value;
3062 }
3063 return static_cast<unsigned int>(*reinterpret_cast<DWORD *>(data.data()));
3064 }
3065
3066private:
3067 HKEY m_handle = nullptr;
3068};
3069
3070// Compare the specified version against the OS version.
3071// Returns less than 0 if the OS version is less.
3072// Returns 0 if the versions are equal.
3073// Returns greater than 0 if the specified version is greater.
3074inline int compare_os_version(unsigned int major, unsigned int minor,
3075 unsigned int build) {
3076 // Use RtlGetVersion both to bypass potential issues related to
3077 // VerifyVersionInfo and manifests, and because both GetVersion and
3078 // GetVersionEx are deprecated.
3079 auto ntdll = native_library(L"ntdll.dll");
3080 if (auto fn = ntdll.get(ntdll_symbols::RtlGetVersion)) {
3081 RTL_OSVERSIONINFOW vi{};
3082 vi.dwOSVersionInfoSize = sizeof(vi);
3083 if (fn(&vi) != 0) {
3084 return false;
3085 }
3086 if (vi.dwMajorVersion == major) {
3087 if (vi.dwMinorVersion == minor) {
3088 return static_cast<int>(vi.dwBuildNumber) - static_cast<int>(build);
3089 }
3090 return static_cast<int>(vi.dwMinorVersion) - static_cast<int>(minor);
3091 }
3092 return static_cast<int>(vi.dwMajorVersion) - static_cast<int>(major);
3093 }
3094 return false;
3095}
3096
3097inline bool is_per_monitor_v2_awareness_available() {
3098 // Windows 10, version 1703
3099 return compare_os_version(10, 0, 15063) >= 0;
3100}
3101
3102inline bool enable_dpi_awareness() {
3103 auto user32 = native_library(L"user32.dll");
3104 if (auto fn = user32.get(user32_symbols::SetProcessDpiAwarenessContext)) {
3105 auto dpi_awareness =
3106 reinterpret_cast<user32_symbols::DPI_AWARENESS_CONTEXT>(
3107 is_per_monitor_v2_awareness_available()
3108 ? user32_symbols::dpi_awareness::per_monitor_v2_aware
3109 : user32_symbols::dpi_awareness::per_monitor_aware);
3110 if (fn(dpi_awareness)) {
3111 return true;
3112 }
3113 return GetLastError() == ERROR_ACCESS_DENIED;
3114 }
3115 if (auto shcore = native_library(L"shcore.dll")) {
3116 if (auto fn = shcore.get(shcore_symbols::SetProcessDpiAwareness)) {
3117 auto result = fn(shcore_symbols::PROCESS_PER_MONITOR_DPI_AWARE);
3118 return result == S_OK || result == E_ACCESSDENIED;
3119 }
3120 }
3121 if (auto fn = user32.get(user32_symbols::SetProcessDPIAware)) {
3122 return !!fn();
3123 }
3124 return true;
3125}
3126
3127inline bool enable_non_client_dpi_scaling_if_needed(HWND window) {
3128 auto user32 = native_library(L"user32.dll");
3129 auto get_ctx_fn = user32.get(user32_symbols::GetWindowDpiAwarenessContext);
3130 if (!get_ctx_fn) {
3131 return true;
3132 }
3133 auto awareness = get_ctx_fn(window);
3134 if (!awareness) {
3135 return false;
3136 }
3137 auto ctx_equal_fn = user32.get(user32_symbols::AreDpiAwarenessContextsEqual);
3138 if (!ctx_equal_fn) {
3139 return true;
3140 }
3141 // EnableNonClientDpiScaling is only needed with per monitor v1 awareness.
3142 auto per_monitor = reinterpret_cast<user32_symbols::DPI_AWARENESS_CONTEXT>(
3143 user32_symbols::dpi_awareness::per_monitor_aware);
3144 if (!ctx_equal_fn(awareness, per_monitor)) {
3145 return true;
3146 }
3147 auto enable_fn = user32.get(user32_symbols::EnableNonClientDpiScaling);
3148 if (!enable_fn) {
3149 return true;
3150 }
3151 return !!enable_fn(window);
3152}
3153
3154constexpr int get_default_window_dpi() {
3155 constexpr const int default_dpi = 96; // USER_DEFAULT_SCREEN_DPI
3156 return default_dpi;
3157}
3158
3159inline int get_window_dpi(HWND window) {
3160 auto user32 = native_library(L"user32.dll");
3161 if (auto fn = user32.get(user32_symbols::GetDpiForWindow)) {
3162 auto dpi = static_cast<int>(fn(window));
3163 return dpi;
3164 }
3165 return get_default_window_dpi();
3166}
3167
3168constexpr int scale_value_for_dpi(int value, int from_dpi, int to_dpi) {
3169 return (value * to_dpi) / from_dpi;
3170}
3171
3172constexpr SIZE scale_size(int width, int height, int from_dpi, int to_dpi) {
3173 auto scaled_width = scale_value_for_dpi(width, from_dpi, to_dpi);
3174 auto scaled_height = scale_value_for_dpi(height, from_dpi, to_dpi);
3175 return {scaled_width, scaled_height};
3176}
3177
3178inline SIZE make_window_frame_size(HWND window, int width, int height,
3179 int dpi) {
3180 auto style = GetWindowLong(window, GWL_STYLE);
3181 RECT r{0, 0, width, height};
3182 auto user32 = native_library(L"user32.dll");
3183 if (auto fn = user32.get(user32_symbols::AdjustWindowRectExForDpi)) {
3184 fn(&r, style, FALSE, 0, static_cast<UINT>(dpi));
3185 } else {
3186 AdjustWindowRect(&r, style, 0);
3187 }
3188 auto frame_width = r.right - r.left;
3189 auto frame_height = r.bottom - r.top;
3190 return {frame_width, frame_height};
3191}
3192
3193inline bool is_dark_theme_enabled() {
3194 constexpr auto *sub_key =
3195 L"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Themes\\Personalize";
3196 reg_key key(HKEY_CURRENT_USER, sub_key, 0, KEY_READ);
3197 if (!key.is_open()) {
3198 // Default is light theme
3199 return false;
3200 }
3201 return key.query_uint(L"AppsUseLightTheme", 1) == 0;
3202}
3203
3204inline void apply_window_theme(HWND window) {
3205 auto dark_theme_enabled = is_dark_theme_enabled();
3206
3207 // Use "immersive dark mode" on systems that support it.
3208 // Changes the color of the window's title bar (light or dark).
3209 BOOL use_dark_mode{dark_theme_enabled ? TRUE : FALSE};
3210 static native_library dwmapi{L"dwmapi.dll"};
3211 if (auto fn = dwmapi.get(dwmapi_symbols::DwmSetWindowAttribute)) {
3212 // Try the modern, documented attribute before the older, undocumented one.
3213 if (fn(window, dwmapi_symbols::DWMWA_USE_IMMERSIVE_DARK_MODE,
3214 &use_dark_mode, sizeof(use_dark_mode)) != S_OK) {
3215 fn(window,
3216 dwmapi_symbols::DWMWA_USE_IMMERSIVE_DARK_MODE_BEFORE_V10_0_19041,
3217 &use_dark_mode, sizeof(use_dark_mode));
3218 }
3219 }
3220}
3221
3222// Enable built-in WebView2Loader implementation by default.
3223#ifndef WEBVIEW_MSWEBVIEW2_BUILTIN_IMPL
3224#define WEBVIEW_MSWEBVIEW2_BUILTIN_IMPL 1
3225#endif
3226
3227// Link WebView2Loader.dll explicitly by default only if the built-in
3228// implementation is enabled.
3229#ifndef WEBVIEW_MSWEBVIEW2_EXPLICIT_LINK
3230#define WEBVIEW_MSWEBVIEW2_EXPLICIT_LINK WEBVIEW_MSWEBVIEW2_BUILTIN_IMPL
3231#endif
3232
3233// Explicit linking of WebView2Loader.dll should be used along with
3234// the built-in implementation.
3235#if WEBVIEW_MSWEBVIEW2_BUILTIN_IMPL == 1 && \
3236 WEBVIEW_MSWEBVIEW2_EXPLICIT_LINK != 1
3237#undef WEBVIEW_MSWEBVIEW2_EXPLICIT_LINK
3238#error Please set WEBVIEW_MSWEBVIEW2_EXPLICIT_LINK=1.
3239#endif
3240
3241#if WEBVIEW_MSWEBVIEW2_BUILTIN_IMPL == 1
3242// Gets the last component of a Windows native file path.
3243// For example, if the path is "C:\a\b" then the result is "b".
3244template <typename T>
3245std::basic_string<T>
3246get_last_native_path_component(const std::basic_string<T> &path) {
3247 auto pos = path.find_last_of(static_cast<T>('\\'));
3248 if (pos != std::basic_string<T>::npos) {
3249 return path.substr(pos + 1);
3250 }
3251 return std::basic_string<T>();
3252}
3253#endif /* WEBVIEW_MSWEBVIEW2_BUILTIN_IMPL */
3254
3255template <typename T> struct cast_info_t {
3256 using type = T;
3257 IID iid;
3258};
3259
3260namespace mswebview2 {
3261static constexpr IID
3262 IID_ICoreWebView2CreateCoreWebView2ControllerCompletedHandler{
3263 0x6C4819F3,
3264 0xC9B7,
3265 0x4260,
3266 {0x81, 0x27, 0xC9, 0xF5, 0xBD, 0xE7, 0xF6, 0x8C}};
3267static constexpr IID
3268 IID_ICoreWebView2CreateCoreWebView2EnvironmentCompletedHandler{
3269 0x4E8A3389,
3270 0xC9D8,
3271 0x4BD2,
3272 {0xB6, 0xB5, 0x12, 0x4F, 0xEE, 0x6C, 0xC1, 0x4D}};
3273static constexpr IID IID_ICoreWebView2PermissionRequestedEventHandler{
3274 0x15E1C6A3,
3275 0xC72A,
3276 0x4DF3,
3277 {0x91, 0xD7, 0xD0, 0x97, 0xFB, 0xEC, 0x6B, 0xFD}};
3278static constexpr IID IID_ICoreWebView2WebMessageReceivedEventHandler{
3279 0x57213F19,
3280 0x00E6,
3281 0x49FA,
3282 {0x8E, 0x07, 0x89, 0x8E, 0xA0, 0x1E, 0xCB, 0xD2}};
3283static constexpr IID
3284 IID_ICoreWebView2AddScriptToExecuteOnDocumentCreatedCompletedHandler{
3285 0xB99369F3,
3286 0x9B11,
3287 0x47B5,
3288 {0xBC, 0x6F, 0x8E, 0x78, 0x95, 0xFC, 0xEA, 0x17}};
3289
3290#if WEBVIEW_MSWEBVIEW2_BUILTIN_IMPL == 1
3291enum class webview2_runtime_type { installed = 0, embedded = 1 };
3292
3293namespace webview2_symbols {
3294using CreateWebViewEnvironmentWithOptionsInternal_t =
3295 HRESULT(STDMETHODCALLTYPE *)(
3296 bool, webview2_runtime_type, PCWSTR, IUnknown *,
3297 ICoreWebView2CreateCoreWebView2EnvironmentCompletedHandler *);
3298using DllCanUnloadNow_t = HRESULT(STDMETHODCALLTYPE *)();
3299
3300static constexpr auto CreateWebViewEnvironmentWithOptionsInternal =
3301 library_symbol<CreateWebViewEnvironmentWithOptionsInternal_t>(
3302 "CreateWebViewEnvironmentWithOptionsInternal");
3303static constexpr auto DllCanUnloadNow =
3304 library_symbol<DllCanUnloadNow_t>("DllCanUnloadNow");
3305} // namespace webview2_symbols
3306#endif /* WEBVIEW_MSWEBVIEW2_BUILTIN_IMPL */
3307
3308#if WEBVIEW_MSWEBVIEW2_EXPLICIT_LINK == 1
3309namespace webview2_symbols {
3310using CreateCoreWebView2EnvironmentWithOptions_t = HRESULT(STDMETHODCALLTYPE *)(
3311 PCWSTR, PCWSTR, ICoreWebView2EnvironmentOptions *,
3312 ICoreWebView2CreateCoreWebView2EnvironmentCompletedHandler *);
3313using GetAvailableCoreWebView2BrowserVersionString_t =
3314 HRESULT(STDMETHODCALLTYPE *)(PCWSTR, LPWSTR *);
3315
3316static constexpr auto CreateCoreWebView2EnvironmentWithOptions =
3317 library_symbol<CreateCoreWebView2EnvironmentWithOptions_t>(
3318 "CreateCoreWebView2EnvironmentWithOptions");
3319static constexpr auto GetAvailableCoreWebView2BrowserVersionString =
3320 library_symbol<GetAvailableCoreWebView2BrowserVersionString_t>(
3321 "GetAvailableCoreWebView2BrowserVersionString");
3322} // namespace webview2_symbols
3323#endif /* WEBVIEW_MSWEBVIEW2_EXPLICIT_LINK */
3324
3325class loader {
3326public:
3327 HRESULT create_environment_with_options(
3328 PCWSTR browser_dir, PCWSTR user_data_dir,
3329 ICoreWebView2EnvironmentOptions *env_options,
3330 ICoreWebView2CreateCoreWebView2EnvironmentCompletedHandler
3331 *created_handler) const {
3332#if WEBVIEW_MSWEBVIEW2_EXPLICIT_LINK == 1
3333 if (m_lib.is_loaded()) {
3334 if (auto fn = m_lib.get(
3335 webview2_symbols::CreateCoreWebView2EnvironmentWithOptions)) {
3336 return fn(browser_dir, user_data_dir, env_options, created_handler);
3337 }
3338 }
3339#if WEBVIEW_MSWEBVIEW2_BUILTIN_IMPL == 1
3340 return create_environment_with_options_impl(browser_dir, user_data_dir,
3341 env_options, created_handler);
3342#else
3343 return S_FALSE;
3344#endif
3345#else
3346 return ::CreateCoreWebView2EnvironmentWithOptions(
3347 browser_dir, user_data_dir, env_options, created_handler);
3348#endif /* WEBVIEW_MSWEBVIEW2_EXPLICIT_LINK */
3349 }
3350
3351 HRESULT
3352 get_available_browser_version_string(PCWSTR browser_dir,
3353 LPWSTR *version) const {
3354#if WEBVIEW_MSWEBVIEW2_EXPLICIT_LINK == 1
3355 if (m_lib.is_loaded()) {
3356 if (auto fn = m_lib.get(
3357 webview2_symbols::GetAvailableCoreWebView2BrowserVersionString)) {
3358 return fn(browser_dir, version);
3359 }
3360 }
3361#if WEBVIEW_MSWEBVIEW2_BUILTIN_IMPL == 1
3362 return get_available_browser_version_string_impl(browser_dir, version);
3363#else
3364 return S_FALSE;
3365#endif
3366#else
3367 return ::GetAvailableCoreWebView2BrowserVersionString(browser_dir, version);
3368#endif /* WEBVIEW_MSWEBVIEW2_EXPLICIT_LINK */
3369 }
3370
3371private:
3372#if WEBVIEW_MSWEBVIEW2_BUILTIN_IMPL == 1
3373 struct client_info_t {
3374 bool found = false;
3375 std::wstring dll_path;
3376 std::wstring version;
3377 webview2_runtime_type runtime_type;
3378 };
3379
3380 HRESULT create_environment_with_options_impl(
3381 PCWSTR browser_dir, PCWSTR user_data_dir,
3382 ICoreWebView2EnvironmentOptions *env_options,
3383 ICoreWebView2CreateCoreWebView2EnvironmentCompletedHandler
3384 *created_handler) const {
3385 auto found_client = find_available_client(browser_dir);
3386 if (!found_client.found) {
3387 return -1;
3388 }
3389 auto client_dll = native_library(found_client.dll_path.c_str());
3390 if (auto fn = client_dll.get(
3391 webview2_symbols::CreateWebViewEnvironmentWithOptionsInternal)) {
3392 return fn(true, found_client.runtime_type, user_data_dir, env_options,
3393 created_handler);
3394 }
3395 if (auto fn = client_dll.get(webview2_symbols::DllCanUnloadNow)) {
3396 if (!fn()) {
3397 client_dll.detach();
3398 }
3399 }
3400 return ERROR_SUCCESS;
3401 }
3402
3403 HRESULT
3404 get_available_browser_version_string_impl(PCWSTR browser_dir,
3405 LPWSTR *version) const {
3406 if (!version) {
3407 return -1;
3408 }
3409 auto found_client = find_available_client(browser_dir);
3410 if (!found_client.found) {
3411 return -1;
3412 }
3413 auto info_length_bytes =
3414 found_client.version.size() * sizeof(found_client.version[0]);
3415 auto info = static_cast<LPWSTR>(CoTaskMemAlloc(info_length_bytes));
3416 if (!info) {
3417 return -1;
3418 }
3419 CopyMemory(info, found_client.version.c_str(), info_length_bytes);
3420 *version = info;
3421 return 0;
3422 }
3423
3424 client_info_t find_available_client(PCWSTR browser_dir) const {
3425 if (browser_dir) {
3426 return find_embedded_client(api_version, browser_dir);
3427 }
3428 auto found_client =
3429 find_installed_client(api_version, true, default_release_channel_guid);
3430 if (!found_client.found) {
3431 found_client = find_installed_client(api_version, false,
3432 default_release_channel_guid);
3433 }
3434 return found_client;
3435 }
3436
3437 std::wstring make_client_dll_path(const std::wstring &dir) const {
3438 auto dll_path = dir;
3439 if (!dll_path.empty()) {
3440 auto last_char = dir[dir.size() - 1];
3441 if (last_char != L'\\' && last_char != L'/') {
3442 dll_path += L'\\';
3443 }
3444 }
3445 dll_path += L"EBWebView\\";
3446#if defined(_M_X64) || defined(__x86_64__)
3447 dll_path += L"x64";
3448#elif defined(_M_IX86) || defined(__i386__)
3449 dll_path += L"x86";
3450#elif defined(_M_ARM64) || defined(__aarch64__)
3451 dll_path += L"arm64";
3452#else
3453#error WebView2 integration for this platform is not yet supported.
3454#endif
3455 dll_path += L"\\EmbeddedBrowserWebView.dll";
3456 return dll_path;
3457 }
3458
3459 client_info_t
3460 find_installed_client(unsigned int min_api_version, bool system,
3461 const std::wstring &release_channel) const {
3462 std::wstring sub_key = client_state_reg_sub_key;
3463 sub_key += release_channel;
3464 auto root_key = system ? HKEY_LOCAL_MACHINE : HKEY_CURRENT_USER;
3465 reg_key key(root_key, sub_key, 0, KEY_READ | KEY_WOW64_32KEY);
3466 if (!key.is_open()) {
3467 return {};
3468 }
3469 auto ebwebview_value = key.query_string(L"EBWebView");
3470
3471 auto client_version_string =
3472 get_last_native_path_component(ebwebview_value);
3473 auto client_version = parse_version(client_version_string);
3474 if (client_version[2] < min_api_version) {
3475 // Our API version is greater than the runtime API version.
3476 return {};
3477 }
3478
3479 auto client_dll_path = make_client_dll_path(ebwebview_value);
3480 return {true, client_dll_path, client_version_string,
3481 webview2_runtime_type::installed};
3482 }
3483
3484 client_info_t find_embedded_client(unsigned int min_api_version,
3485 const std::wstring &dir) const {
3486 auto client_dll_path = make_client_dll_path(dir);
3487
3488 auto client_version_string = get_file_version_string(client_dll_path);
3489 auto client_version = parse_version(client_version_string);
3490 if (client_version[2] < min_api_version) {
3491 // Our API version is greater than the runtime API version.
3492 return {};
3493 }
3494
3495 return {true, client_dll_path, client_version_string,
3496 webview2_runtime_type::embedded};
3497 }
3498
3499 // The minimum WebView2 API version we need regardless of the SDK release
3500 // actually used. The number comes from the SDK release version,
3501 // e.g. 1.0.1150.38. To be safe the SDK should have a number that is greater
3502 // than or equal to this number. The Edge browser webview client must
3503 // have a number greater than or equal to this number.
3504 static constexpr unsigned int api_version = 1150;
3505
3506 static constexpr auto client_state_reg_sub_key =
3507 L"SOFTWARE\\Microsoft\\EdgeUpdate\\ClientState\\";
3508
3509 // GUID for the stable release channel.
3510 static constexpr auto stable_release_guid =
3511 L"{F3017226-FE2A-4295-8BDF-00C3A9A7E4C5}";
3512
3513 static constexpr auto default_release_channel_guid = stable_release_guid;
3514#endif /* WEBVIEW_MSWEBVIEW2_BUILTIN_IMPL */
3515
3516#if WEBVIEW_MSWEBVIEW2_EXPLICIT_LINK == 1
3517 native_library m_lib{L"WebView2Loader.dll"};
3518#endif
3519};
3520
3521namespace cast_info {
3522static constexpr auto controller_completed =
3523 cast_info_t<ICoreWebView2CreateCoreWebView2ControllerCompletedHandler>{
3524 IID_ICoreWebView2CreateCoreWebView2ControllerCompletedHandler};
3525
3526static constexpr auto environment_completed =
3527 cast_info_t<ICoreWebView2CreateCoreWebView2EnvironmentCompletedHandler>{
3528 IID_ICoreWebView2CreateCoreWebView2EnvironmentCompletedHandler};
3529
3530static constexpr auto message_received =
3531 cast_info_t<ICoreWebView2WebMessageReceivedEventHandler>{
3532 IID_ICoreWebView2WebMessageReceivedEventHandler};
3533
3534static constexpr auto permission_requested =
3535 cast_info_t<ICoreWebView2PermissionRequestedEventHandler>{
3536 IID_ICoreWebView2PermissionRequestedEventHandler};
3537
3538static constexpr auto add_script_to_execute_on_document_created_completed =
3539 cast_info_t<
3540 ICoreWebView2AddScriptToExecuteOnDocumentCreatedCompletedHandler>{
3541 IID_ICoreWebView2AddScriptToExecuteOnDocumentCreatedCompletedHandler};
3542} // namespace cast_info
3543} // namespace mswebview2
3544
3545// Checks whether the specified IID equals the IID of the specified type and
3546// if so casts the "this" pointer to T and returns it. Returns nullptr on
3547// mismatching IIDs.
3548// If ppv is specified then the pointer will also be assigned to *ppv.
3549template <typename From, typename To>
3550To *cast_if_equal_iid(From *from, REFIID riid, const cast_info_t<To> &info,
3551 LPVOID *ppv = nullptr) noexcept {
3552 To *ptr = nullptr;
3553 if (IsEqualIID(riid, info.iid)) {
3554 ptr = static_cast<To *>(from);
3555 ptr->AddRef();
3556 }
3557 if (ppv) {
3558 *ppv = ptr;
3559 }
3560 return ptr;
3561}
3562
3563class webview2_com_handler
3564 : public ICoreWebView2CreateCoreWebView2EnvironmentCompletedHandler,
3565 public ICoreWebView2CreateCoreWebView2ControllerCompletedHandler,
3566 public ICoreWebView2WebMessageReceivedEventHandler,
3567 public ICoreWebView2PermissionRequestedEventHandler {
3568 using webview2_com_handler_cb_t =
3569 std::function<void(ICoreWebView2Controller *, ICoreWebView2 *webview)>;
3570
3571public:
3572 webview2_com_handler(HWND hwnd, msg_cb_t msgCb, webview2_com_handler_cb_t cb)
3573 : m_window(hwnd), m_msgCb(msgCb), m_cb(cb) {}
3574
3575 virtual ~webview2_com_handler() = default;
3576 webview2_com_handler(const webview2_com_handler &other) = delete;
3577 webview2_com_handler &operator=(const webview2_com_handler &other) = delete;
3578 webview2_com_handler(webview2_com_handler &&other) = delete;
3579 webview2_com_handler &operator=(webview2_com_handler &&other) = delete;
3580
3581 ULONG STDMETHODCALLTYPE AddRef() { return ++m_ref_count; }
3582 ULONG STDMETHODCALLTYPE Release() {
3583 if (m_ref_count > 1) {
3584 return --m_ref_count;
3585 }
3586 delete this;
3587 return 0;
3588 }
3589 HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, LPVOID *ppv) {
3590 using namespace mswebview2::cast_info;
3591
3592 if (!ppv) {
3593 return E_POINTER;
3594 }
3595
3596 // All of the COM interfaces we implement should be added here regardless
3597 // of whether they are required.
3598 // This is just to be on the safe side in case the WebView2 Runtime ever
3599 // requests a pointer to an interface we implement.
3600 // The WebView2 Runtime must at the very least be able to get a pointer to
3601 // ICoreWebView2CreateCoreWebView2EnvironmentCompletedHandler when we use
3602 // our custom WebView2 loader implementation, and observations have shown
3603 // that it is the only interface requested in this case. None have been
3604 // observed to be requested when using the official WebView2 loader.
3605
3606 if (cast_if_equal_iid(this, riid, controller_completed, ppv) ||
3607 cast_if_equal_iid(this, riid, environment_completed, ppv) ||
3608 cast_if_equal_iid(this, riid, message_received, ppv) ||
3609 cast_if_equal_iid(this, riid, permission_requested, ppv)) {
3610 return S_OK;
3611 }
3612
3613 return E_NOINTERFACE;
3614 }
3615 HRESULT STDMETHODCALLTYPE Invoke(HRESULT res, ICoreWebView2Environment *env) {
3616 if (SUCCEEDED(res)) {
3617 res = env->CreateCoreWebView2Controller(m_window, this);
3618 if (SUCCEEDED(res)) {
3619 return S_OK;
3620 }
3621 }
3622 try_create_environment();
3623 return S_OK;
3624 }
3625 HRESULT STDMETHODCALLTYPE Invoke(HRESULT res,
3626 ICoreWebView2Controller *controller) {
3627 if (FAILED(res)) {
3628 // See try_create_environment() regarding
3629 // HRESULT_FROM_WIN32(ERROR_INVALID_STATE).
3630 // The result is E_ABORT if the parent window has been destroyed already.
3631 switch (res) {
3632 case HRESULT_FROM_WIN32(ERROR_INVALID_STATE):
3633 case E_ABORT:
3634 return S_OK;
3635 }
3636 try_create_environment();
3637 return S_OK;
3638 }
3639
3640 ICoreWebView2 *webview;
3641 ::EventRegistrationToken token;
3642 controller->get_CoreWebView2(&webview);
3643 webview->add_WebMessageReceived(this, &token);
3644 webview->add_PermissionRequested(this, &token);
3645
3646 m_cb(controller, webview);
3647 return S_OK;
3648 }
3649 HRESULT STDMETHODCALLTYPE
3650 Invoke(ICoreWebView2 * /*sender*/,
3651 ICoreWebView2WebMessageReceivedEventArgs *args) {
3652 LPWSTR message{};
3653 auto res = args->TryGetWebMessageAsString(&message);
3654 if (SUCCEEDED(res)) {
3655 m_msgCb(narrow_string(message));
3656 }
3657
3658 CoTaskMemFree(message);
3659 return S_OK;
3660 }
3661 HRESULT STDMETHODCALLTYPE
3662 Invoke(ICoreWebView2 * /*sender*/,
3663 ICoreWebView2PermissionRequestedEventArgs *args) {
3664 COREWEBVIEW2_PERMISSION_KIND kind;
3665 args->get_PermissionKind(&kind);
3666 if (kind == COREWEBVIEW2_PERMISSION_KIND_CLIPBOARD_READ) {
3667 args->put_State(COREWEBVIEW2_PERMISSION_STATE_ALLOW);
3668 }
3669 return S_OK;
3670 }
3671
3672 // Set the function that will perform the initiating logic for creating
3673 // the WebView2 environment.
3674 void set_attempt_handler(std::function<HRESULT()> attempt_handler) noexcept {
3675 m_attempt_handler = attempt_handler;
3676 }
3677
3678 // Retry creating a WebView2 environment.
3679 // The initiating logic for creating the environment is defined by the
3680 // caller of set_attempt_handler().
3681 void try_create_environment() noexcept {
3682 // WebView creation fails with HRESULT_FROM_WIN32(ERROR_INVALID_STATE) if
3683 // a running instance using the same user data folder exists, and the
3684 // Environment objects have different EnvironmentOptions.
3685 // Source: https://docs.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/icorewebview2environment?view=webview2-1.0.1150.38
3686 if (m_attempts < m_max_attempts) {
3687 ++m_attempts;
3688 auto res = m_attempt_handler();
3689 if (SUCCEEDED(res)) {
3690 return;
3691 }
3692 // Not entirely sure if this error code only applies to
3693 // CreateCoreWebView2Controller so we check here as well.
3694 if (res == HRESULT_FROM_WIN32(ERROR_INVALID_STATE)) {
3695 return;
3696 }
3697 try_create_environment();
3698 return;
3699 }
3700 // Give up.
3701 m_cb(nullptr, nullptr);
3702 }
3703
3704private:
3705 HWND m_window;
3706 msg_cb_t m_msgCb;
3707 webview2_com_handler_cb_t m_cb;
3708 std::atomic<ULONG> m_ref_count{1};
3709 std::function<HRESULT()> m_attempt_handler;
3710 unsigned int m_max_attempts = 5;
3711 unsigned int m_attempts = 0;
3712};
3713
3714class webview2_user_script_added_handler
3715 : public ICoreWebView2AddScriptToExecuteOnDocumentCreatedCompletedHandler {
3716public:
3717 using callback_fn = std::function<void(HRESULT errorCode, LPCWSTR id)>;
3718
3719 webview2_user_script_added_handler(callback_fn cb) : m_cb{cb} {}
3720
3721 virtual ~webview2_user_script_added_handler() = default;
3722 webview2_user_script_added_handler(
3723 const webview2_user_script_added_handler &other) = delete;
3724 webview2_user_script_added_handler &
3725 operator=(const webview2_user_script_added_handler &other) = delete;
3726 webview2_user_script_added_handler(
3727 webview2_user_script_added_handler &&other) = delete;
3728 webview2_user_script_added_handler &
3729 operator=(webview2_user_script_added_handler &&other) = delete;
3730
3731 ULONG STDMETHODCALLTYPE AddRef() { return ++m_ref_count; }
3732 ULONG STDMETHODCALLTYPE Release() {
3733 if (m_ref_count > 1) {
3734 return --m_ref_count;
3735 }
3736 delete this;
3737 return 0;
3738 }
3739
3740 HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, LPVOID *ppv) {
3741 using namespace mswebview2::cast_info;
3742
3743 if (!ppv) {
3744 return E_POINTER;
3745 }
3746
3747 if (cast_if_equal_iid(this, riid,
3748 add_script_to_execute_on_document_created_completed,
3749 ppv)) {
3750 return S_OK;
3751 }
3752
3753 return E_NOINTERFACE;
3754 }
3755
3756 HRESULT STDMETHODCALLTYPE Invoke(HRESULT res, LPCWSTR id) {
3757 m_cb(res, id);
3758 return S_OK;
3759 }
3760
3761private:
3762 callback_fn m_cb;
3763 std::atomic<ULONG> m_ref_count{1};
3764};
3765
3766class user_script::impl {
3767public:
3768 impl(const std::wstring &id, const std::wstring &code)
3769 : m_id{id}, m_code{code} {}
3770
3771 impl(const impl &) = delete;
3772 impl &operator=(const impl &) = delete;
3773 impl(impl &&) = delete;
3774 impl &operator=(impl &&) = delete;
3775
3776 const std::wstring &get_id() const { return m_id; }
3777 const std::wstring &get_code() const { return m_code; }
3778
3779private:
3780 std::wstring m_id;
3781 std::wstring m_code;
3782};
3783
3784class win32_edge_engine : public engine_base {
3785public:
3786 win32_edge_engine(bool debug, void *window) : m_owns_window{!window} {
3787 if (!is_webview2_available()) {
3788 throw exception{WEBVIEW_ERROR_MISSING_DEPENDENCY,
3789 "WebView2 is unavailable"};
3790 }
3791
3792 HINSTANCE hInstance = GetModuleHandle(nullptr);
3793
3794 if (m_owns_window) {
3795 m_com_init = {COINIT_APARTMENTTHREADED};
3796 enable_dpi_awareness();
3797
3798 HICON icon = (HICON)LoadImage(
3799 hInstance, IDI_APPLICATION, IMAGE_ICON, GetSystemMetrics(SM_CXICON),
3800 GetSystemMetrics(SM_CYICON), LR_DEFAULTCOLOR);
3801
3802 // Create a top-level window.
3803 WNDCLASSEXW wc;
3804 ZeroMemory(&wc, sizeof(WNDCLASSEX));
3805 wc.cbSize = sizeof(WNDCLASSEX);
3806 wc.hInstance = hInstance;
3807 wc.lpszClassName = L"webview";
3808 wc.hIcon = icon;
3809 wc.lpfnWndProc = (WNDPROC)(+[](HWND hwnd, UINT msg, WPARAM wp,
3810 LPARAM lp) -> LRESULT {
3811 win32_edge_engine *w{};
3812
3813 if (msg == WM_NCCREATE) {
3814 auto *lpcs{reinterpret_cast<LPCREATESTRUCT>(lp)};
3815 w = static_cast<win32_edge_engine *>(lpcs->lpCreateParams);
3816 w->m_window = hwnd;
3817 SetWindowLongPtrW(hwnd, GWLP_USERDATA, reinterpret_cast<LONG_PTR>(w));
3818 enable_non_client_dpi_scaling_if_needed(hwnd);
3819 apply_window_theme(hwnd);
3820 } else {
3821 w = reinterpret_cast<win32_edge_engine *>(
3822 GetWindowLongPtrW(hwnd, GWLP_USERDATA));
3823 }
3824
3825 if (!w) {
3826 return DefWindowProcW(hwnd, msg, wp, lp);
3827 }
3828
3829 switch (msg) {
3830 case WM_SIZE:
3831 w->resize_widget();
3832 break;
3833 case WM_CLOSE:
3834 DestroyWindow(hwnd);
3835 break;
3836 case WM_DESTROY:
3837 w->m_window = nullptr;
3838 SetWindowLongPtrW(hwnd, GWLP_USERDATA, 0);
3839 w->on_window_destroyed();
3840 break;
3841 case WM_GETMINMAXINFO: {
3842 auto lpmmi = (LPMINMAXINFO)lp;
3843 if (w->m_maxsz.x > 0 && w->m_maxsz.y > 0) {
3844 lpmmi->ptMaxSize = w->m_maxsz;
3845 lpmmi->ptMaxTrackSize = w->m_maxsz;
3846 }
3847 if (w->m_minsz.x > 0 && w->m_minsz.y > 0) {
3848 lpmmi->ptMinTrackSize = w->m_minsz;
3849 }
3850 } break;
3851 case 0x02E4 /*WM_GETDPISCALEDSIZE*/: {
3852 auto dpi = static_cast<int>(wp);
3853 auto *size{reinterpret_cast<SIZE *>(lp)};
3854 *size = w->get_scaled_size(w->m_dpi, dpi);
3855 return TRUE;
3856 }
3857 case 0x02E0 /*WM_DPICHANGED*/: {
3858 // Windows 10: The size we get here is exactly what we supplied to WM_GETDPISCALEDSIZE.
3859 // Windows 11: The size we get here is NOT what we supplied to WM_GETDPISCALEDSIZE.
3860 // Due to this difference, don't use the suggested bounds.
3861 auto dpi = static_cast<int>(HIWORD(wp));
3862 w->on_dpi_changed(dpi);
3863 break;
3864 }
3865 case WM_SETTINGCHANGE: {
3866 auto *area = reinterpret_cast<const wchar_t *>(lp);
3867 if (area) {
3868 w->on_system_setting_change(area);
3869 }
3870 break;
3871 }
3872 case WM_ACTIVATE:
3873 if (LOWORD(wp) != WA_INACTIVE) {
3874 w->focus_webview();
3875 }
3876 break;
3877 default:
3878 return DefWindowProcW(hwnd, msg, wp, lp);
3879 }
3880 return 0;
3881 });
3882 RegisterClassExW(&wc);
3883
3884 CreateWindowW(L"webview", L"", WS_OVERLAPPEDWINDOW, CW_USEDEFAULT,
3885 CW_USEDEFAULT, 0, 0, nullptr, nullptr, hInstance, this);
3886 if (!m_window) {
3887 throw exception{WEBVIEW_ERROR_INVALID_STATE, "Window is null"};
3888 }
3889 on_window_created();
3890
3891 m_dpi = get_window_dpi(m_window);
3892 constexpr const int initial_width = 640;
3893 constexpr const int initial_height = 480;
3894 set_size(initial_width, initial_height, WEBVIEW_HINT_NONE);
3895 } else {
3896 m_window = IsWindow(static_cast<HWND>(window))
3897 ? static_cast<HWND>(window)
3898 : *(static_cast<HWND *>(window));
3899 m_dpi = get_window_dpi(m_window);
3900 }
3901
3902 // Create a window that WebView2 will be embedded into.
3903 WNDCLASSEXW widget_wc{};
3904 widget_wc.cbSize = sizeof(WNDCLASSEX);
3905 widget_wc.hInstance = hInstance;
3906 widget_wc.lpszClassName = L"webview_widget";
3907 widget_wc.lpfnWndProc = (WNDPROC)(+[](HWND hwnd, UINT msg, WPARAM wp,
3908 LPARAM lp) -> LRESULT {
3909 win32_edge_engine *w{};
3910
3911 if (msg == WM_NCCREATE) {
3912 auto *lpcs{reinterpret_cast<LPCREATESTRUCT>(lp)};
3913 w = static_cast<win32_edge_engine *>(lpcs->lpCreateParams);
3914 w->m_widget = hwnd;
3915 SetWindowLongPtrW(hwnd, GWLP_USERDATA, reinterpret_cast<LONG_PTR>(w));
3916 } else {
3917 w = reinterpret_cast<win32_edge_engine *>(
3918 GetWindowLongPtrW(hwnd, GWLP_USERDATA));
3919 }
3920
3921 if (!w) {
3922 return DefWindowProcW(hwnd, msg, wp, lp);
3923 }
3924
3925 switch (msg) {
3926 case WM_SIZE:
3927 w->resize_webview();
3928 break;
3929 case WM_DESTROY:
3930 w->m_widget = nullptr;
3931 SetWindowLongPtrW(hwnd, GWLP_USERDATA, 0);
3932 break;
3933 default:
3934 return DefWindowProcW(hwnd, msg, wp, lp);
3935 }
3936 return 0;
3937 });
3938 RegisterClassExW(&widget_wc);
3939 CreateWindowExW(WS_EX_CONTROLPARENT, L"webview_widget", nullptr, WS_CHILD,
3940 0, 0, 0, 0, m_window, nullptr, hInstance, this);
3941 if (!m_widget) {
3942 throw exception{WEBVIEW_ERROR_INVALID_STATE, "Widget window is null"};
3943 }
3944
3945 // Create a message-only window for internal messaging.
3946 WNDCLASSEXW message_wc{};
3947 message_wc.cbSize = sizeof(WNDCLASSEX);
3948 message_wc.hInstance = hInstance;
3949 message_wc.lpszClassName = L"webview_message";
3950 message_wc.lpfnWndProc = (WNDPROC)(+[](HWND hwnd, UINT msg, WPARAM wp,
3951 LPARAM lp) -> LRESULT {
3952 win32_edge_engine *w{};
3953
3954 if (msg == WM_NCCREATE) {
3955 auto *lpcs{reinterpret_cast<LPCREATESTRUCT>(lp)};
3956 w = static_cast<win32_edge_engine *>(lpcs->lpCreateParams);
3957 w->m_message_window = hwnd;
3958 SetWindowLongPtrW(hwnd, GWLP_USERDATA, reinterpret_cast<LONG_PTR>(w));
3959 } else {
3960 w = reinterpret_cast<win32_edge_engine *>(
3961 GetWindowLongPtrW(hwnd, GWLP_USERDATA));
3962 }
3963
3964 if (!w) {
3965 return DefWindowProcW(hwnd, msg, wp, lp);
3966 }
3967
3968 switch (msg) {
3969 case WM_APP:
3970 if (auto f = (dispatch_fn_t *)(lp)) {
3971 (*f)();
3972 delete f;
3973 }
3974 break;
3975 case WM_DESTROY:
3976 w->m_message_window = nullptr;
3977 SetWindowLongPtrW(hwnd, GWLP_USERDATA, 0);
3978 break;
3979 default:
3980 return DefWindowProcW(hwnd, msg, wp, lp);
3981 }
3982 return 0;
3983 });
3984 RegisterClassExW(&message_wc);
3985 CreateWindowExW(0, L"webview_message", nullptr, 0, 0, 0, 0, 0, HWND_MESSAGE,
3986 nullptr, hInstance, this);
3987 if (!m_message_window) {
3988 throw exception{WEBVIEW_ERROR_INVALID_STATE, "Message window is null"};
3989 }
3990
3991 if (m_owns_window) {
3992 ShowWindow(m_window, SW_SHOW);
3993 UpdateWindow(m_window);
3994 SetFocus(m_window);
3995 }
3996
3997 auto cb =
3998 std::bind(&win32_edge_engine::on_message, this, std::placeholders::_1);
3999
4000 embed(m_widget, debug, cb).ensure_ok();
4001 }
4002
4003 virtual ~win32_edge_engine() {
4004 if (m_com_handler) {
4005 m_com_handler->Release();
4006 m_com_handler = nullptr;
4007 }
4008 if (m_webview) {
4009 m_webview->Release();
4010 m_webview = nullptr;
4011 }
4012 if (m_controller) {
4013 m_controller->Release();
4014 m_controller = nullptr;
4015 }
4016 // Replace wndproc to avoid callbacks and other bad things during
4017 // destruction.
4018 auto wndproc = reinterpret_cast<LONG_PTR>(
4019 +[](HWND hwnd, UINT msg, WPARAM wp, LPARAM lp) -> LRESULT {
4020 return DefWindowProcW(hwnd, msg, wp, lp);
4021 });
4022 if (m_widget) {
4023 SetWindowLongPtrW(m_widget, GWLP_WNDPROC, wndproc);
4024 }
4025 if (m_window && m_owns_window) {
4026 SetWindowLongPtrW(m_window, GWLP_WNDPROC, wndproc);
4027 }
4028 if (m_widget) {
4029 DestroyWindow(m_widget);
4030 m_widget = nullptr;
4031 }
4032 if (m_window) {
4033 if (m_owns_window) {
4034 DestroyWindow(m_window);
4035 on_window_destroyed(true);
4036 }
4037 m_window = nullptr;
4038 }
4039 if (m_owns_window) {
4040 // Not strictly needed for windows to close immediately but aligns
4041 // behavior across backends.
4042 deplete_run_loop_event_queue();
4043 }
4044 // We need the message window in order to deplete the event queue.
4045 if (m_message_window) {
4046 SetWindowLongPtrW(m_message_window, GWLP_WNDPROC, wndproc);
4047 DestroyWindow(m_message_window);
4048 m_message_window = nullptr;
4049 }
4050 }
4051
4052 win32_edge_engine(const win32_edge_engine &other) = delete;
4053 win32_edge_engine &operator=(const win32_edge_engine &other) = delete;
4054 win32_edge_engine(win32_edge_engine &&other) = delete;
4055 win32_edge_engine &operator=(win32_edge_engine &&other) = delete;
4056
4057protected:
4058 noresult run_impl() override {
4059 MSG msg;
4060 while (GetMessageW(&msg, nullptr, 0, 0) > 0) {
4061 TranslateMessage(&msg);
4062 DispatchMessageW(&msg);
4063 }
4064 return {};
4065 }
4066 result<void *> window_impl() override {
4067 if (m_window) {
4068 return m_window;
4069 }
4070 return error_info{WEBVIEW_ERROR_INVALID_STATE};
4071 }
4072 result<void *> widget_impl() override {
4073 if (m_widget) {
4074 return m_widget;
4075 }
4076 return error_info{WEBVIEW_ERROR_INVALID_STATE};
4077 }
4078 result<void *> browser_controller_impl() override {
4079 if (m_controller) {
4080 return m_controller;
4081 }
4082 return error_info{WEBVIEW_ERROR_INVALID_STATE};
4083 }
4084 noresult terminate_impl() override {
4085 PostQuitMessage(0);
4086 return {};
4087 }
4088 noresult dispatch_impl(dispatch_fn_t f) override {
4089 PostMessageW(m_message_window, WM_APP, 0, (LPARAM) new dispatch_fn_t(f));
4090 return {};
4091 }
4092
4093 noresult set_title_impl(const std::string &title) override {
4094 SetWindowTextW(m_window, widen_string(title).c_str());
4095 return {};
4096 }
4097
4098 noresult set_size_impl(int width, int height, webview_hint_t hints) override {
4099 auto style = GetWindowLong(m_window, GWL_STYLE);
4100 if (hints == WEBVIEW_HINT_FIXED) {
4101 style &= ~(WS_THICKFRAME | WS_MAXIMIZEBOX);
4102 } else {
4103 style |= (WS_THICKFRAME | WS_MAXIMIZEBOX);
4104 }
4105 SetWindowLong(m_window, GWL_STYLE, style);
4106
4107 if (hints == WEBVIEW_HINT_MAX) {
4108 m_maxsz.x = width;
4109 m_maxsz.y = height;
4110 } else if (hints == WEBVIEW_HINT_MIN) {
4111 m_minsz.x = width;
4112 m_minsz.y = height;
4113 } else {
4114 auto dpi = get_window_dpi(m_window);
4115 m_dpi = dpi;
4116 auto scaled_size =
4117 scale_size(width, height, get_default_window_dpi(), dpi);
4118 auto frame_size =
4119 make_window_frame_size(m_window, scaled_size.cx, scaled_size.cy, dpi);
4120 SetWindowPos(m_window, nullptr, 0, 0, frame_size.cx, frame_size.cy,
4121 SWP_NOZORDER | SWP_NOACTIVATE | SWP_NOMOVE |
4122 SWP_FRAMECHANGED);
4123 }
4124 return {};
4125 }
4126
4127 noresult navigate_impl(const std::string &url) override {
4128 auto wurl = widen_string(url);
4129 m_webview->Navigate(wurl.c_str());
4130 return {};
4131 }
4132
4133 noresult eval_impl(const std::string &js) override {
4134 // TODO: Skip if no content has begun loading yet. Can't check with
4135 // ICoreWebView2::get_Source because it returns "about:blank".
4136 auto wjs = widen_string(js);
4137 m_webview->ExecuteScript(wjs.c_str(), nullptr);
4138 return {};
4139 }
4140
4141 noresult set_html_impl(const std::string &html) override {
4142 m_webview->NavigateToString(widen_string(html).c_str());
4143 return {};
4144 }
4145
4146 user_script add_user_script_impl(const std::string &js) override {
4147 auto wjs = widen_string(js);
4148 std::wstring script_id;
4149 bool done{};
4150 webview2_user_script_added_handler handler{[&](HRESULT res, LPCWSTR id) {
4151 if (SUCCEEDED(res)) {
4152 script_id = id;
4153 }
4154 done = true;
4155 }};
4156 auto res =
4157 m_webview->AddScriptToExecuteOnDocumentCreated(wjs.c_str(), &handler);
4158 if (SUCCEEDED(res)) {
4159 // Sadly we need to pump the even loop in order to get the script ID.
4160 while (!done) {
4161 deplete_run_loop_event_queue();
4162 }
4163 }
4164 // TODO: There's a non-zero chance that we didn't get the script ID.
4165 // We need to convey the error somehow.
4166 return user_script{js, std::unique_ptr<user_script::impl>{
4167 new user_script::impl{script_id, wjs}}};
4168 }
4169
4170 void
4171 remove_all_user_scripts_impl(const std::list<user_script> &scripts) override {
4172 for (const auto &script : scripts) {
4173 const auto &id = script.get_impl().get_id();
4174 m_webview->RemoveScriptToExecuteOnDocumentCreated(id.c_str());
4175 }
4176 }
4177
4178 bool are_user_scripts_equal_impl(const user_script &first,
4179 const user_script &second) override {
4180 const auto &first_id = first.get_impl().get_id();
4181 const auto &second_id = second.get_impl().get_id();
4182 return first_id == second_id;
4183 }
4184
4185private:
4186 noresult embed(HWND wnd, bool debug, msg_cb_t cb) {
4187 std::atomic_flag flag = ATOMIC_FLAG_INIT;
4188 flag.test_and_set();
4189
4190 wchar_t currentExePath[MAX_PATH];
4191 GetModuleFileNameW(nullptr, currentExePath, MAX_PATH);
4192 wchar_t *currentExeName = PathFindFileNameW(currentExePath);
4193
4194 wchar_t dataPath[MAX_PATH];
4195 if (!SUCCEEDED(
4196 SHGetFolderPathW(nullptr, CSIDL_APPDATA, nullptr, 0, dataPath))) {
4197 return error_info{WEBVIEW_ERROR_UNSPECIFIED, "SHGetFolderPathW failed"};
4198 }
4199 wchar_t userDataFolder[MAX_PATH];
4200 PathCombineW(userDataFolder, dataPath, currentExeName);
4201
4202 m_com_handler = new webview2_com_handler(
4203 wnd, cb,
4204 [&](ICoreWebView2Controller *controller, ICoreWebView2 *webview) {
4205 if (!controller || !webview) {
4206 flag.clear();
4207 return;
4208 }
4209 controller->AddRef();
4210 webview->AddRef();
4211 m_controller = controller;
4212 m_webview = webview;
4213 flag.clear();
4214 });
4215
4216 m_com_handler->set_attempt_handler([&] {
4217 return m_webview2_loader.create_environment_with_options(
4218 nullptr, userDataFolder, nullptr, m_com_handler);
4219 });
4220 m_com_handler->try_create_environment();
4221
4222 // Pump the message loop until WebView2 has finished initialization.
4223 bool got_quit_msg = false;
4224 MSG msg;
4225 while (flag.test_and_set() && GetMessageW(&msg, nullptr, 0, 0) >= 0) {
4226 if (msg.message == WM_QUIT) {
4227 got_quit_msg = true;
4228 break;
4229 }
4230 TranslateMessage(&msg);
4231 DispatchMessageW(&msg);
4232 }
4233 if (got_quit_msg) {
4234 return error_info{WEBVIEW_ERROR_CANCELED};
4235 }
4236 if (!m_controller || !m_webview) {
4237 return error_info{WEBVIEW_ERROR_INVALID_STATE};
4238 }
4239 ICoreWebView2Settings *settings = nullptr;
4240 auto res = m_webview->get_Settings(&settings);
4241 if (res != S_OK) {
4242 return error_info{WEBVIEW_ERROR_UNSPECIFIED, "get_Settings failed"};
4243 }
4244 res = settings->put_AreDevToolsEnabled(debug ? TRUE : FALSE);
4245 if (res != S_OK) {
4246 return error_info{WEBVIEW_ERROR_UNSPECIFIED,
4247 "put_AreDevToolsEnabled failed"};
4248 }
4249 res = settings->put_IsStatusBarEnabled(FALSE);
4250 if (res != S_OK) {
4251 return error_info{WEBVIEW_ERROR_UNSPECIFIED,
4252 "put_IsStatusBarEnabled failed"};
4253 }
4254 add_init_script("function(message) {\n\
4255 return window.chrome.webview.postMessage(message);\n\
4256}");
4257 resize_webview();
4258 m_controller->put_IsVisible(TRUE);
4259 ShowWindow(m_widget, SW_SHOW);
4260 UpdateWindow(m_widget);
4261 if (m_owns_window) {
4262 focus_webview();
4263 }
4264 return {};
4265 }
4266
4267 void resize_widget() {
4268 if (m_widget) {
4269 RECT r{};
4270 if (GetClientRect(GetParent(m_widget), &r)) {
4271 MoveWindow(m_widget, r.left, r.top, r.right - r.left, r.bottom - r.top,
4272 TRUE);
4273 }
4274 }
4275 }
4276
4277 void resize_webview() {
4278 if (m_widget && m_controller) {
4279 RECT bounds{};
4280 if (GetClientRect(m_widget, &bounds)) {
4281 m_controller->put_Bounds(bounds);
4282 }
4283 }
4284 }
4285
4286 void focus_webview() {
4287 if (m_controller) {
4288 m_controller->MoveFocus(COREWEBVIEW2_MOVE_FOCUS_REASON_PROGRAMMATIC);
4289 }
4290 }
4291
4292 bool is_webview2_available() const noexcept {
4293 LPWSTR version_info = nullptr;
4294 auto res = m_webview2_loader.get_available_browser_version_string(
4295 nullptr, &version_info);
4296 // The result will be equal to HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND)
4297 // if the WebView2 runtime is not installed.
4298 auto ok = SUCCEEDED(res) && version_info;
4299 if (version_info) {
4300 CoTaskMemFree(version_info);
4301 }
4302 return ok;
4303 }
4304
4305 void on_dpi_changed(int dpi) {
4306 auto scaled_size = get_scaled_size(m_dpi, dpi);
4307 auto frame_size =
4308 make_window_frame_size(m_window, scaled_size.cx, scaled_size.cy, dpi);
4309 SetWindowPos(m_window, nullptr, 0, 0, frame_size.cx, frame_size.cy,
4310 SWP_NOZORDER | SWP_NOACTIVATE | SWP_NOMOVE | SWP_FRAMECHANGED);
4311 m_dpi = dpi;
4312 }
4313
4314 SIZE get_size() const {
4315 RECT bounds;
4316 GetClientRect(m_window, &bounds);
4317 auto width = bounds.right - bounds.left;
4318 auto height = bounds.bottom - bounds.top;
4319 return {width, height};
4320 }
4321
4322 SIZE get_scaled_size(int from_dpi, int to_dpi) const {
4323 auto size = get_size();
4324 return scale_size(size.cx, size.cy, from_dpi, to_dpi);
4325 }
4326
4327 void on_system_setting_change(const wchar_t *area) {
4328 // Detect light/dark mode change in system.
4329 if (lstrcmpW(area, L"ImmersiveColorSet") == 0) {
4330 apply_window_theme(m_window);
4331 }
4332 }
4333
4334 // Blocks while depleting the run loop of events.
4335 void deplete_run_loop_event_queue() {
4336 bool done{};
4337 dispatch([&] { done = true; });
4338 while (!done) {
4339 MSG msg;
4340 if (GetMessageW(&msg, nullptr, 0, 0) > 0) {
4341 TranslateMessage(&msg);
4342 DispatchMessageW(&msg);
4343 }
4344 }
4345 }
4346
4347 // The app is expected to call CoInitializeEx before
4348 // CreateCoreWebView2EnvironmentWithOptions.
4349 // Source: https://docs.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/webview2-idl#createcorewebview2environmentwithoptions
4350 com_init_wrapper m_com_init;
4351 HWND m_window = nullptr;
4352 HWND m_widget = nullptr;
4353 HWND m_message_window = nullptr;
4354 POINT m_minsz = POINT{0, 0};
4355 POINT m_maxsz = POINT{0, 0};
4356 DWORD m_main_thread = GetCurrentThreadId();
4357 ICoreWebView2 *m_webview = nullptr;
4358 ICoreWebView2Controller *m_controller = nullptr;
4359 webview2_com_handler *m_com_handler = nullptr;
4360 mswebview2::loader m_webview2_loader;
4361 int m_dpi{};
4362 bool m_owns_window{};
4363};
4364
4365} // namespace detail
4366
4367using browser_engine = detail::win32_edge_engine;
4368
4369} // namespace webview
4370
4371#endif /* WEBVIEW_GTK, WEBVIEW_COCOA, WEBVIEW_EDGE */
4372
4373namespace webview {
4374using webview = browser_engine;
4375
4376namespace detail {
4377
4378inline webview *cast_to_webview(void *w) {
4379 if (!w) {
4380 throw exception{WEBVIEW_ERROR_INVALID_ARGUMENT,
4381 "Cannot cast null pointer to webview instance"};
4382 }
4383 return static_cast<webview *>(w);
4384}
4385
4386} // namespace detail
4387} // namespace webview
4388
4389WEBVIEW_API webview_t webview_create(int debug, void *wnd) {
4390 using namespace webview::detail;
4391 webview::webview *w{};
4392 auto err = api_filter(
4393 [=]() -> webview::result<webview::webview *> {
4394 return new webview::webview{static_cast<bool>(debug), wnd};
4395 },
4396 [&](webview::webview *w_) { w = w_; });
4397 if (err == WEBVIEW_ERROR_OK) {
4398 return w;
4399 }
4400 return nullptr;
4401}
4402
4404 using namespace webview::detail;
4405 return api_filter([=]() -> webview::noresult {
4406 delete cast_to_webview(w);
4407 return {};
4408 });
4409}
4410
4412 using namespace webview::detail;
4413 return api_filter([=] { return cast_to_webview(w)->run(); });
4414}
4415
4417 using namespace webview::detail;
4418 return api_filter([=] { return cast_to_webview(w)->terminate(); });
4419}
4420
4422 void (*fn)(webview_t, void *),
4423 void *arg) {
4424 using namespace webview::detail;
4425 if (!fn) {
4427 }
4428 return api_filter(
4429 [=] { return cast_to_webview(w)->dispatch([=]() { fn(w, arg); }); });
4430}
4431
4433 using namespace webview::detail;
4434 void *window = nullptr;
4435 auto err = api_filter([=] { return cast_to_webview(w)->window(); },
4436 [&](void *value) { window = value; });
4437 if (err == WEBVIEW_ERROR_OK) {
4438 return window;
4439 }
4440 return nullptr;
4441}
4442
4445 using namespace webview::detail;
4446 void *handle{};
4447 auto err = api_filter(
4448 [=]() -> webview::result<void *> {
4449 auto *w_ = cast_to_webview(w);
4450 switch (kind) {
4452 return w_->window();
4454 return w_->widget();
4456 return w_->browser_controller();
4457 default:
4458 return webview::error_info{WEBVIEW_ERROR_INVALID_ARGUMENT};
4459 }
4460 },
4461 [&](void *handle_) { handle = handle_; });
4462 if (err == WEBVIEW_ERROR_OK) {
4463 return handle;
4464 }
4465 return nullptr;
4466}
4467
4469 using namespace webview::detail;
4470 if (!title) {
4472 }
4473 return api_filter([=] { return cast_to_webview(w)->set_title(title); });
4474}
4475
4476WEBVIEW_API webview_error_t webview_set_size(webview_t w, int width, int height,
4477 webview_hint_t hints) {
4478 using namespace webview::detail;
4479 return api_filter(
4480 [=] { return cast_to_webview(w)->set_size(width, height, hints); });
4481}
4482
4484 using namespace webview::detail;
4485 if (!url) {
4487 }
4488 return api_filter([=] { return cast_to_webview(w)->navigate(url); });
4489}
4490
4492 using namespace webview::detail;
4493 if (!html) {
4495 }
4496 return api_filter([=] { return cast_to_webview(w)->set_html(html); });
4497}
4498
4500 using namespace webview::detail;
4501 if (!js) {
4503 }
4504 return api_filter([=] { return cast_to_webview(w)->init(js); });
4505}
4506
4508 using namespace webview::detail;
4509 if (!js) {
4511 }
4512 return api_filter([=] { return cast_to_webview(w)->eval(js); });
4513}
4514
4516 void (*fn)(const char *id,
4517 const char *req, void *arg),
4518 void *arg) {
4519 using namespace webview::detail;
4520 if (!name || !fn) {
4522 }
4523 return api_filter([=] {
4524 return cast_to_webview(w)->bind(
4525 name,
4526 [=](const std::string &seq, const std::string &req, void *arg_) {
4527 fn(seq.c_str(), req.c_str(), arg_);
4528 },
4529 arg);
4530 });
4531}
4532
4534 using namespace webview::detail;
4535 if (!name) {
4537 }
4538 return api_filter([=] { return cast_to_webview(w)->unbind(name); });
4539}
4540
4542 int status, const char *result) {
4543 using namespace webview::detail;
4544 if (!id || !result) {
4546 }
4547 return api_filter(
4548 [=] { return cast_to_webview(w)->resolve(id, status, result); });
4549}
4550
4552 return &webview::detail::library_version_info;
4553}
4554
4555#endif /* WEBVIEW_HEADER */
4556#endif /* __cplusplus */
4557#endif /* WEBVIEW_H */
Holds the library's version information.
Definition webview.h:127
char build_metadata[48]
SemVer 2.0.0 build metadata prefixed with "+", otherwise an empty string.
Definition webview.h:136
webview_version_t version
The elements of the version number.
Definition webview.h:129
char version_number[32]
SemVer 2.0.0 version number in MAJOR.MINOR.PATCH format.
Definition webview.h:131
char pre_release[48]
Definition webview.h:134
Holds the elements of a MAJOR.MINOR.PATCH version number.
Definition webview.h:117
unsigned int major
Major version.
Definition webview.h:119
unsigned int minor
Minor version.
Definition webview.h:121
unsigned int patch
Patch version.
Definition webview.h:123
void * webview_t
Pointer to a webview instance.
Definition webview.h:140
WEBVIEW_API webview_error_t webview_bind(webview_t w, const char *name, void(*fn)(const char *id, const char *req, void *arg), void *arg)
WEBVIEW_API const webview_version_info_t * webview_version(void)
WEBVIEW_API webview_error_t webview_run(webview_t w)
#define WEBVIEW_VERSION_BUILD_METADATA
SemVer 2.0.0 build metadata prefixed with "+".
Definition webview.h:89
WEBVIEW_API webview_error_t webview_terminate(webview_t w)
WEBVIEW_API webview_error_t webview_unbind(webview_t w, const char *name)
#define WEBVIEW_API
Definition webview.h:60
WEBVIEW_API void * webview_get_window(webview_t w)
webview_native_handle_kind_t
Native handle kind. The actual type depends on the backend.
Definition webview.h:143
@ WEBVIEW_NATIVE_HANDLE_KIND_BROWSER_CONTROLLER
Definition webview.h:153
@ WEBVIEW_NATIVE_HANDLE_KIND_UI_WIDGET
Definition webview.h:149
@ WEBVIEW_NATIVE_HANDLE_KIND_UI_WINDOW
Definition webview.h:146
WEBVIEW_API void * webview_get_native_handle(webview_t w, webview_native_handle_kind_t kind)
WEBVIEW_API webview_error_t webview_set_title(webview_t w, const char *title)
#define WEBVIEW_VERSION_MINOR
The current library minor version.
Definition webview.h:74
#define WEBVIEW_VERSION_MAJOR
The current library major version.
Definition webview.h:69
WEBVIEW_API webview_error_t webview_destroy(webview_t w)
WEBVIEW_API webview_error_t webview_navigate(webview_t w, const char *url)
WEBVIEW_API webview_error_t webview_dispatch(webview_t w, void(*fn)(webview_t w, void *arg), void *arg)
WEBVIEW_API webview_t webview_create(int debug, void *window)
webview_hint_t
Window size hints.
Definition webview.h:157
@ WEBVIEW_HINT_NONE
Width and height are default size.
Definition webview.h:159
@ WEBVIEW_HINT_MIN
Width and height are minimum bounds.
Definition webview.h:161
@ WEBVIEW_HINT_FIXED
Window size can not be changed by a user.
Definition webview.h:165
@ WEBVIEW_HINT_MAX
Width and height are maximum bounds.
Definition webview.h:163
#define WEBVIEW_VERSION_PRE_RELEASE
SemVer 2.0.0 pre-release labels prefixed with "-".
Definition webview.h:84
WEBVIEW_API webview_error_t webview_eval(webview_t w, const char *js)
webview_error_t
Error codes returned to callers of the API.
Definition webview.h:184
@ WEBVIEW_ERROR_INVALID_ARGUMENT
One or more invalid arguments have been specified e.g. in a function call.
Definition webview.h:192
@ WEBVIEW_ERROR_DUPLICATE
Signifies that something already exists.
Definition webview.h:199
@ WEBVIEW_ERROR_NOT_FOUND
Signifies that something does not exist.
Definition webview.h:201
@ WEBVIEW_ERROR_MISSING_DEPENDENCY
Missing dependency.
Definition webview.h:186
@ WEBVIEW_ERROR_INVALID_STATE
Invalid state detected.
Definition webview.h:190
@ WEBVIEW_ERROR_CANCELED
Operation canceled.
Definition webview.h:188
@ WEBVIEW_ERROR_UNSPECIFIED
An unspecified error occurred. A more specific error code may be needed.
Definition webview.h:194
@ WEBVIEW_ERROR_OK
Definition webview.h:197
WEBVIEW_API webview_error_t webview_set_size(webview_t w, int width, int height, webview_hint_t hints)
WEBVIEW_API webview_error_t webview_return(webview_t w, const char *id, int status, const char *result)
#define WEBVIEW_VERSION_PATCH
The current library patch version.
Definition webview.h:79
WEBVIEW_API webview_error_t webview_init(webview_t w, const char *js)
#define WEBVIEW_VERSION_NUMBER
SemVer 2.0.0 version number in MAJOR.MINOR.PATCH format.
Definition webview.h:109
WEBVIEW_API webview_error_t webview_set_html(webview_t w, const char *html)