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.
- checksums.yaml +7 -0
- data/.rubocop.yml +13 -0
- data/CHANGELOG.md +5 -0
- data/CODE_OF_CONDUCT.md +84 -0
- data/Gemfile +12 -0
- data/Gemfile.lock +50 -0
- data/LICENSE.txt +21 -0
- data/README.md +108 -0
- data/Rakefile +27 -0
- data/bin/console +15 -0
- data/bin/setup +8 -0
- data/ext/Rakefile +14 -0
- data/ext/webview/webview.cpp +1 -0
- data/ext/webview/webview.h +1380 -0
- data/lib/webview_ruby/version.rb +5 -0
- data/lib/webview_ruby.rb +83 -0
- metadata +99 -0
@@ -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 */
|