webview_ruby 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,1369 @@
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
+
587
+ #define NSApplicationActivationPolicyRegular 0
588
+
589
+ #define WKUserScriptInjectionTimeAtDocumentStart 0
590
+
591
+ namespace webview {
592
+
593
+ // Helpers to avoid too much typing
594
+ id operator"" _cls(const char *s, std::size_t) { return (id)objc_getClass(s); }
595
+ SEL operator"" _sel(const char *s, std::size_t) { return sel_registerName(s); }
596
+ id operator"" _str(const char *s, std::size_t) {
597
+ return ((id(*)(id, SEL, const char *))objc_msgSend)(
598
+ "NSString"_cls, "stringWithUTF8String:"_sel, s);
599
+ }
600
+
601
+ class cocoa_wkwebview_engine {
602
+ public:
603
+ cocoa_wkwebview_engine(bool debug, void *window) {
604
+ // Application
605
+ id app = ((id(*)(id, SEL))objc_msgSend)("NSApplication"_cls,
606
+ "sharedApplication"_sel);
607
+ ((void (*)(id, SEL, long))objc_msgSend)(
608
+ app, "setActivationPolicy:"_sel, NSApplicationActivationPolicyRegular);
609
+
610
+ // Delegate
611
+ auto cls =
612
+ objc_allocateClassPair((Class) "NSResponder"_cls, "AppDelegate", 0);
613
+ class_addProtocol(cls, objc_getProtocol("NSTouchBarProvider"));
614
+ class_addMethod(cls, "applicationShouldTerminateAfterLastWindowClosed:"_sel,
615
+ (IMP)(+[](id, SEL, id) -> BOOL { return 1; }), "c@:@");
616
+ class_addMethod(cls, "userContentController:didReceiveScriptMessage:"_sel,
617
+ (IMP)(+[](id self, SEL, id, id msg) {
618
+ auto w =
619
+ (cocoa_wkwebview_engine *)objc_getAssociatedObject(
620
+ self, "webview");
621
+ assert(w);
622
+ w->on_message(((const char *(*)(id, SEL))objc_msgSend)(
623
+ ((id(*)(id, SEL))objc_msgSend)(msg, "body"_sel),
624
+ "UTF8String"_sel));
625
+ }),
626
+ "v@:@@");
627
+ objc_registerClassPair(cls);
628
+
629
+ auto delegate = ((id(*)(id, SEL))objc_msgSend)((id)cls, "new"_sel);
630
+ objc_setAssociatedObject(delegate, "webview", (id)this,
631
+ OBJC_ASSOCIATION_ASSIGN);
632
+ ((void (*)(id, SEL, id))objc_msgSend)(app, sel_registerName("setDelegate:"),
633
+ delegate);
634
+
635
+ // Main window
636
+ if (window == nullptr) {
637
+ m_window = ((id(*)(id, SEL))objc_msgSend)("NSWindow"_cls, "alloc"_sel);
638
+ m_window =
639
+ ((id(*)(id, SEL, CGRect, int, unsigned long, int))objc_msgSend)(
640
+ m_window, "initWithContentRect:styleMask:backing:defer:"_sel,
641
+ CGRectMake(0, 0, 0, 0), 0, NSBackingStoreBuffered, 0);
642
+ } else {
643
+ m_window = (id)window;
644
+ }
645
+
646
+ // Webview
647
+ auto config =
648
+ ((id(*)(id, SEL))objc_msgSend)("WKWebViewConfiguration"_cls, "new"_sel);
649
+ m_manager =
650
+ ((id(*)(id, SEL))objc_msgSend)(config, "userContentController"_sel);
651
+ m_webview = ((id(*)(id, SEL))objc_msgSend)("WKWebView"_cls, "alloc"_sel);
652
+
653
+ if (debug) {
654
+ // Equivalent Obj-C:
655
+ // [[config preferences] setValue:@YES forKey:@"developerExtrasEnabled"];
656
+ ((id(*)(id, SEL, id, id))objc_msgSend)(
657
+ ((id(*)(id, SEL))objc_msgSend)(config, "preferences"_sel),
658
+ "setValue:forKey:"_sel,
659
+ ((id(*)(id, SEL, BOOL))objc_msgSend)("NSNumber"_cls,
660
+ "numberWithBool:"_sel, 1),
661
+ "developerExtrasEnabled"_str);
662
+ }
663
+
664
+ // Equivalent Obj-C:
665
+ // [[config preferences] setValue:@YES forKey:@"fullScreenEnabled"];
666
+ ((id(*)(id, SEL, id, id))objc_msgSend)(
667
+ ((id(*)(id, SEL))objc_msgSend)(config, "preferences"_sel),
668
+ "setValue:forKey:"_sel,
669
+ ((id(*)(id, SEL, BOOL))objc_msgSend)("NSNumber"_cls,
670
+ "numberWithBool:"_sel, 1),
671
+ "fullScreenEnabled"_str);
672
+
673
+ // Equivalent Obj-C:
674
+ // [[config preferences] setValue:@YES forKey:@"javaScriptCanAccessClipboard"];
675
+ ((id(*)(id, SEL, id, id))objc_msgSend)(
676
+ ((id(*)(id, SEL))objc_msgSend)(config, "preferences"_sel),
677
+ "setValue:forKey:"_sel,
678
+ ((id(*)(id, SEL, BOOL))objc_msgSend)("NSNumber"_cls,
679
+ "numberWithBool:"_sel, 1),
680
+ "javaScriptCanAccessClipboard"_str);
681
+
682
+ // Equivalent Obj-C:
683
+ // [[config preferences] setValue:@YES forKey:@"DOMPasteAllowed"];
684
+ ((id(*)(id, SEL, id, id))objc_msgSend)(
685
+ ((id(*)(id, SEL))objc_msgSend)(config, "preferences"_sel),
686
+ "setValue:forKey:"_sel,
687
+ ((id(*)(id, SEL, BOOL))objc_msgSend)("NSNumber"_cls,
688
+ "numberWithBool:"_sel, 1),
689
+ "DOMPasteAllowed"_str);
690
+
691
+ ((void (*)(id, SEL, CGRect, id))objc_msgSend)(
692
+ m_webview, "initWithFrame:configuration:"_sel, CGRectMake(0, 0, 0, 0),
693
+ config);
694
+ ((void (*)(id, SEL, id, id))objc_msgSend)(
695
+ m_manager, "addScriptMessageHandler:name:"_sel, delegate,
696
+ "external"_str);
697
+
698
+ init(R"script(
699
+ window.external = {
700
+ invoke: function(s) {
701
+ window.webkit.messageHandlers.external.postMessage(s);
702
+ },
703
+ };
704
+ )script");
705
+ ((void (*)(id, SEL, id))objc_msgSend)(m_window, "setContentView:"_sel,
706
+ m_webview);
707
+ ((void (*)(id, SEL, id))objc_msgSend)(m_window, "makeKeyAndOrderFront:"_sel,
708
+ nullptr);
709
+ }
710
+ ~cocoa_wkwebview_engine() { close(); }
711
+ void *window() { return (void *)m_window; }
712
+ void terminate() {
713
+ close();
714
+ ((void (*)(id, SEL, id))objc_msgSend)("NSApp"_cls, "terminate:"_sel,
715
+ nullptr);
716
+ }
717
+ void run() {
718
+ id app = ((id(*)(id, SEL))objc_msgSend)("NSApplication"_cls,
719
+ "sharedApplication"_sel);
720
+ dispatch([&]() {
721
+ ((void (*)(id, SEL, BOOL))objc_msgSend)(
722
+ app, "activateIgnoringOtherApps:"_sel, 1);
723
+ });
724
+ ((void (*)(id, SEL))objc_msgSend)(app, "run"_sel);
725
+ }
726
+ void dispatch(std::function<void()> f) {
727
+ dispatch_async_f(dispatch_get_main_queue(), new dispatch_fn_t(f),
728
+ (dispatch_function_t)([](void *arg) {
729
+ auto f = static_cast<dispatch_fn_t *>(arg);
730
+ (*f)();
731
+ delete f;
732
+ }));
733
+ }
734
+ void set_title(const std::string title) {
735
+ ((void (*)(id, SEL, id))objc_msgSend)(
736
+ m_window, "setTitle:"_sel,
737
+ ((id(*)(id, SEL, const char *))objc_msgSend)(
738
+ "NSString"_cls, "stringWithUTF8String:"_sel, title.c_str()));
739
+ }
740
+ void set_size(int width, int height, int hints) {
741
+ auto style = NSWindowStyleMaskTitled | NSWindowStyleMaskClosable |
742
+ NSWindowStyleMaskMiniaturizable;
743
+ if (hints != WEBVIEW_HINT_FIXED) {
744
+ style = style | NSWindowStyleMaskResizable;
745
+ }
746
+ ((void (*)(id, SEL, unsigned long))objc_msgSend)(
747
+ m_window, "setStyleMask:"_sel, style);
748
+
749
+ if (hints == WEBVIEW_HINT_MIN) {
750
+ ((void (*)(id, SEL, CGSize))objc_msgSend)(
751
+ m_window, "setContentMinSize:"_sel, CGSizeMake(width, height));
752
+ } else if (hints == WEBVIEW_HINT_MAX) {
753
+ ((void (*)(id, SEL, CGSize))objc_msgSend)(
754
+ m_window, "setContentMaxSize:"_sel, CGSizeMake(width, height));
755
+ } else {
756
+ ((void (*)(id, SEL, CGRect, BOOL, BOOL))objc_msgSend)(
757
+ m_window, "setFrame:display:animate:"_sel,
758
+ CGRectMake(0, 0, width, height), 1, 0);
759
+ }
760
+ ((void (*)(id, SEL))objc_msgSend)(m_window, "center"_sel);
761
+ }
762
+ void navigate(const std::string url) {
763
+ auto nsurl = ((id(*)(id, SEL, id))objc_msgSend)(
764
+ "NSURL"_cls, "URLWithString:"_sel,
765
+ ((id(*)(id, SEL, const char *))objc_msgSend)(
766
+ "NSString"_cls, "stringWithUTF8String:"_sel, url.c_str()));
767
+
768
+ ((void (*)(id, SEL, id))objc_msgSend)(
769
+ m_webview, "loadRequest:"_sel,
770
+ ((id(*)(id, SEL, id))objc_msgSend)("NSURLRequest"_cls,
771
+ "requestWithURL:"_sel, nsurl));
772
+ }
773
+ void init(const std::string js) {
774
+ // Equivalent Obj-C:
775
+ // [m_manager addUserScript:[[WKUserScript alloc] initWithSource:[NSString stringWithUTF8String:js.c_str()] injectionTime:WKUserScriptInjectionTimeAtDocumentStart forMainFrameOnly:YES]]
776
+ ((void (*)(id, SEL, id))objc_msgSend)(
777
+ m_manager, "addUserScript:"_sel,
778
+ ((id(*)(id, SEL, id, long, BOOL))objc_msgSend)(
779
+ ((id(*)(id, SEL))objc_msgSend)("WKUserScript"_cls, "alloc"_sel),
780
+ "initWithSource:injectionTime:forMainFrameOnly:"_sel,
781
+ ((id(*)(id, SEL, const char *))objc_msgSend)(
782
+ "NSString"_cls, "stringWithUTF8String:"_sel, js.c_str()),
783
+ WKUserScriptInjectionTimeAtDocumentStart, 1));
784
+ }
785
+ void eval(const std::string js) {
786
+ ((void (*)(id, SEL, id, id))objc_msgSend)(
787
+ m_webview, "evaluateJavaScript:completionHandler:"_sel,
788
+ ((id(*)(id, SEL, const char *))objc_msgSend)(
789
+ "NSString"_cls, "stringWithUTF8String:"_sel, js.c_str()),
790
+ nullptr);
791
+ }
792
+
793
+ private:
794
+ virtual void on_message(const std::string msg) = 0;
795
+ void close() { ((void (*)(id, SEL))objc_msgSend)(m_window, "close"_sel); }
796
+ id m_window;
797
+ id m_webview;
798
+ id m_manager;
799
+ };
800
+
801
+ using browser_engine = cocoa_wkwebview_engine;
802
+
803
+ } // namespace webview
804
+
805
+ #elif defined(WEBVIEW_EDGE)
806
+
807
+ //
808
+ // ====================================================================
809
+ //
810
+ // This implementation uses Win32 API to create a native window. It can
811
+ // use either EdgeHTML or Edge/Chromium backend as a browser engine.
812
+ //
813
+ // ====================================================================
814
+ //
815
+
816
+ #define WIN32_LEAN_AND_MEAN
817
+ #include <Shlwapi.h>
818
+ #include <codecvt>
819
+ #include <stdlib.h>
820
+ #include <windows.h>
821
+
822
+ #pragma comment(lib, "user32.lib")
823
+ #pragma comment(lib, "Shlwapi.lib")
824
+
825
+ // EdgeHTML headers and libs
826
+ #include <objbase.h>
827
+ #include <winrt/Windows.Foundation.Collections.h>
828
+ #include <winrt/Windows.Foundation.h>
829
+ #include <winrt/Windows.Web.UI.Interop.h>
830
+ #pragma comment(lib, "windowsapp")
831
+
832
+ // Edge/Chromium headers and libs
833
+ #include "webview2.h"
834
+ #pragma comment(lib, "ole32.lib")
835
+ #pragma comment(lib, "oleaut32.lib")
836
+
837
+ namespace webview {
838
+
839
+ using msg_cb_t = std::function<void(const std::string)>;
840
+
841
+ // Common interface for EdgeHTML and Edge/Chromium
842
+ class browser {
843
+ public:
844
+ virtual ~browser() = default;
845
+ virtual bool embed(HWND, bool, msg_cb_t) = 0;
846
+ virtual void navigate(const std::string url) = 0;
847
+ virtual void eval(const std::string js) = 0;
848
+ virtual void init(const std::string js) = 0;
849
+ virtual void resize(HWND) = 0;
850
+ };
851
+
852
+ //
853
+ // EdgeHTML browser engine
854
+ //
855
+ using namespace winrt;
856
+ using namespace Windows::Foundation;
857
+ using namespace Windows::Web::UI;
858
+ using namespace Windows::Web::UI::Interop;
859
+
860
+ class edge_html : public browser {
861
+ public:
862
+ bool embed(HWND wnd, bool debug, msg_cb_t cb) override {
863
+ init_apartment(winrt::apartment_type::single_threaded);
864
+ auto process = WebViewControlProcess();
865
+ auto op = process.CreateWebViewControlAsync(reinterpret_cast<int64_t>(wnd),
866
+ Rect());
867
+ if (op.Status() != AsyncStatus::Completed) {
868
+ handle h(CreateEvent(nullptr, false, false, nullptr));
869
+ op.Completed([h = h.get()](auto, auto) { SetEvent(h); });
870
+ HANDLE hs[] = {h.get()};
871
+ DWORD i;
872
+ CoWaitForMultipleHandles(COWAIT_DISPATCH_WINDOW_MESSAGES |
873
+ COWAIT_DISPATCH_CALLS |
874
+ COWAIT_INPUTAVAILABLE,
875
+ INFINITE, 1, hs, &i);
876
+ }
877
+ m_webview = op.GetResults();
878
+ m_webview.Settings().IsScriptNotifyAllowed(true);
879
+ m_webview.IsVisible(true);
880
+ m_webview.ScriptNotify([=](auto const &sender, auto const &args) {
881
+ std::string s = winrt::to_string(args.Value());
882
+ cb(s.c_str());
883
+ });
884
+ m_webview.NavigationStarting([=](auto const &sender, auto const &args) {
885
+ m_webview.AddInitializeScript(winrt::to_hstring(init_js));
886
+ });
887
+ init("window.external.invoke = s => window.external.notify(s)");
888
+ return true;
889
+ }
890
+
891
+ void navigate(const std::string url) override {
892
+ std::string html = html_from_uri(url);
893
+ if (html != "") {
894
+ m_webview.NavigateToString(winrt::to_hstring(html));
895
+ } else {
896
+ Uri uri(winrt::to_hstring(url));
897
+ m_webview.Navigate(uri);
898
+ }
899
+ }
900
+
901
+ void init(const std::string js) override {
902
+ init_js = init_js + "(function(){" + js + "})();";
903
+ }
904
+
905
+ void eval(const std::string js) override {
906
+ m_webview.InvokeScriptAsync(
907
+ L"eval", single_threaded_vector<hstring>({winrt::to_hstring(js)}));
908
+ }
909
+
910
+ void resize(HWND wnd) override {
911
+ if (m_webview == nullptr) {
912
+ return;
913
+ }
914
+ RECT r;
915
+ GetClientRect(wnd, &r);
916
+ Rect bounds(r.left, r.top, r.right - r.left, r.bottom - r.top);
917
+ m_webview.Bounds(bounds);
918
+ }
919
+
920
+ private:
921
+ WebViewControl m_webview = nullptr;
922
+ std::string init_js = "";
923
+ };
924
+
925
+ //
926
+ // Edge/Chromium browser engine
927
+ //
928
+ class edge_chromium : public browser {
929
+ public:
930
+ bool embed(HWND wnd, bool debug, msg_cb_t cb) override {
931
+ CoInitializeEx(nullptr, 0);
932
+ std::atomic_flag flag = ATOMIC_FLAG_INIT;
933
+ flag.test_and_set();
934
+
935
+ char currentExePath[MAX_PATH];
936
+ GetModuleFileNameA(NULL, currentExePath, MAX_PATH);
937
+ char *currentExeName = PathFindFileNameA(currentExePath);
938
+
939
+ std::wstring_convert<std::codecvt_utf8_utf16<wchar_t>> wideCharConverter;
940
+ std::wstring userDataFolder =
941
+ wideCharConverter.from_bytes(std::getenv("APPDATA"));
942
+ std::wstring currentExeNameW = wideCharConverter.from_bytes(currentExeName);
943
+
944
+ HRESULT res = CreateCoreWebView2EnvironmentWithOptions(
945
+ nullptr, (userDataFolder + L"/" + currentExeNameW).c_str(), nullptr,
946
+ new webview2_com_handler(wnd, cb,
947
+ [&](ICoreWebView2Controller *controller) {
948
+ m_controller = controller;
949
+ m_controller->get_CoreWebView2(&m_webview);
950
+ m_webview->AddRef();
951
+ flag.clear();
952
+ }));
953
+ if (res != S_OK) {
954
+ CoUninitialize();
955
+ return false;
956
+ }
957
+ MSG msg = {};
958
+ while (flag.test_and_set() && GetMessage(&msg, NULL, 0, 0)) {
959
+ TranslateMessage(&msg);
960
+ DispatchMessage(&msg);
961
+ }
962
+ init("window.external={invoke:s=>window.chrome.webview.postMessage(s)}");
963
+ return true;
964
+ }
965
+
966
+ void resize(HWND wnd) override {
967
+ if (m_controller == nullptr) {
968
+ return;
969
+ }
970
+ RECT bounds;
971
+ GetClientRect(wnd, &bounds);
972
+ m_controller->put_Bounds(bounds);
973
+ }
974
+
975
+ void navigate(const std::string url) override {
976
+ auto wurl = to_lpwstr(url);
977
+ m_webview->Navigate(wurl);
978
+ delete[] wurl;
979
+ }
980
+
981
+ void init(const std::string js) override {
982
+ LPCWSTR wjs = to_lpwstr(js);
983
+ m_webview->AddScriptToExecuteOnDocumentCreated(wjs, nullptr);
984
+ delete[] wjs;
985
+ }
986
+
987
+ void eval(const std::string js) override {
988
+ LPCWSTR wjs = to_lpwstr(js);
989
+ m_webview->ExecuteScript(wjs, nullptr);
990
+ delete[] wjs;
991
+ }
992
+
993
+ private:
994
+ LPWSTR to_lpwstr(const std::string s) {
995
+ int n = MultiByteToWideChar(CP_UTF8, 0, s.c_str(), -1, NULL, 0);
996
+ wchar_t *ws = new wchar_t[n];
997
+ MultiByteToWideChar(CP_UTF8, 0, s.c_str(), -1, ws, n);
998
+ return ws;
999
+ }
1000
+
1001
+ ICoreWebView2 *m_webview = nullptr;
1002
+ ICoreWebView2Controller *m_controller = nullptr;
1003
+
1004
+ class webview2_com_handler
1005
+ : public ICoreWebView2CreateCoreWebView2EnvironmentCompletedHandler,
1006
+ public ICoreWebView2CreateCoreWebView2ControllerCompletedHandler,
1007
+ public ICoreWebView2WebMessageReceivedEventHandler,
1008
+ public ICoreWebView2PermissionRequestedEventHandler {
1009
+ using webview2_com_handler_cb_t =
1010
+ std::function<void(ICoreWebView2Controller *)>;
1011
+
1012
+ public:
1013
+ webview2_com_handler(HWND hwnd, msg_cb_t msgCb,
1014
+ webview2_com_handler_cb_t cb)
1015
+ : m_window(hwnd), m_msgCb(msgCb), m_cb(cb) {}
1016
+ ULONG STDMETHODCALLTYPE AddRef() { return 1; }
1017
+ ULONG STDMETHODCALLTYPE Release() { return 1; }
1018
+ HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, LPVOID *ppv) {
1019
+ return S_OK;
1020
+ }
1021
+ HRESULT STDMETHODCALLTYPE Invoke(HRESULT res,
1022
+ ICoreWebView2Environment *env) {
1023
+ env->CreateCoreWebView2Controller(m_window, this);
1024
+ return S_OK;
1025
+ }
1026
+ HRESULT STDMETHODCALLTYPE Invoke(HRESULT res,
1027
+ ICoreWebView2Controller *controller) {
1028
+ controller->AddRef();
1029
+
1030
+ ICoreWebView2 *webview;
1031
+ ::EventRegistrationToken token;
1032
+ controller->get_CoreWebView2(&webview);
1033
+ webview->add_WebMessageReceived(this, &token);
1034
+ webview->add_PermissionRequested(this, &token);
1035
+
1036
+ m_cb(controller);
1037
+ return S_OK;
1038
+ }
1039
+ HRESULT STDMETHODCALLTYPE Invoke(
1040
+ ICoreWebView2 *sender, ICoreWebView2WebMessageReceivedEventArgs *args) {
1041
+ LPWSTR message;
1042
+ args->TryGetWebMessageAsString(&message);
1043
+
1044
+ std::wstring_convert<std::codecvt_utf8_utf16<wchar_t>> wideCharConverter;
1045
+ m_msgCb(wideCharConverter.to_bytes(message));
1046
+ sender->PostWebMessageAsString(message);
1047
+
1048
+ CoTaskMemFree(message);
1049
+ return S_OK;
1050
+ }
1051
+ HRESULT STDMETHODCALLTYPE
1052
+ Invoke(ICoreWebView2 *sender,
1053
+ ICoreWebView2PermissionRequestedEventArgs *args) {
1054
+ COREWEBVIEW2_PERMISSION_KIND kind;
1055
+ args->get_PermissionKind(&kind);
1056
+ if (kind == COREWEBVIEW2_PERMISSION_KIND_CLIPBOARD_READ) {
1057
+ args->put_State(COREWEBVIEW2_PERMISSION_STATE_ALLOW);
1058
+ }
1059
+ return S_OK;
1060
+ }
1061
+
1062
+ private:
1063
+ HWND m_window;
1064
+ msg_cb_t m_msgCb;
1065
+ webview2_com_handler_cb_t m_cb;
1066
+ };
1067
+ };
1068
+
1069
+ class win32_edge_engine {
1070
+ public:
1071
+ win32_edge_engine(bool debug, void *window) {
1072
+ if (window == nullptr) {
1073
+ HINSTANCE hInstance = GetModuleHandle(nullptr);
1074
+ HICON icon = (HICON)LoadImage(
1075
+ hInstance, IDI_APPLICATION, IMAGE_ICON, GetSystemMetrics(SM_CXSMICON),
1076
+ GetSystemMetrics(SM_CYSMICON), LR_DEFAULTCOLOR);
1077
+
1078
+ WNDCLASSEX wc;
1079
+ ZeroMemory(&wc, sizeof(WNDCLASSEX));
1080
+ wc.cbSize = sizeof(WNDCLASSEX);
1081
+ wc.hInstance = hInstance;
1082
+ wc.lpszClassName = "webview";
1083
+ wc.hIcon = icon;
1084
+ wc.hIconSm = icon;
1085
+ wc.lpfnWndProc =
1086
+ (WNDPROC)(+[](HWND hwnd, UINT msg, WPARAM wp, LPARAM lp) -> int {
1087
+ auto w = (win32_edge_engine *)GetWindowLongPtr(hwnd, GWLP_USERDATA);
1088
+ switch (msg) {
1089
+ case WM_SIZE:
1090
+ w->m_browser->resize(hwnd);
1091
+ break;
1092
+ case WM_CLOSE:
1093
+ DestroyWindow(hwnd);
1094
+ break;
1095
+ case WM_DESTROY:
1096
+ w->terminate();
1097
+ break;
1098
+ case WM_GETMINMAXINFO: {
1099
+ auto lpmmi = (LPMINMAXINFO)lp;
1100
+ if (w == nullptr) {
1101
+ return 0;
1102
+ }
1103
+ if (w->m_maxsz.x > 0 && w->m_maxsz.y > 0) {
1104
+ lpmmi->ptMaxSize = w->m_maxsz;
1105
+ lpmmi->ptMaxTrackSize = w->m_maxsz;
1106
+ }
1107
+ if (w->m_minsz.x > 0 && w->m_minsz.y > 0) {
1108
+ lpmmi->ptMinTrackSize = w->m_minsz;
1109
+ }
1110
+ } break;
1111
+ default:
1112
+ return DefWindowProc(hwnd, msg, wp, lp);
1113
+ }
1114
+ return 0;
1115
+ });
1116
+ RegisterClassEx(&wc);
1117
+ m_window = CreateWindow("webview", "", WS_OVERLAPPEDWINDOW, CW_USEDEFAULT,
1118
+ CW_USEDEFAULT, 640, 480, nullptr, nullptr,
1119
+ GetModuleHandle(nullptr), nullptr);
1120
+ SetWindowLongPtr(m_window, GWLP_USERDATA, (LONG_PTR)this);
1121
+ } else {
1122
+ m_window = *(static_cast<HWND *>(window));
1123
+ }
1124
+
1125
+ SetProcessDpiAwarenessContext(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE);
1126
+ ShowWindow(m_window, SW_SHOW);
1127
+ UpdateWindow(m_window);
1128
+ SetFocus(m_window);
1129
+
1130
+ auto cb =
1131
+ std::bind(&win32_edge_engine::on_message, this, std::placeholders::_1);
1132
+
1133
+ if (!m_browser->embed(m_window, debug, cb)) {
1134
+ m_browser = std::make_unique<webview::edge_html>();
1135
+ m_browser->embed(m_window, debug, cb);
1136
+ }
1137
+
1138
+ m_browser->resize(m_window);
1139
+ }
1140
+
1141
+ void run() {
1142
+ MSG msg;
1143
+ BOOL res;
1144
+ while ((res = GetMessage(&msg, nullptr, 0, 0)) != -1) {
1145
+ if (msg.hwnd) {
1146
+ TranslateMessage(&msg);
1147
+ DispatchMessage(&msg);
1148
+ continue;
1149
+ }
1150
+ if (msg.message == WM_APP) {
1151
+ auto f = (dispatch_fn_t *)(msg.lParam);
1152
+ (*f)();
1153
+ delete f;
1154
+ } else if (msg.message == WM_QUIT) {
1155
+ return;
1156
+ }
1157
+ }
1158
+ }
1159
+ void *window() { return (void *)m_window; }
1160
+ void terminate() { PostQuitMessage(0); }
1161
+ void dispatch(dispatch_fn_t f) {
1162
+ PostThreadMessage(m_main_thread, WM_APP, 0, (LPARAM) new dispatch_fn_t(f));
1163
+ }
1164
+
1165
+ void set_title(const std::string title) {
1166
+ SetWindowText(m_window, title.c_str());
1167
+ }
1168
+
1169
+ void set_size(int width, int height, int hints) {
1170
+ auto style = GetWindowLong(m_window, GWL_STYLE);
1171
+ if (hints == WEBVIEW_HINT_FIXED) {
1172
+ style &= ~(WS_THICKFRAME | WS_MAXIMIZEBOX);
1173
+ } else {
1174
+ style |= (WS_THICKFRAME | WS_MAXIMIZEBOX);
1175
+ }
1176
+ SetWindowLong(m_window, GWL_STYLE, style);
1177
+
1178
+ if (hints == WEBVIEW_HINT_MAX) {
1179
+ m_maxsz.x = width;
1180
+ m_maxsz.y = height;
1181
+ } else if (hints == WEBVIEW_HINT_MIN) {
1182
+ m_minsz.x = width;
1183
+ m_minsz.y = height;
1184
+ } else {
1185
+ RECT r;
1186
+ r.left = r.top = 0;
1187
+ r.right = width;
1188
+ r.bottom = height;
1189
+ AdjustWindowRect(&r, WS_OVERLAPPEDWINDOW, 0);
1190
+ SetWindowPos(
1191
+ m_window, NULL, r.left, r.top, r.right - r.left, r.bottom - r.top,
1192
+ SWP_NOZORDER | SWP_NOACTIVATE | SWP_NOMOVE | SWP_FRAMECHANGED);
1193
+ m_browser->resize(m_window);
1194
+ }
1195
+ }
1196
+
1197
+ void navigate(const std::string url) { m_browser->navigate(url); }
1198
+ void eval(const std::string js) { m_browser->eval(js); }
1199
+ void init(const std::string js) { m_browser->init(js); }
1200
+
1201
+ private:
1202
+ virtual void on_message(const std::string msg) = 0;
1203
+
1204
+ HWND m_window;
1205
+ POINT m_minsz = POINT{0, 0};
1206
+ POINT m_maxsz = POINT{0, 0};
1207
+ DWORD m_main_thread = GetCurrentThreadId();
1208
+ std::unique_ptr<webview::browser> m_browser =
1209
+ std::make_unique<webview::edge_chromium>();
1210
+ };
1211
+
1212
+ using browser_engine = win32_edge_engine;
1213
+ } // namespace webview
1214
+
1215
+ #endif /* WEBVIEW_GTK, WEBVIEW_COCOA, WEBVIEW_EDGE */
1216
+
1217
+ namespace webview {
1218
+
1219
+ class webview : public browser_engine {
1220
+ public:
1221
+ webview(bool debug = false, void *wnd = nullptr)
1222
+ : browser_engine(debug, wnd) {}
1223
+
1224
+ void navigate(const std::string url) {
1225
+ if (url == "") {
1226
+ browser_engine::navigate("data:text/html," +
1227
+ url_encode("<html><body>Hello</body></html>"));
1228
+ return;
1229
+ }
1230
+ std::string html = html_from_uri(url);
1231
+ if (html != "") {
1232
+ browser_engine::navigate("data:text/html," + url_encode(html));
1233
+ } else {
1234
+ browser_engine::navigate(url);
1235
+ }
1236
+ }
1237
+
1238
+ using binding_t = std::function<void(std::string, std::string, void *)>;
1239
+ using binding_ctx_t = std::pair<binding_t *, void *>;
1240
+
1241
+ using sync_binding_t = std::function<std::string(std::string)>;
1242
+ using sync_binding_ctx_t = std::pair<webview *, sync_binding_t>;
1243
+
1244
+ void bind(const std::string name, sync_binding_t fn) {
1245
+ bind(
1246
+ name,
1247
+ [](std::string seq, std::string req, void *arg) {
1248
+ auto pair = static_cast<sync_binding_ctx_t *>(arg);
1249
+ pair->first->resolve(seq, 0, pair->second(req));
1250
+ },
1251
+ new sync_binding_ctx_t(this, fn));
1252
+ }
1253
+
1254
+ void bind(const std::string name, binding_t f, void *arg) {
1255
+ auto js = "(function() { var name = '" + name + "';" + R"(
1256
+ var RPC = window._rpc = (window._rpc || {nextSeq: 1});
1257
+ window[name] = function() {
1258
+ var seq = RPC.nextSeq++;
1259
+ var promise = new Promise(function(resolve, reject) {
1260
+ RPC[seq] = {
1261
+ resolve: resolve,
1262
+ reject: reject,
1263
+ };
1264
+ });
1265
+ window.external.invoke(JSON.stringify({
1266
+ id: seq,
1267
+ method: name,
1268
+ params: Array.prototype.slice.call(arguments),
1269
+ }));
1270
+ return promise;
1271
+ }
1272
+ })())";
1273
+ init(js);
1274
+ bindings[name] = new binding_ctx_t(new binding_t(f), arg);
1275
+ }
1276
+
1277
+ void resolve(const std::string seq, int status, const std::string result) {
1278
+ dispatch([=]() {
1279
+ if (status == 0) {
1280
+ eval("window._rpc[" + seq + "].resolve(" + result + "); window._rpc[" +
1281
+ seq + "] = undefined");
1282
+ } else {
1283
+ eval("window._rpc[" + seq + "].reject(" + result + "); window._rpc[" +
1284
+ seq + "] = undefined");
1285
+ }
1286
+ });
1287
+ }
1288
+
1289
+ private:
1290
+ void on_message(const std::string msg) {
1291
+ auto seq = json_parse(msg, "id", 0);
1292
+ auto name = json_parse(msg, "method", 0);
1293
+ auto args = json_parse(msg, "params", 0);
1294
+ if (bindings.find(name) == bindings.end()) {
1295
+ return;
1296
+ }
1297
+ auto fn = bindings[name];
1298
+ (*fn->first)(seq, args, fn->second);
1299
+ }
1300
+ std::map<std::string, binding_ctx_t *> bindings;
1301
+ };
1302
+ } // namespace webview
1303
+
1304
+ WEBVIEW_API webview_t webview_create(int debug, void *wnd) {
1305
+ return new webview::webview(debug, wnd);
1306
+ }
1307
+
1308
+ WEBVIEW_API void webview_destroy(webview_t w) {
1309
+ delete static_cast<webview::webview *>(w);
1310
+ }
1311
+
1312
+ WEBVIEW_API void webview_run(webview_t w) {
1313
+ static_cast<webview::webview *>(w)->run();
1314
+ }
1315
+
1316
+ WEBVIEW_API void webview_terminate(webview_t w) {
1317
+ static_cast<webview::webview *>(w)->terminate();
1318
+ }
1319
+
1320
+ WEBVIEW_API void webview_dispatch(webview_t w, void (*fn)(webview_t, void *),
1321
+ void *arg) {
1322
+ static_cast<webview::webview *>(w)->dispatch([=]() { fn(w, arg); });
1323
+ }
1324
+
1325
+ WEBVIEW_API void *webview_get_window(webview_t w) {
1326
+ return static_cast<webview::webview *>(w)->window();
1327
+ }
1328
+
1329
+ WEBVIEW_API void webview_set_title(webview_t w, const char *title) {
1330
+ static_cast<webview::webview *>(w)->set_title(title);
1331
+ }
1332
+
1333
+ WEBVIEW_API void webview_set_size(webview_t w, int width, int height,
1334
+ int hints) {
1335
+ static_cast<webview::webview *>(w)->set_size(width, height, hints);
1336
+ }
1337
+
1338
+ WEBVIEW_API void webview_navigate(webview_t w, const char *url) {
1339
+ static_cast<webview::webview *>(w)->navigate(url);
1340
+ }
1341
+
1342
+ WEBVIEW_API void webview_init(webview_t w, const char *js) {
1343
+ static_cast<webview::webview *>(w)->init(js);
1344
+ }
1345
+
1346
+ WEBVIEW_API void webview_eval(webview_t w, const char *js) {
1347
+ static_cast<webview::webview *>(w)->eval(js);
1348
+ }
1349
+
1350
+ WEBVIEW_API void webview_bind(webview_t w, const char *name,
1351
+ void (*fn)(const char *seq, const char *req,
1352
+ void *arg),
1353
+ void *arg) {
1354
+ static_cast<webview::webview *>(w)->bind(
1355
+ name,
1356
+ [=](std::string seq, std::string req, void *arg) {
1357
+ fn(seq.c_str(), req.c_str(), arg);
1358
+ },
1359
+ arg);
1360
+ }
1361
+
1362
+ WEBVIEW_API void webview_return(webview_t w, const char *seq, int status,
1363
+ const char *result) {
1364
+ static_cast<webview::webview *>(w)->resolve(seq, status, result);
1365
+ }
1366
+
1367
+ #endif /* WEBVIEW_HEADER */
1368
+
1369
+ #endif /* WEBVIEW_H */