webview_ruby2 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,1380 @@
1
+ /*
2
+ * MIT License
3
+ *
4
+ * Copyright (c) 2017 Serge Zaitsev
5
+ *
6
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
7
+ * of this software and associated documentation files (the "Software"), to deal
8
+ * in the Software without restriction, including without limitation the rights
9
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10
+ * copies of the Software, and to permit persons to whom the Software is
11
+ * furnished to do so, subject to the following conditions:
12
+ *
13
+ * The above copyright notice and this permission notice shall be included in
14
+ * all copies or substantial portions of the Software.
15
+ *
16
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22
+ * SOFTWARE.
23
+ */
24
+ #ifndef WEBVIEW_H
25
+ #define WEBVIEW_H
26
+
27
+ #ifndef WEBVIEW_API
28
+ #define WEBVIEW_API extern
29
+ #endif
30
+
31
+ #ifdef __cplusplus
32
+ extern "C" {
33
+ #endif
34
+
35
+ typedef void *webview_t;
36
+
37
+ // Creates a new webview instance. If debug is non-zero - developer tools will
38
+ // be enabled (if the platform supports them). Window parameter can be a
39
+ // pointer to the native window handle. If it's non-null - then child WebView
40
+ // is embedded into the given parent window. Otherwise a new window is created.
41
+ // Depending on the platform, a GtkWindow, NSWindow or HWND pointer can be
42
+ // passed here.
43
+ WEBVIEW_API webview_t webview_create(int debug, void *window);
44
+
45
+ // Destroys a webview and closes the native window.
46
+ WEBVIEW_API void webview_destroy(webview_t w);
47
+
48
+ // Runs the main loop until it's terminated. After this function exits - you
49
+ // must destroy the webview.
50
+ WEBVIEW_API void webview_run(webview_t w);
51
+
52
+ // Stops the main loop. It is safe to call this function from another other
53
+ // background thread.
54
+ WEBVIEW_API void webview_terminate(webview_t w);
55
+
56
+ // Posts a function to be executed on the main thread. You normally do not need
57
+ // to call this function, unless you want to tweak the native window.
58
+ WEBVIEW_API void
59
+ webview_dispatch(webview_t w, void (*fn)(webview_t w, void *arg), void *arg);
60
+
61
+ // Returns a native window handle pointer. When using GTK backend the pointer
62
+ // is GtkWindow pointer, when using Cocoa backend the pointer is NSWindow
63
+ // pointer, when using Win32 backend the pointer is HWND pointer.
64
+ WEBVIEW_API void *webview_get_window(webview_t w);
65
+
66
+ // Updates the title of the native window. Must be called from the UI thread.
67
+ WEBVIEW_API void webview_set_title(webview_t w, const char *title);
68
+
69
+ // Window size hints
70
+ #define WEBVIEW_HINT_NONE 0 // Width and height are default size
71
+ #define WEBVIEW_HINT_MIN 1 // Width and height are minimum bounds
72
+ #define WEBVIEW_HINT_MAX 2 // Width and height are maximum bounds
73
+ #define WEBVIEW_HINT_FIXED 3 // Window size can not be changed by a user
74
+ // Updates native window size. See WEBVIEW_HINT constants.
75
+ WEBVIEW_API void webview_set_size(webview_t w, int width, int height,
76
+ int hints);
77
+
78
+ // Navigates webview to the given URL. URL may be a data URI, i.e.
79
+ // "data:text/text,<html>...</html>". It is often ok not to url-encode it
80
+ // properly, webview will re-encode it for you.
81
+ WEBVIEW_API void webview_navigate(webview_t w, const char *url);
82
+
83
+ // Injects JavaScript code at the initialization of the new page. Every time
84
+ // the webview will open a the new page - this initialization code will be
85
+ // executed. It is guaranteed that code is executed before window.onload.
86
+ WEBVIEW_API void webview_init(webview_t w, const char *js);
87
+
88
+ // Evaluates arbitrary JavaScript code. Evaluation happens asynchronously, also
89
+ // the result of the expression is ignored. Use RPC bindings if you want to
90
+ // receive notifications about the results of the evaluation.
91
+ WEBVIEW_API void webview_eval(webview_t w, const char *js);
92
+
93
+ // Binds a native C callback so that it will appear under the given name as a
94
+ // global JavaScript function. Internally it uses webview_init(). Callback
95
+ // receives a request string and a user-provided argument pointer. Request
96
+ // string is a JSON array of all the arguments passed to the JavaScript
97
+ // function.
98
+ WEBVIEW_API void webview_bind(webview_t w, const char *name,
99
+ void (*fn)(const char *seq, const char *req,
100
+ void *arg),
101
+ void *arg);
102
+
103
+ // Allows to return a value from the native binding. Original request pointer
104
+ // must be provided to help internal RPC engine match requests with responses.
105
+ // If status is zero - result is expected to be a valid JSON result value.
106
+ // If status is not zero - result is an error JSON object.
107
+ WEBVIEW_API void webview_return(webview_t w, const char *seq, int status,
108
+ const char *result);
109
+
110
+ #ifdef __cplusplus
111
+ }
112
+ #endif
113
+
114
+ #ifndef WEBVIEW_HEADER
115
+
116
+ #if !defined(WEBVIEW_GTK) && !defined(WEBVIEW_COCOA) && !defined(WEBVIEW_EDGE)
117
+ #if defined(__linux__)
118
+ #define WEBVIEW_GTK
119
+ #elif defined(__APPLE__)
120
+ #define WEBVIEW_COCOA
121
+ #elif defined(_WIN32)
122
+ #define WEBVIEW_EDGE
123
+ #else
124
+ #error "please, specify webview backend"
125
+ #endif
126
+ #endif
127
+
128
+ #include <atomic>
129
+ #include <functional>
130
+ #include <future>
131
+ #include <map>
132
+ #include <string>
133
+ #include <utility>
134
+ #include <vector>
135
+
136
+ #include <cstring>
137
+
138
+ namespace webview {
139
+ using dispatch_fn_t = std::function<void()>;
140
+
141
+ // Convert ASCII hex digit to a nibble (four bits, 0 - 15).
142
+ //
143
+ // Use unsigned to avoid signed overflow UB.
144
+ static inline unsigned char hex2nibble(unsigned char c) {
145
+ if (c >= '0' && c <= '9') {
146
+ return c - '0';
147
+ } else if (c >= 'a' && c <= 'f') {
148
+ return 10 + (c - 'a');
149
+ } else if (c >= 'A' && c <= 'F') {
150
+ return 10 + (c - 'A');
151
+ }
152
+ return 0;
153
+ }
154
+
155
+ // Convert ASCII hex string (two characters) to byte.
156
+ //
157
+ // E.g., "0B" => 0x0B, "af" => 0xAF.
158
+ static inline char hex2char(const char *p) {
159
+ return hex2nibble(p[0]) * 16 + hex2nibble(p[1]);
160
+ }
161
+
162
+ inline std::string url_encode(const std::string s) {
163
+ std::string encoded;
164
+ for (unsigned int i = 0; i < s.length(); i++) {
165
+ auto c = s[i];
166
+ if (isalnum(c) || c == '-' || c == '_' || c == '.' || c == '~') {
167
+ encoded = encoded + c;
168
+ } else {
169
+ char hex[4];
170
+ snprintf(hex, sizeof(hex), "%%%02x", c);
171
+ encoded = encoded + hex;
172
+ }
173
+ }
174
+ return encoded;
175
+ }
176
+
177
+ inline std::string url_decode(const std::string st) {
178
+ std::string decoded;
179
+ const char *s = st.c_str();
180
+ size_t length = strlen(s);
181
+ for (unsigned int i = 0; i < length; i++) {
182
+ if (s[i] == '%') {
183
+ decoded.push_back(hex2char(s + i + 1));
184
+ i = i + 2;
185
+ } else if (s[i] == '+') {
186
+ decoded.push_back(' ');
187
+ } else {
188
+ decoded.push_back(s[i]);
189
+ }
190
+ }
191
+ return decoded;
192
+ }
193
+
194
+ inline std::string html_from_uri(const std::string s) {
195
+ if (s.substr(0, 15) == "data:text/html,") {
196
+ return url_decode(s.substr(15));
197
+ }
198
+ return "";
199
+ }
200
+
201
+ inline int json_parse_c(const char *s, size_t sz, const char *key, size_t keysz,
202
+ const char **value, size_t *valuesz) {
203
+ enum {
204
+ JSON_STATE_VALUE,
205
+ JSON_STATE_LITERAL,
206
+ JSON_STATE_STRING,
207
+ JSON_STATE_ESCAPE,
208
+ JSON_STATE_UTF8
209
+ } state = JSON_STATE_VALUE;
210
+ const char *k = NULL;
211
+ int index = 1;
212
+ int depth = 0;
213
+ int utf8_bytes = 0;
214
+
215
+ if (key == NULL) {
216
+ index = keysz;
217
+ keysz = 0;
218
+ }
219
+
220
+ *value = NULL;
221
+ *valuesz = 0;
222
+
223
+ for (; sz > 0; s++, sz--) {
224
+ enum {
225
+ JSON_ACTION_NONE,
226
+ JSON_ACTION_START,
227
+ JSON_ACTION_END,
228
+ JSON_ACTION_START_STRUCT,
229
+ JSON_ACTION_END_STRUCT
230
+ } action = JSON_ACTION_NONE;
231
+ unsigned char c = *s;
232
+ switch (state) {
233
+ case JSON_STATE_VALUE:
234
+ if (c == ' ' || c == '\t' || c == '\n' || c == '\r' || c == ',' ||
235
+ c == ':') {
236
+ continue;
237
+ } else if (c == '"') {
238
+ action = JSON_ACTION_START;
239
+ state = JSON_STATE_STRING;
240
+ } else if (c == '{' || c == '[') {
241
+ action = JSON_ACTION_START_STRUCT;
242
+ } else if (c == '}' || c == ']') {
243
+ action = JSON_ACTION_END_STRUCT;
244
+ } else if (c == 't' || c == 'f' || c == 'n' || c == '-' ||
245
+ (c >= '0' && c <= '9')) {
246
+ action = JSON_ACTION_START;
247
+ state = JSON_STATE_LITERAL;
248
+ } else {
249
+ return -1;
250
+ }
251
+ break;
252
+ case JSON_STATE_LITERAL:
253
+ if (c == ' ' || c == '\t' || c == '\n' || c == '\r' || c == ',' ||
254
+ c == ']' || c == '}' || c == ':') {
255
+ state = JSON_STATE_VALUE;
256
+ s--;
257
+ sz++;
258
+ action = JSON_ACTION_END;
259
+ } else if (c < 32 || c > 126) {
260
+ return -1;
261
+ } // fallthrough
262
+ case JSON_STATE_STRING:
263
+ if (c < 32 || (c > 126 && c < 192)) {
264
+ return -1;
265
+ } else if (c == '"') {
266
+ action = JSON_ACTION_END;
267
+ state = JSON_STATE_VALUE;
268
+ } else if (c == '\\') {
269
+ state = JSON_STATE_ESCAPE;
270
+ } else if (c >= 192 && c < 224) {
271
+ utf8_bytes = 1;
272
+ state = JSON_STATE_UTF8;
273
+ } else if (c >= 224 && c < 240) {
274
+ utf8_bytes = 2;
275
+ state = JSON_STATE_UTF8;
276
+ } else if (c >= 240 && c < 247) {
277
+ utf8_bytes = 3;
278
+ state = JSON_STATE_UTF8;
279
+ } else if (c >= 128 && c < 192) {
280
+ return -1;
281
+ }
282
+ break;
283
+ case JSON_STATE_ESCAPE:
284
+ if (c == '"' || c == '\\' || c == '/' || c == 'b' || c == 'f' ||
285
+ c == 'n' || c == 'r' || c == 't' || c == 'u') {
286
+ state = JSON_STATE_STRING;
287
+ } else {
288
+ return -1;
289
+ }
290
+ break;
291
+ case JSON_STATE_UTF8:
292
+ if (c < 128 || c > 191) {
293
+ return -1;
294
+ }
295
+ utf8_bytes--;
296
+ if (utf8_bytes == 0) {
297
+ state = JSON_STATE_STRING;
298
+ }
299
+ break;
300
+ default:
301
+ return -1;
302
+ }
303
+
304
+ if (action == JSON_ACTION_END_STRUCT) {
305
+ depth--;
306
+ }
307
+
308
+ if (depth == 1) {
309
+ if (action == JSON_ACTION_START || action == JSON_ACTION_START_STRUCT) {
310
+ if (index == 0) {
311
+ *value = s;
312
+ } else if (keysz > 0 && index == 1) {
313
+ k = s;
314
+ } else {
315
+ index--;
316
+ }
317
+ } else if (action == JSON_ACTION_END ||
318
+ action == JSON_ACTION_END_STRUCT) {
319
+ if (*value != NULL && index == 0) {
320
+ *valuesz = (size_t)(s + 1 - *value);
321
+ return 0;
322
+ } else if (keysz > 0 && k != NULL) {
323
+ if (keysz == (size_t)(s - k - 1) && memcmp(key, k + 1, keysz) == 0) {
324
+ index = 0;
325
+ } else {
326
+ index = 2;
327
+ }
328
+ k = NULL;
329
+ }
330
+ }
331
+ }
332
+
333
+ if (action == JSON_ACTION_START_STRUCT) {
334
+ depth++;
335
+ }
336
+ }
337
+ return -1;
338
+ }
339
+
340
+ inline std::string json_escape(std::string s) {
341
+ // TODO: implement
342
+ return '"' + s + '"';
343
+ }
344
+
345
+ inline int json_unescape(const char *s, size_t n, char *out) {
346
+ int r = 0;
347
+ if (*s++ != '"') {
348
+ return -1;
349
+ }
350
+ while (n > 2) {
351
+ char c = *s;
352
+ if (c == '\\') {
353
+ s++;
354
+ n--;
355
+ switch (*s) {
356
+ case 'b':
357
+ c = '\b';
358
+ break;
359
+ case 'f':
360
+ c = '\f';
361
+ break;
362
+ case 'n':
363
+ c = '\n';
364
+ break;
365
+ case 'r':
366
+ c = '\r';
367
+ break;
368
+ case 't':
369
+ c = '\t';
370
+ break;
371
+ case '\\':
372
+ c = '\\';
373
+ break;
374
+ case '/':
375
+ c = '/';
376
+ break;
377
+ case '\"':
378
+ c = '\"';
379
+ break;
380
+ default: // TODO: support unicode decoding
381
+ return -1;
382
+ }
383
+ }
384
+ if (out != NULL) {
385
+ *out++ = c;
386
+ }
387
+ s++;
388
+ n--;
389
+ r++;
390
+ }
391
+ if (*s != '"') {
392
+ return -1;
393
+ }
394
+ if (out != NULL) {
395
+ *out = '\0';
396
+ }
397
+ return r;
398
+ }
399
+
400
+ inline std::string json_parse(const std::string s, const std::string key,
401
+ const int index) {
402
+ const char *value;
403
+ size_t value_sz;
404
+ if (key == "") {
405
+ json_parse_c(s.c_str(), s.length(), nullptr, index, &value, &value_sz);
406
+ } else {
407
+ json_parse_c(s.c_str(), s.length(), key.c_str(), key.length(), &value,
408
+ &value_sz);
409
+ }
410
+ if (value != nullptr) {
411
+ if (value[0] != '"') {
412
+ return std::string(value, value_sz);
413
+ }
414
+ int n = json_unescape(value, value_sz, nullptr);
415
+ if (n > 0) {
416
+ char *decoded = new char[n + 1];
417
+ json_unescape(value, value_sz, decoded);
418
+ std::string result(decoded, n);
419
+ delete[] decoded;
420
+ return result;
421
+ }
422
+ }
423
+ return "";
424
+ }
425
+
426
+ } // namespace webview
427
+
428
+ #if defined(WEBVIEW_GTK)
429
+ //
430
+ // ====================================================================
431
+ //
432
+ // This implementation uses webkit2gtk backend. It requires gtk+3.0 and
433
+ // webkit2gtk-4.0 libraries. Proper compiler flags can be retrieved via:
434
+ //
435
+ // pkg-config --cflags --libs gtk+-3.0 webkit2gtk-4.0
436
+ //
437
+ // ====================================================================
438
+ //
439
+ #include <JavaScriptCore/JavaScript.h>
440
+ #include <gtk/gtk.h>
441
+ #include <webkit2/webkit2.h>
442
+
443
+ namespace webview {
444
+
445
+ class gtk_webkit_engine {
446
+ public:
447
+ gtk_webkit_engine(bool debug, void *window)
448
+ : m_window(static_cast<GtkWidget *>(window)) {
449
+ gtk_init_check(0, NULL);
450
+ m_window = static_cast<GtkWidget *>(window);
451
+ if (m_window == nullptr) {
452
+ m_window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
453
+ }
454
+ g_signal_connect(G_OBJECT(m_window), "destroy",
455
+ G_CALLBACK(+[](GtkWidget *, gpointer arg) {
456
+ static_cast<gtk_webkit_engine *>(arg)->terminate();
457
+ }),
458
+ this);
459
+ // Initialize webview widget
460
+ m_webview = webkit_web_view_new();
461
+ WebKitUserContentManager *manager =
462
+ webkit_web_view_get_user_content_manager(WEBKIT_WEB_VIEW(m_webview));
463
+ g_signal_connect(manager, "script-message-received::external",
464
+ G_CALLBACK(+[](WebKitUserContentManager *,
465
+ WebKitJavascriptResult *r, gpointer arg) {
466
+ auto *w = static_cast<gtk_webkit_engine *>(arg);
467
+ #if WEBKIT_MAJOR_VERSION >= 2 && WEBKIT_MINOR_VERSION >= 22
468
+ JSCValue *value =
469
+ webkit_javascript_result_get_js_value(r);
470
+ char *s = jsc_value_to_string(value);
471
+ #else
472
+ JSGlobalContextRef ctx =
473
+ webkit_javascript_result_get_global_context(r);
474
+ JSValueRef value = webkit_javascript_result_get_value(r);
475
+ JSStringRef js = JSValueToStringCopy(ctx, value, NULL);
476
+ size_t n = JSStringGetMaximumUTF8CStringSize(js);
477
+ char *s = g_new(char, n);
478
+ JSStringGetUTF8CString(js, s, n);
479
+ JSStringRelease(js);
480
+ #endif
481
+ w->on_message(s);
482
+ g_free(s);
483
+ }),
484
+ this);
485
+ webkit_user_content_manager_register_script_message_handler(manager,
486
+ "external");
487
+ init("window.external={invoke:function(s){window.webkit.messageHandlers."
488
+ "external.postMessage(s);}}");
489
+
490
+ gtk_container_add(GTK_CONTAINER(m_window), GTK_WIDGET(m_webview));
491
+ gtk_widget_grab_focus(GTK_WIDGET(m_webview));
492
+
493
+ WebKitSettings *settings =
494
+ webkit_web_view_get_settings(WEBKIT_WEB_VIEW(m_webview));
495
+ webkit_settings_set_javascript_can_access_clipboard(settings, true);
496
+ if (debug) {
497
+ webkit_settings_set_enable_write_console_messages_to_stdout(settings,
498
+ true);
499
+ webkit_settings_set_enable_developer_extras(settings, true);
500
+ }
501
+
502
+ gtk_widget_show_all(m_window);
503
+ }
504
+ void *window() { return (void *)m_window; }
505
+ void run() { gtk_main(); }
506
+ void terminate() { gtk_main_quit(); }
507
+ void dispatch(std::function<void()> f) {
508
+ g_idle_add_full(G_PRIORITY_HIGH_IDLE, (GSourceFunc)([](void *f) -> int {
509
+ (*static_cast<dispatch_fn_t *>(f))();
510
+ return G_SOURCE_REMOVE;
511
+ }),
512
+ new std::function<void()>(f),
513
+ [](void *f) { delete static_cast<dispatch_fn_t *>(f); });
514
+ }
515
+
516
+ void set_title(const std::string title) {
517
+ gtk_window_set_title(GTK_WINDOW(m_window), title.c_str());
518
+ }
519
+
520
+ void set_size(int width, int height, int hints) {
521
+ gtk_window_set_resizable(GTK_WINDOW(m_window), hints != WEBVIEW_HINT_FIXED);
522
+ if (hints == WEBVIEW_HINT_NONE) {
523
+ gtk_window_resize(GTK_WINDOW(m_window), width, height);
524
+ } else if (hints == WEBVIEW_HINT_FIXED) {
525
+ gtk_widget_set_size_request(m_window, width, height);
526
+ } else {
527
+ GdkGeometry g;
528
+ g.min_width = g.max_width = width;
529
+ g.min_height = g.max_height = height;
530
+ GdkWindowHints h =
531
+ (hints == WEBVIEW_HINT_MIN ? GDK_HINT_MIN_SIZE : GDK_HINT_MAX_SIZE);
532
+ // This defines either MIN_SIZE, or MAX_SIZE, but not both:
533
+ gtk_window_set_geometry_hints(GTK_WINDOW(m_window), nullptr, &g, h);
534
+ }
535
+ }
536
+
537
+ void navigate(const std::string url) {
538
+ webkit_web_view_load_uri(WEBKIT_WEB_VIEW(m_webview), url.c_str());
539
+ }
540
+
541
+ void init(const std::string js) {
542
+ WebKitUserContentManager *manager =
543
+ webkit_web_view_get_user_content_manager(WEBKIT_WEB_VIEW(m_webview));
544
+ webkit_user_content_manager_add_script(
545
+ manager, webkit_user_script_new(
546
+ js.c_str(), WEBKIT_USER_CONTENT_INJECT_TOP_FRAME,
547
+ WEBKIT_USER_SCRIPT_INJECT_AT_DOCUMENT_START, NULL, NULL));
548
+ }
549
+
550
+ void eval(const std::string js) {
551
+ webkit_web_view_run_javascript(WEBKIT_WEB_VIEW(m_webview), js.c_str(), NULL,
552
+ NULL, NULL);
553
+ }
554
+
555
+ private:
556
+ virtual void on_message(const std::string msg) = 0;
557
+ GtkWidget *m_window;
558
+ GtkWidget *m_webview;
559
+ };
560
+
561
+ using browser_engine = gtk_webkit_engine;
562
+
563
+ } // namespace webview
564
+
565
+ #elif defined(WEBVIEW_COCOA)
566
+
567
+ //
568
+ // ====================================================================
569
+ //
570
+ // This implementation uses Cocoa WKWebView backend on macOS. It is
571
+ // written using ObjC runtime and uses WKWebView class as a browser runtime.
572
+ // You should pass "-framework Webkit" flag to the compiler.
573
+ //
574
+ // ====================================================================
575
+ //
576
+
577
+ #include <CoreGraphics/CoreGraphics.h>
578
+ #include <objc/objc-runtime.h>
579
+
580
+ #define NSBackingStoreBuffered 2
581
+
582
+ #define NSWindowStyleMaskResizable 8
583
+ #define NSWindowStyleMaskMiniaturizable 4
584
+ #define NSWindowStyleMaskTitled 1
585
+ #define NSWindowStyleMaskClosable 2
586
+ #define NSWindowStyleMaskFullSizeContentView (1 << 15)
587
+
588
+ #define NSWindowTitleHidden 1
589
+
590
+ #define NSApplicationActivationPolicyRegular 0
591
+
592
+ #define WKUserScriptInjectionTimeAtDocumentStart 0
593
+
594
+ namespace webview {
595
+
596
+ // Helpers to avoid too much typing
597
+ id operator"" _cls(const char *s, std::size_t) { return (id)objc_getClass(s); }
598
+ SEL operator"" _sel(const char *s, std::size_t) { return sel_registerName(s); }
599
+ id operator"" _str(const char *s, std::size_t) {
600
+ return ((id(*)(id, SEL, const char *))objc_msgSend)(
601
+ "NSString"_cls, "stringWithUTF8String:"_sel, s);
602
+ }
603
+
604
+ class cocoa_wkwebview_engine {
605
+ public:
606
+ cocoa_wkwebview_engine(bool debug, void *window) {
607
+ // Application
608
+ id app = ((id(*)(id, SEL))objc_msgSend)("NSApplication"_cls,
609
+ "sharedApplication"_sel);
610
+ ((void (*)(id, SEL, long))objc_msgSend)(
611
+ app, "setActivationPolicy:"_sel, NSApplicationActivationPolicyRegular);
612
+
613
+ // Delegate
614
+ auto cls =
615
+ objc_allocateClassPair((Class) "NSResponder"_cls, "AppDelegate", 0);
616
+ class_addProtocol(cls, objc_getProtocol("NSTouchBarProvider"));
617
+ class_addMethod(cls, "applicationShouldTerminateAfterLastWindowClosed:"_sel,
618
+ (IMP)(+[](id, SEL, id) -> BOOL { return 1; }), "c@:@");
619
+ class_addMethod(cls, "userContentController:didReceiveScriptMessage:"_sel,
620
+ (IMP)(+[](id self, SEL, id, id msg) {
621
+ auto w =
622
+ (cocoa_wkwebview_engine *)objc_getAssociatedObject(
623
+ self, "webview");
624
+ assert(w);
625
+ w->on_message(((const char *(*)(id, SEL))objc_msgSend)(
626
+ ((id(*)(id, SEL))objc_msgSend)(msg, "body"_sel),
627
+ "UTF8String"_sel));
628
+ }),
629
+ "v@:@@");
630
+ objc_registerClassPair(cls);
631
+
632
+ auto delegate = ((id(*)(id, SEL))objc_msgSend)((id)cls, "new"_sel);
633
+ objc_setAssociatedObject(delegate, "webview", (id)this,
634
+ OBJC_ASSOCIATION_ASSIGN);
635
+ ((void (*)(id, SEL, id))objc_msgSend)(app, sel_registerName("setDelegate:"),
636
+ delegate);
637
+
638
+ // Main window
639
+ if (window == nullptr) {
640
+ m_window = ((id(*)(id, SEL))objc_msgSend)("NSWindow"_cls, "alloc"_sel);
641
+ m_window =
642
+ ((id(*)(id, SEL, CGRect, int, unsigned long, int))objc_msgSend)(
643
+ m_window, "initWithContentRect:styleMask:backing:defer:"_sel,
644
+ CGRectMake(0, 0, 0, 0), 0, NSBackingStoreBuffered, 0);
645
+ } else {
646
+ m_window = (id)window;
647
+ }
648
+
649
+ // Webview
650
+ auto config =
651
+ ((id(*)(id, SEL))objc_msgSend)("WKWebViewConfiguration"_cls, "new"_sel);
652
+ m_manager =
653
+ ((id(*)(id, SEL))objc_msgSend)(config, "userContentController"_sel);
654
+ m_webview = ((id(*)(id, SEL))objc_msgSend)("WKWebView"_cls, "alloc"_sel);
655
+
656
+ if (debug) {
657
+ // Equivalent Obj-C:
658
+ // [[config preferences] setValue:@YES forKey:@"developerExtrasEnabled"];
659
+ ((id(*)(id, SEL, id, id))objc_msgSend)(
660
+ ((id(*)(id, SEL))objc_msgSend)(config, "preferences"_sel),
661
+ "setValue:forKey:"_sel,
662
+ ((id(*)(id, SEL, BOOL))objc_msgSend)("NSNumber"_cls,
663
+ "numberWithBool:"_sel, 1),
664
+ "developerExtrasEnabled"_str);
665
+ }
666
+
667
+ // Equivalent Obj-C:
668
+ // [[config preferences] setValue:@YES forKey:@"fullScreenEnabled"];
669
+ ((id(*)(id, SEL, id, id))objc_msgSend)(
670
+ ((id(*)(id, SEL))objc_msgSend)(config, "preferences"_sel),
671
+ "setValue:forKey:"_sel,
672
+ ((id(*)(id, SEL, BOOL))objc_msgSend)("NSNumber"_cls,
673
+ "numberWithBool:"_sel, 1),
674
+ "fullScreenEnabled"_str);
675
+
676
+ // Equivalent Obj-C:
677
+ // [[config preferences] setValue:@YES forKey:@"javaScriptCanAccessClipboard"];
678
+ ((id(*)(id, SEL, id, id))objc_msgSend)(
679
+ ((id(*)(id, SEL))objc_msgSend)(config, "preferences"_sel),
680
+ "setValue:forKey:"_sel,
681
+ ((id(*)(id, SEL, BOOL))objc_msgSend)("NSNumber"_cls,
682
+ "numberWithBool:"_sel, 1),
683
+ "javaScriptCanAccessClipboard"_str);
684
+
685
+ // Equivalent Obj-C:
686
+ // [[config preferences] setValue:@YES forKey:@"DOMPasteAllowed"];
687
+ ((id(*)(id, SEL, id, id))objc_msgSend)(
688
+ ((id(*)(id, SEL))objc_msgSend)(config, "preferences"_sel),
689
+ "setValue:forKey:"_sel,
690
+ ((id(*)(id, SEL, BOOL))objc_msgSend)("NSNumber"_cls,
691
+ "numberWithBool:"_sel, 1),
692
+ "DOMPasteAllowed"_str);
693
+
694
+ ((void (*)(id, SEL, CGRect, id))objc_msgSend)(
695
+ m_webview, "initWithFrame:configuration:"_sel, CGRectMake(0, 0, 0, 0),
696
+ config);
697
+ ((void (*)(id, SEL, id, id))objc_msgSend)(
698
+ m_manager, "addScriptMessageHandler:name:"_sel, delegate,
699
+ "external"_str);
700
+
701
+ init(R"script(
702
+ window.external = {
703
+ invoke: function(s) {
704
+ window.webkit.messageHandlers.external.postMessage(s);
705
+ },
706
+ };
707
+ )script");
708
+ ((void (*)(id, SEL, id))objc_msgSend)(m_window, "setContentView:"_sel,
709
+ m_webview);
710
+ ((void (*)(id, SEL, id))objc_msgSend)(m_window, "makeKeyAndOrderFront:"_sel,
711
+ nullptr);
712
+ }
713
+ ~cocoa_wkwebview_engine() { close(); }
714
+ void *window() { return (void *)m_window; }
715
+ void terminate() {
716
+ close();
717
+ ((void (*)(id, SEL, id))objc_msgSend)("NSApp"_cls, "terminate:"_sel,
718
+ nullptr);
719
+ }
720
+ void run() {
721
+ id app = ((id(*)(id, SEL))objc_msgSend)("NSApplication"_cls,
722
+ "sharedApplication"_sel);
723
+ dispatch([&]() {
724
+ ((void (*)(id, SEL, BOOL))objc_msgSend)(
725
+ app, "activateIgnoringOtherApps:"_sel, 1);
726
+ });
727
+ ((void (*)(id, SEL))objc_msgSend)(app, "run"_sel);
728
+ }
729
+ void dispatch(std::function<void()> f) {
730
+ dispatch_async_f(dispatch_get_main_queue(), new dispatch_fn_t(f),
731
+ (dispatch_function_t)([](void *arg) {
732
+ auto f = static_cast<dispatch_fn_t *>(arg);
733
+ (*f)();
734
+ delete f;
735
+ }));
736
+ }
737
+ void set_title(const std::string title) {
738
+ ((void (*)(id, SEL, id))objc_msgSend)(
739
+ m_window, "setTitle:"_sel,
740
+ ((id(*)(id, SEL, const char *))objc_msgSend)(
741
+ "NSString"_cls, "stringWithUTF8String:"_sel, title.c_str()));
742
+ }
743
+ void set_size(int width, int height, int hints) {
744
+ auto style = NSWindowStyleMaskTitled | NSWindowStyleMaskClosable |
745
+ NSWindowStyleMaskMiniaturizable | NSWindowStyleMaskResizable | NSWindowStyleMaskFullSizeContentView;
746
+
747
+
748
+ ((void (*)(id, SEL, unsigned long))objc_msgSend)(
749
+ m_window, "setTitleVisibility:"_sel, NSWindowTitleHidden);
750
+
751
+ ((void (*)(id, SEL, unsigned long))objc_msgSend)(
752
+ m_window, "setTitlebarAppearsTransparent:"_sel, 1);
753
+
754
+ if (hints != WEBVIEW_HINT_FIXED) {
755
+ style = style | NSWindowStyleMaskResizable;
756
+ }
757
+ ((void (*)(id, SEL, unsigned long))objc_msgSend)(
758
+ m_window, "setStyleMask:"_sel, style);
759
+
760
+ if (hints == WEBVIEW_HINT_MIN) {
761
+ ((void (*)(id, SEL, CGSize))objc_msgSend)(
762
+ m_window, "setContentMinSize:"_sel, CGSizeMake(width, height));
763
+ } else if (hints == WEBVIEW_HINT_MAX) {
764
+ ((void (*)(id, SEL, CGSize))objc_msgSend)(
765
+ m_window, "setContentMaxSize:"_sel, CGSizeMake(width, height));
766
+ } else {
767
+ ((void (*)(id, SEL, CGRect, BOOL, BOOL))objc_msgSend)(
768
+ m_window, "setFrame:display:animate:"_sel,
769
+ CGRectMake(0, 0, width, height), 1, 0);
770
+ }
771
+ ((void (*)(id, SEL))objc_msgSend)(m_window, "center"_sel);
772
+ }
773
+ void navigate(const std::string url) {
774
+ auto nsurl = ((id(*)(id, SEL, id))objc_msgSend)(
775
+ "NSURL"_cls, "URLWithString:"_sel,
776
+ ((id(*)(id, SEL, const char *))objc_msgSend)(
777
+ "NSString"_cls, "stringWithUTF8String:"_sel, url.c_str()));
778
+
779
+ ((void (*)(id, SEL, id))objc_msgSend)(
780
+ m_webview, "loadRequest:"_sel,
781
+ ((id(*)(id, SEL, id))objc_msgSend)("NSURLRequest"_cls,
782
+ "requestWithURL:"_sel, nsurl));
783
+ }
784
+ void init(const std::string js) {
785
+ // Equivalent Obj-C:
786
+ // [m_manager addUserScript:[[WKUserScript alloc] initWithSource:[NSString stringWithUTF8String:js.c_str()] injectionTime:WKUserScriptInjectionTimeAtDocumentStart forMainFrameOnly:YES]]
787
+ ((void (*)(id, SEL, id))objc_msgSend)(
788
+ m_manager, "addUserScript:"_sel,
789
+ ((id(*)(id, SEL, id, long, BOOL))objc_msgSend)(
790
+ ((id(*)(id, SEL))objc_msgSend)("WKUserScript"_cls, "alloc"_sel),
791
+ "initWithSource:injectionTime:forMainFrameOnly:"_sel,
792
+ ((id(*)(id, SEL, const char *))objc_msgSend)(
793
+ "NSString"_cls, "stringWithUTF8String:"_sel, js.c_str()),
794
+ WKUserScriptInjectionTimeAtDocumentStart, 1));
795
+ }
796
+ void eval(const std::string js) {
797
+ ((void (*)(id, SEL, id, id))objc_msgSend)(
798
+ m_webview, "evaluateJavaScript:completionHandler:"_sel,
799
+ ((id(*)(id, SEL, const char *))objc_msgSend)(
800
+ "NSString"_cls, "stringWithUTF8String:"_sel, js.c_str()),
801
+ nullptr);
802
+ }
803
+
804
+ private:
805
+ virtual void on_message(const std::string msg) = 0;
806
+ void close() { ((void (*)(id, SEL))objc_msgSend)(m_window, "close"_sel); }
807
+ id m_window;
808
+ id m_webview;
809
+ id m_manager;
810
+ };
811
+
812
+ using browser_engine = cocoa_wkwebview_engine;
813
+
814
+ } // namespace webview
815
+
816
+ #elif defined(WEBVIEW_EDGE)
817
+
818
+ //
819
+ // ====================================================================
820
+ //
821
+ // This implementation uses Win32 API to create a native window. It can
822
+ // use either EdgeHTML or Edge/Chromium backend as a browser engine.
823
+ //
824
+ // ====================================================================
825
+ //
826
+
827
+ #define WIN32_LEAN_AND_MEAN
828
+ #include <Shlwapi.h>
829
+ #include <codecvt>
830
+ #include <stdlib.h>
831
+ #include <windows.h>
832
+
833
+ #pragma comment(lib, "user32.lib")
834
+ #pragma comment(lib, "Shlwapi.lib")
835
+
836
+ // EdgeHTML headers and libs
837
+ #include <objbase.h>
838
+ #include <winrt/Windows.Foundation.Collections.h>
839
+ #include <winrt/Windows.Foundation.h>
840
+ #include <winrt/Windows.Web.UI.Interop.h>
841
+ #pragma comment(lib, "windowsapp")
842
+
843
+ // Edge/Chromium headers and libs
844
+ #include "webview2.h"
845
+ #pragma comment(lib, "ole32.lib")
846
+ #pragma comment(lib, "oleaut32.lib")
847
+
848
+ namespace webview {
849
+
850
+ using msg_cb_t = std::function<void(const std::string)>;
851
+
852
+ // Common interface for EdgeHTML and Edge/Chromium
853
+ class browser {
854
+ public:
855
+ virtual ~browser() = default;
856
+ virtual bool embed(HWND, bool, msg_cb_t) = 0;
857
+ virtual void navigate(const std::string url) = 0;
858
+ virtual void eval(const std::string js) = 0;
859
+ virtual void init(const std::string js) = 0;
860
+ virtual void resize(HWND) = 0;
861
+ };
862
+
863
+ //
864
+ // EdgeHTML browser engine
865
+ //
866
+ using namespace winrt;
867
+ using namespace Windows::Foundation;
868
+ using namespace Windows::Web::UI;
869
+ using namespace Windows::Web::UI::Interop;
870
+
871
+ class edge_html : public browser {
872
+ public:
873
+ bool embed(HWND wnd, bool debug, msg_cb_t cb) override {
874
+ init_apartment(winrt::apartment_type::single_threaded);
875
+ auto process = WebViewControlProcess();
876
+ auto op = process.CreateWebViewControlAsync(reinterpret_cast<int64_t>(wnd),
877
+ Rect());
878
+ if (op.Status() != AsyncStatus::Completed) {
879
+ handle h(CreateEvent(nullptr, false, false, nullptr));
880
+ op.Completed([h = h.get()](auto, auto) { SetEvent(h); });
881
+ HANDLE hs[] = {h.get()};
882
+ DWORD i;
883
+ CoWaitForMultipleHandles(COWAIT_DISPATCH_WINDOW_MESSAGES |
884
+ COWAIT_DISPATCH_CALLS |
885
+ COWAIT_INPUTAVAILABLE,
886
+ INFINITE, 1, hs, &i);
887
+ }
888
+ m_webview = op.GetResults();
889
+ m_webview.Settings().IsScriptNotifyAllowed(true);
890
+ m_webview.IsVisible(true);
891
+ m_webview.ScriptNotify([=](auto const &sender, auto const &args) {
892
+ std::string s = winrt::to_string(args.Value());
893
+ cb(s.c_str());
894
+ });
895
+ m_webview.NavigationStarting([=](auto const &sender, auto const &args) {
896
+ m_webview.AddInitializeScript(winrt::to_hstring(init_js));
897
+ });
898
+ init("window.external.invoke = s => window.external.notify(s)");
899
+ return true;
900
+ }
901
+
902
+ void navigate(const std::string url) override {
903
+ std::string html = html_from_uri(url);
904
+ if (html != "") {
905
+ m_webview.NavigateToString(winrt::to_hstring(html));
906
+ } else {
907
+ Uri uri(winrt::to_hstring(url));
908
+ m_webview.Navigate(uri);
909
+ }
910
+ }
911
+
912
+ void init(const std::string js) override {
913
+ init_js = init_js + "(function(){" + js + "})();";
914
+ }
915
+
916
+ void eval(const std::string js) override {
917
+ m_webview.InvokeScriptAsync(
918
+ L"eval", single_threaded_vector<hstring>({winrt::to_hstring(js)}));
919
+ }
920
+
921
+ void resize(HWND wnd) override {
922
+ if (m_webview == nullptr) {
923
+ return;
924
+ }
925
+ RECT r;
926
+ GetClientRect(wnd, &r);
927
+ Rect bounds(r.left, r.top, r.right - r.left, r.bottom - r.top);
928
+ m_webview.Bounds(bounds);
929
+ }
930
+
931
+ private:
932
+ WebViewControl m_webview = nullptr;
933
+ std::string init_js = "";
934
+ };
935
+
936
+ //
937
+ // Edge/Chromium browser engine
938
+ //
939
+ class edge_chromium : public browser {
940
+ public:
941
+ bool embed(HWND wnd, bool debug, msg_cb_t cb) override {
942
+ CoInitializeEx(nullptr, 0);
943
+ std::atomic_flag flag = ATOMIC_FLAG_INIT;
944
+ flag.test_and_set();
945
+
946
+ char currentExePath[MAX_PATH];
947
+ GetModuleFileNameA(NULL, currentExePath, MAX_PATH);
948
+ char *currentExeName = PathFindFileNameA(currentExePath);
949
+
950
+ std::wstring_convert<std::codecvt_utf8_utf16<wchar_t>> wideCharConverter;
951
+ std::wstring userDataFolder =
952
+ wideCharConverter.from_bytes(std::getenv("APPDATA"));
953
+ std::wstring currentExeNameW = wideCharConverter.from_bytes(currentExeName);
954
+
955
+ HRESULT res = CreateCoreWebView2EnvironmentWithOptions(
956
+ nullptr, (userDataFolder + L"/" + currentExeNameW).c_str(), nullptr,
957
+ new webview2_com_handler(wnd, cb,
958
+ [&](ICoreWebView2Controller *controller) {
959
+ m_controller = controller;
960
+ m_controller->get_CoreWebView2(&m_webview);
961
+ m_webview->AddRef();
962
+ flag.clear();
963
+ }));
964
+ if (res != S_OK) {
965
+ CoUninitialize();
966
+ return false;
967
+ }
968
+ MSG msg = {};
969
+ while (flag.test_and_set() && GetMessage(&msg, NULL, 0, 0)) {
970
+ TranslateMessage(&msg);
971
+ DispatchMessage(&msg);
972
+ }
973
+ init("window.external={invoke:s=>window.chrome.webview.postMessage(s)}");
974
+ return true;
975
+ }
976
+
977
+ void resize(HWND wnd) override {
978
+ if (m_controller == nullptr) {
979
+ return;
980
+ }
981
+ RECT bounds;
982
+ GetClientRect(wnd, &bounds);
983
+ m_controller->put_Bounds(bounds);
984
+ }
985
+
986
+ void navigate(const std::string url) override {
987
+ auto wurl = to_lpwstr(url);
988
+ m_webview->Navigate(wurl);
989
+ delete[] wurl;
990
+ }
991
+
992
+ void init(const std::string js) override {
993
+ LPCWSTR wjs = to_lpwstr(js);
994
+ m_webview->AddScriptToExecuteOnDocumentCreated(wjs, nullptr);
995
+ delete[] wjs;
996
+ }
997
+
998
+ void eval(const std::string js) override {
999
+ LPCWSTR wjs = to_lpwstr(js);
1000
+ m_webview->ExecuteScript(wjs, nullptr);
1001
+ delete[] wjs;
1002
+ }
1003
+
1004
+ private:
1005
+ LPWSTR to_lpwstr(const std::string s) {
1006
+ int n = MultiByteToWideChar(CP_UTF8, 0, s.c_str(), -1, NULL, 0);
1007
+ wchar_t *ws = new wchar_t[n];
1008
+ MultiByteToWideChar(CP_UTF8, 0, s.c_str(), -1, ws, n);
1009
+ return ws;
1010
+ }
1011
+
1012
+ ICoreWebView2 *m_webview = nullptr;
1013
+ ICoreWebView2Controller *m_controller = nullptr;
1014
+
1015
+ class webview2_com_handler
1016
+ : public ICoreWebView2CreateCoreWebView2EnvironmentCompletedHandler,
1017
+ public ICoreWebView2CreateCoreWebView2ControllerCompletedHandler,
1018
+ public ICoreWebView2WebMessageReceivedEventHandler,
1019
+ public ICoreWebView2PermissionRequestedEventHandler {
1020
+ using webview2_com_handler_cb_t =
1021
+ std::function<void(ICoreWebView2Controller *)>;
1022
+
1023
+ public:
1024
+ webview2_com_handler(HWND hwnd, msg_cb_t msgCb,
1025
+ webview2_com_handler_cb_t cb)
1026
+ : m_window(hwnd), m_msgCb(msgCb), m_cb(cb) {}
1027
+ ULONG STDMETHODCALLTYPE AddRef() { return 1; }
1028
+ ULONG STDMETHODCALLTYPE Release() { return 1; }
1029
+ HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, LPVOID *ppv) {
1030
+ return S_OK;
1031
+ }
1032
+ HRESULT STDMETHODCALLTYPE Invoke(HRESULT res,
1033
+ ICoreWebView2Environment *env) {
1034
+ env->CreateCoreWebView2Controller(m_window, this);
1035
+ return S_OK;
1036
+ }
1037
+ HRESULT STDMETHODCALLTYPE Invoke(HRESULT res,
1038
+ ICoreWebView2Controller *controller) {
1039
+ controller->AddRef();
1040
+
1041
+ ICoreWebView2 *webview;
1042
+ ::EventRegistrationToken token;
1043
+ controller->get_CoreWebView2(&webview);
1044
+ webview->add_WebMessageReceived(this, &token);
1045
+ webview->add_PermissionRequested(this, &token);
1046
+
1047
+ m_cb(controller);
1048
+ return S_OK;
1049
+ }
1050
+ HRESULT STDMETHODCALLTYPE Invoke(
1051
+ ICoreWebView2 *sender, ICoreWebView2WebMessageReceivedEventArgs *args) {
1052
+ LPWSTR message;
1053
+ args->TryGetWebMessageAsString(&message);
1054
+
1055
+ std::wstring_convert<std::codecvt_utf8_utf16<wchar_t>> wideCharConverter;
1056
+ m_msgCb(wideCharConverter.to_bytes(message));
1057
+ sender->PostWebMessageAsString(message);
1058
+
1059
+ CoTaskMemFree(message);
1060
+ return S_OK;
1061
+ }
1062
+ HRESULT STDMETHODCALLTYPE
1063
+ Invoke(ICoreWebView2 *sender,
1064
+ ICoreWebView2PermissionRequestedEventArgs *args) {
1065
+ COREWEBVIEW2_PERMISSION_KIND kind;
1066
+ args->get_PermissionKind(&kind);
1067
+ if (kind == COREWEBVIEW2_PERMISSION_KIND_CLIPBOARD_READ) {
1068
+ args->put_State(COREWEBVIEW2_PERMISSION_STATE_ALLOW);
1069
+ }
1070
+ return S_OK;
1071
+ }
1072
+
1073
+ private:
1074
+ HWND m_window;
1075
+ msg_cb_t m_msgCb;
1076
+ webview2_com_handler_cb_t m_cb;
1077
+ };
1078
+ };
1079
+
1080
+ class win32_edge_engine {
1081
+ public:
1082
+ win32_edge_engine(bool debug, void *window) {
1083
+ if (window == nullptr) {
1084
+ HINSTANCE hInstance = GetModuleHandle(nullptr);
1085
+ HICON icon = (HICON)LoadImage(
1086
+ hInstance, IDI_APPLICATION, IMAGE_ICON, GetSystemMetrics(SM_CXSMICON),
1087
+ GetSystemMetrics(SM_CYSMICON), LR_DEFAULTCOLOR);
1088
+
1089
+ WNDCLASSEX wc;
1090
+ ZeroMemory(&wc, sizeof(WNDCLASSEX));
1091
+ wc.cbSize = sizeof(WNDCLASSEX);
1092
+ wc.hInstance = hInstance;
1093
+ wc.lpszClassName = "webview";
1094
+ wc.hIcon = icon;
1095
+ wc.hIconSm = icon;
1096
+ wc.lpfnWndProc =
1097
+ (WNDPROC)(+[](HWND hwnd, UINT msg, WPARAM wp, LPARAM lp) -> int {
1098
+ auto w = (win32_edge_engine *)GetWindowLongPtr(hwnd, GWLP_USERDATA);
1099
+ switch (msg) {
1100
+ case WM_SIZE:
1101
+ w->m_browser->resize(hwnd);
1102
+ break;
1103
+ case WM_CLOSE:
1104
+ DestroyWindow(hwnd);
1105
+ break;
1106
+ case WM_DESTROY:
1107
+ w->terminate();
1108
+ break;
1109
+ case WM_GETMINMAXINFO: {
1110
+ auto lpmmi = (LPMINMAXINFO)lp;
1111
+ if (w == nullptr) {
1112
+ return 0;
1113
+ }
1114
+ if (w->m_maxsz.x > 0 && w->m_maxsz.y > 0) {
1115
+ lpmmi->ptMaxSize = w->m_maxsz;
1116
+ lpmmi->ptMaxTrackSize = w->m_maxsz;
1117
+ }
1118
+ if (w->m_minsz.x > 0 && w->m_minsz.y > 0) {
1119
+ lpmmi->ptMinTrackSize = w->m_minsz;
1120
+ }
1121
+ } break;
1122
+ default:
1123
+ return DefWindowProc(hwnd, msg, wp, lp);
1124
+ }
1125
+ return 0;
1126
+ });
1127
+ RegisterClassEx(&wc);
1128
+ m_window = CreateWindow("webview", "", WS_OVERLAPPEDWINDOW, CW_USEDEFAULT,
1129
+ CW_USEDEFAULT, 640, 480, nullptr, nullptr,
1130
+ GetModuleHandle(nullptr), nullptr);
1131
+ SetWindowLongPtr(m_window, GWLP_USERDATA, (LONG_PTR)this);
1132
+ } else {
1133
+ m_window = *(static_cast<HWND *>(window));
1134
+ }
1135
+
1136
+ SetProcessDpiAwarenessContext(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE);
1137
+ ShowWindow(m_window, SW_SHOW);
1138
+ UpdateWindow(m_window);
1139
+ SetFocus(m_window);
1140
+
1141
+ auto cb =
1142
+ std::bind(&win32_edge_engine::on_message, this, std::placeholders::_1);
1143
+
1144
+ if (!m_browser->embed(m_window, debug, cb)) {
1145
+ m_browser = std::make_unique<webview::edge_html>();
1146
+ m_browser->embed(m_window, debug, cb);
1147
+ }
1148
+
1149
+ m_browser->resize(m_window);
1150
+ }
1151
+
1152
+ void run() {
1153
+ MSG msg;
1154
+ BOOL res;
1155
+ while ((res = GetMessage(&msg, nullptr, 0, 0)) != -1) {
1156
+ if (msg.hwnd) {
1157
+ TranslateMessage(&msg);
1158
+ DispatchMessage(&msg);
1159
+ continue;
1160
+ }
1161
+ if (msg.message == WM_APP) {
1162
+ auto f = (dispatch_fn_t *)(msg.lParam);
1163
+ (*f)();
1164
+ delete f;
1165
+ } else if (msg.message == WM_QUIT) {
1166
+ return;
1167
+ }
1168
+ }
1169
+ }
1170
+ void *window() { return (void *)m_window; }
1171
+ void terminate() { PostQuitMessage(0); }
1172
+ void dispatch(dispatch_fn_t f) {
1173
+ PostThreadMessage(m_main_thread, WM_APP, 0, (LPARAM) new dispatch_fn_t(f));
1174
+ }
1175
+
1176
+ void set_title(const std::string title) {
1177
+ SetWindowText(m_window, title.c_str());
1178
+ }
1179
+
1180
+ void set_size(int width, int height, int hints) {
1181
+ auto style = GetWindowLong(m_window, GWL_STYLE);
1182
+ if (hints == WEBVIEW_HINT_FIXED) {
1183
+ style &= ~(WS_THICKFRAME | WS_MAXIMIZEBOX);
1184
+ } else {
1185
+ style |= (WS_THICKFRAME | WS_MAXIMIZEBOX);
1186
+ }
1187
+ SetWindowLong(m_window, GWL_STYLE, style);
1188
+
1189
+ if (hints == WEBVIEW_HINT_MAX) {
1190
+ m_maxsz.x = width;
1191
+ m_maxsz.y = height;
1192
+ } else if (hints == WEBVIEW_HINT_MIN) {
1193
+ m_minsz.x = width;
1194
+ m_minsz.y = height;
1195
+ } else {
1196
+ RECT r;
1197
+ r.left = r.top = 0;
1198
+ r.right = width;
1199
+ r.bottom = height;
1200
+ AdjustWindowRect(&r, WS_OVERLAPPEDWINDOW, 0);
1201
+ SetWindowPos(
1202
+ m_window, NULL, r.left, r.top, r.right - r.left, r.bottom - r.top,
1203
+ SWP_NOZORDER | SWP_NOACTIVATE | SWP_NOMOVE | SWP_FRAMECHANGED);
1204
+ m_browser->resize(m_window);
1205
+ }
1206
+ }
1207
+
1208
+ void navigate(const std::string url) { m_browser->navigate(url); }
1209
+ void eval(const std::string js) { m_browser->eval(js); }
1210
+ void init(const std::string js) { m_browser->init(js); }
1211
+
1212
+ private:
1213
+ virtual void on_message(const std::string msg) = 0;
1214
+
1215
+ HWND m_window;
1216
+ POINT m_minsz = POINT{0, 0};
1217
+ POINT m_maxsz = POINT{0, 0};
1218
+ DWORD m_main_thread = GetCurrentThreadId();
1219
+ std::unique_ptr<webview::browser> m_browser =
1220
+ std::make_unique<webview::edge_chromium>();
1221
+ };
1222
+
1223
+ using browser_engine = win32_edge_engine;
1224
+ } // namespace webview
1225
+
1226
+ #endif /* WEBVIEW_GTK, WEBVIEW_COCOA, WEBVIEW_EDGE */
1227
+
1228
+ namespace webview {
1229
+
1230
+ class webview : public browser_engine {
1231
+ public:
1232
+ webview(bool debug = false, void *wnd = nullptr)
1233
+ : browser_engine(debug, wnd) {}
1234
+
1235
+ void navigate(const std::string url) {
1236
+ if (url == "") {
1237
+ browser_engine::navigate("data:text/html," +
1238
+ url_encode("<html><body>Hello</body></html>"));
1239
+ return;
1240
+ }
1241
+ std::string html = html_from_uri(url);
1242
+ if (html != "") {
1243
+ browser_engine::navigate("data:text/html," + url_encode(html));
1244
+ } else {
1245
+ browser_engine::navigate(url);
1246
+ }
1247
+ }
1248
+
1249
+ using binding_t = std::function<void(std::string, std::string, void *)>;
1250
+ using binding_ctx_t = std::pair<binding_t *, void *>;
1251
+
1252
+ using sync_binding_t = std::function<std::string(std::string)>;
1253
+ using sync_binding_ctx_t = std::pair<webview *, sync_binding_t>;
1254
+
1255
+ void bind(const std::string name, sync_binding_t fn) {
1256
+ bind(
1257
+ name,
1258
+ [](std::string seq, std::string req, void *arg) {
1259
+ auto pair = static_cast<sync_binding_ctx_t *>(arg);
1260
+ pair->first->resolve(seq, 0, pair->second(req));
1261
+ },
1262
+ new sync_binding_ctx_t(this, fn));
1263
+ }
1264
+
1265
+ void bind(const std::string name, binding_t f, void *arg) {
1266
+ auto js = "(function() { var name = '" + name + "';" + R"(
1267
+ var RPC = window._rpc = (window._rpc || {nextSeq: 1});
1268
+ window[name] = function() {
1269
+ var seq = RPC.nextSeq++;
1270
+ var promise = new Promise(function(resolve, reject) {
1271
+ RPC[seq] = {
1272
+ resolve: resolve,
1273
+ reject: reject,
1274
+ };
1275
+ });
1276
+ window.external.invoke(JSON.stringify({
1277
+ id: seq,
1278
+ method: name,
1279
+ params: Array.prototype.slice.call(arguments),
1280
+ }));
1281
+ return promise;
1282
+ }
1283
+ })())";
1284
+ init(js);
1285
+ bindings[name] = new binding_ctx_t(new binding_t(f), arg);
1286
+ }
1287
+
1288
+ void resolve(const std::string seq, int status, const std::string result) {
1289
+ dispatch([=]() {
1290
+ if (status == 0) {
1291
+ eval("window._rpc[" + seq + "].resolve(" + result + "); window._rpc[" +
1292
+ seq + "] = undefined");
1293
+ } else {
1294
+ eval("window._rpc[" + seq + "].reject(" + result + "); window._rpc[" +
1295
+ seq + "] = undefined");
1296
+ }
1297
+ });
1298
+ }
1299
+
1300
+ private:
1301
+ void on_message(const std::string msg) {
1302
+ auto seq = json_parse(msg, "id", 0);
1303
+ auto name = json_parse(msg, "method", 0);
1304
+ auto args = json_parse(msg, "params", 0);
1305
+ if (bindings.find(name) == bindings.end()) {
1306
+ return;
1307
+ }
1308
+ auto fn = bindings[name];
1309
+ (*fn->first)(seq, args, fn->second);
1310
+ }
1311
+ std::map<std::string, binding_ctx_t *> bindings;
1312
+ };
1313
+ } // namespace webview
1314
+
1315
+ WEBVIEW_API webview_t webview_create(int debug, void *wnd) {
1316
+ return new webview::webview(debug, wnd);
1317
+ }
1318
+
1319
+ WEBVIEW_API void webview_destroy(webview_t w) {
1320
+ delete static_cast<webview::webview *>(w);
1321
+ }
1322
+
1323
+ WEBVIEW_API void webview_run(webview_t w) {
1324
+ static_cast<webview::webview *>(w)->run();
1325
+ }
1326
+
1327
+ WEBVIEW_API void webview_terminate(webview_t w) {
1328
+ static_cast<webview::webview *>(w)->terminate();
1329
+ }
1330
+
1331
+ WEBVIEW_API void webview_dispatch(webview_t w, void (*fn)(webview_t, void *),
1332
+ void *arg) {
1333
+ static_cast<webview::webview *>(w)->dispatch([=]() { fn(w, arg); });
1334
+ }
1335
+
1336
+ WEBVIEW_API void *webview_get_window(webview_t w) {
1337
+ return static_cast<webview::webview *>(w)->window();
1338
+ }
1339
+
1340
+ WEBVIEW_API void webview_set_title(webview_t w, const char *title) {
1341
+ static_cast<webview::webview *>(w)->set_title(title);
1342
+ }
1343
+
1344
+ WEBVIEW_API void webview_set_size(webview_t w, int width, int height,
1345
+ int hints) {
1346
+ static_cast<webview::webview *>(w)->set_size(width, height, hints);
1347
+ }
1348
+
1349
+ WEBVIEW_API void webview_navigate(webview_t w, const char *url) {
1350
+ static_cast<webview::webview *>(w)->navigate(url);
1351
+ }
1352
+
1353
+ WEBVIEW_API void webview_init(webview_t w, const char *js) {
1354
+ static_cast<webview::webview *>(w)->init(js);
1355
+ }
1356
+
1357
+ WEBVIEW_API void webview_eval(webview_t w, const char *js) {
1358
+ static_cast<webview::webview *>(w)->eval(js);
1359
+ }
1360
+
1361
+ WEBVIEW_API void webview_bind(webview_t w, const char *name,
1362
+ void (*fn)(const char *seq, const char *req,
1363
+ void *arg),
1364
+ void *arg) {
1365
+ static_cast<webview::webview *>(w)->bind(
1366
+ name,
1367
+ [=](std::string seq, std::string req, void *arg) {
1368
+ fn(seq.c_str(), req.c_str(), arg);
1369
+ },
1370
+ arg);
1371
+ }
1372
+
1373
+ WEBVIEW_API void webview_return(webview_t w, const char *seq, int status,
1374
+ const char *result) {
1375
+ static_cast<webview::webview *>(w)->resolve(seq, status, result);
1376
+ }
1377
+
1378
+ #endif /* WEBVIEW_HEADER */
1379
+
1380
+ #endif /* WEBVIEW_H */