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