webview-ffi 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: d0f63ce057c152790a6e2b7161e0ea166922ef706a8f1bbdd7c37f82b61325b4
4
+ data.tar.gz: 5467ae74c00b5a0eb5bc6258198419e418b518d9dd90f040bc06cecdde7ddbd7
5
+ SHA512:
6
+ metadata.gz: 17d7d4246d29c5787cef4ef098b65e9e8fa010f85965fef16be876d3a2d2924b7f92f4544d797d0e51b7aaa9cb1bcc4250bfca5dc7e77e1f4d2d653d4fa686b3
7
+ data.tar.gz: a7b11530e06109046a21ee776f5fddd0ed58755188d31f63d1623d7cca98fda7a4642ceec990135418f7400537e5d52ac443882c101b007bb8ed610f0fc571f1
@@ -0,0 +1,18 @@
1
+ name: Ruby
2
+
3
+ on: [push,pull_request]
4
+
5
+ jobs:
6
+ build:
7
+ runs-on: ubuntu-latest
8
+ steps:
9
+ - uses: actions/checkout@v2
10
+ - name: Set up Ruby
11
+ uses: ruby/setup-ruby@v1
12
+ with:
13
+ ruby-version: 3.0.0
14
+ - name: Run the default task
15
+ run: |
16
+ gem install bundler -v 2.2.3
17
+ bundle install
18
+ bundle exec rake
data/.gitignore ADDED
@@ -0,0 +1,9 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /_yardoc/
4
+ /coverage/
5
+ /doc/
6
+ /pkg/
7
+ /spec/reports/
8
+ /tmp/
9
+ *.so
data/.rubocop.yml ADDED
@@ -0,0 +1,10 @@
1
+ Style/StringLiterals:
2
+ Enabled: true
3
+ EnforcedStyle: double_quotes
4
+
5
+ Style/StringLiteralsInInterpolation:
6
+ Enabled: true
7
+ EnforcedStyle: double_quotes
8
+
9
+ Layout/LineLength:
10
+ Max: 120
@@ -0,0 +1,84 @@
1
+ # Contributor Covenant Code of Conduct
2
+
3
+ ## Our Pledge
4
+
5
+ We as members, contributors, and leaders pledge to make participation in our community a harassment-free experience for everyone, regardless of age, body size, visible or invisible disability, ethnicity, sex characteristics, gender identity and expression, level of experience, education, socio-economic status, nationality, personal appearance, race, religion, or sexual identity and orientation.
6
+
7
+ We pledge to act and interact in ways that contribute to an open, welcoming, diverse, inclusive, and healthy community.
8
+
9
+ ## Our Standards
10
+
11
+ Examples of behavior that contributes to a positive environment for our community include:
12
+
13
+ * Demonstrating empathy and kindness toward other people
14
+ * Being respectful of differing opinions, viewpoints, and experiences
15
+ * Giving and gracefully accepting constructive feedback
16
+ * Accepting responsibility and apologizing to those affected by our mistakes, and learning from the experience
17
+ * Focusing on what is best not just for us as individuals, but for the overall community
18
+
19
+ Examples of unacceptable behavior include:
20
+
21
+ * The use of sexualized language or imagery, and sexual attention or
22
+ advances of any kind
23
+ * Trolling, insulting or derogatory comments, and personal or political attacks
24
+ * Public or private harassment
25
+ * Publishing others' private information, such as a physical or email
26
+ address, without their explicit permission
27
+ * Other conduct which could reasonably be considered inappropriate in a
28
+ professional setting
29
+
30
+ ## Enforcement Responsibilities
31
+
32
+ Community leaders are responsible for clarifying and enforcing our standards of acceptable behavior and will take appropriate and fair corrective action in response to any behavior that they deem inappropriate, threatening, offensive, or harmful.
33
+
34
+ Community leaders have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, and will communicate reasons for moderation decisions when appropriate.
35
+
36
+ ## Scope
37
+
38
+ This Code of Conduct applies within all community spaces, and also applies when an individual is officially representing the community in public spaces. Examples of representing our community include using an official e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event.
39
+
40
+ ## Enforcement
41
+
42
+ Instances of abusive, harassing, or otherwise unacceptable behavior may be reported to the community leaders responsible for enforcement at TODO: Write your email address. All complaints will be reviewed and investigated promptly and fairly.
43
+
44
+ All community leaders are obligated to respect the privacy and security of the reporter of any incident.
45
+
46
+ ## Enforcement Guidelines
47
+
48
+ Community leaders will follow these Community Impact Guidelines in determining the consequences for any action they deem in violation of this Code of Conduct:
49
+
50
+ ### 1. Correction
51
+
52
+ **Community Impact**: Use of inappropriate language or other behavior deemed unprofessional or unwelcome in the community.
53
+
54
+ **Consequence**: A private, written warning from community leaders, providing clarity around the nature of the violation and an explanation of why the behavior was inappropriate. A public apology may be requested.
55
+
56
+ ### 2. Warning
57
+
58
+ **Community Impact**: A violation through a single incident or series of actions.
59
+
60
+ **Consequence**: A warning with consequences for continued behavior. No interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, for a specified period of time. This includes avoiding interactions in community spaces as well as external channels like social media. Violating these terms may lead to a temporary or permanent ban.
61
+
62
+ ### 3. Temporary Ban
63
+
64
+ **Community Impact**: A serious violation of community standards, including sustained inappropriate behavior.
65
+
66
+ **Consequence**: A temporary ban from any sort of interaction or public communication with the community for a specified period of time. No public or private interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, is allowed during this period. Violating these terms may lead to a permanent ban.
67
+
68
+ ### 4. Permanent Ban
69
+
70
+ **Community Impact**: Demonstrating a pattern of violation of community standards, including sustained inappropriate behavior, harassment of an individual, or aggression toward or disparagement of classes of individuals.
71
+
72
+ **Consequence**: A permanent ban from any sort of public interaction within the community.
73
+
74
+ ## Attribution
75
+
76
+ This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 2.0,
77
+ available at https://www.contributor-covenant.org/version/2/0/code_of_conduct.html.
78
+
79
+ Community Impact Guidelines were inspired by [Mozilla's code of conduct enforcement ladder](https://github.com/mozilla/diversity).
80
+
81
+ [homepage]: https://www.contributor-covenant.org
82
+
83
+ For answers to common questions about this code of conduct, see the FAQ at
84
+ https://www.contributor-covenant.org/faq. Translations are available at https://www.contributor-covenant.org/translations.
data/Gemfile ADDED
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ source "https://rubygems.org"
4
+
5
+ # Specify your gem's dependencies in webview.gemspec
6
+ gemspec
7
+
8
+ gem "rake", "~> 13.0"
9
+
10
+ gem "rubocop", "~> 0.80"
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2021 TODO: Write your name
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,43 @@
1
+ # Webview
2
+
3
+ Welcome to your new gem! In this directory, you'll find the files you need to be able to package up your Ruby library into a gem. Put your Ruby code in the file `lib/webview`. To experiment with that code, run `bin/console` for an interactive prompt.
4
+
5
+ TODO: Delete this and the text above, and describe your gem
6
+
7
+ ## Installation
8
+
9
+ Add this line to your application's Gemfile:
10
+
11
+ ```ruby
12
+ gem 'webview'
13
+ ```
14
+
15
+ And then execute:
16
+
17
+ $ bundle install
18
+
19
+ Or install it yourself as:
20
+
21
+ $ gem install webview
22
+
23
+ ## Usage
24
+
25
+ TODO: Write usage instructions here
26
+
27
+ ## Development
28
+
29
+ After checking out the repo, run `bin/setup` to install dependencies. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
30
+
31
+ To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and the created tag, and push the `.gem` file to [rubygems.org](https://rubygems.org).
32
+
33
+ ## Contributing
34
+
35
+ Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/webview. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [code of conduct](https://github.com/[USERNAME]/webview/blob/master/CODE_OF_CONDUCT.md).
36
+
37
+ ## License
38
+
39
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
40
+
41
+ ## Code of Conduct
42
+
43
+ Everyone interacting in the Webview project's codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/[USERNAME]/webview/blob/master/CODE_OF_CONDUCT.md).
data/Rakefile ADDED
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "bundler/gem_tasks"
4
+ require "rubocop/rake_task"
5
+ require "rake/extensiontask"
6
+
7
+ RuboCop::RakeTask.new
8
+ task default: :rubocop
9
+
10
+ Rake::ExtensionTask.new "webview" do |ext|
11
+ ext.lib_dir = "lib/webview"
12
+ end
data/bin/console ADDED
@@ -0,0 +1,15 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require "bundler/setup"
5
+ require "webview"
6
+
7
+ # You can add fixtures and/or initialization code here to make experimenting
8
+ # with your gem easier. You can also use a different console, if you like.
9
+
10
+ # (If you use this, don't forget to add pry to your Gemfile!)
11
+ # require "pry"
12
+ # Pry.start
13
+
14
+ require "irb"
15
+ IRB.start(__FILE__)
data/bin/setup ADDED
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
@@ -0,0 +1,8 @@
1
+ require "mkmf"
2
+ require "rbconfig"
3
+ abort("ERROR: OS not supported yet!") unless RbConfig::CONFIG["host_os"] =~ /linux|cygwin/
4
+ abort("ERROR: gtk+-3.0 is missing.") unless have_library("gtk-3")
5
+ abort("ERORR: webkit2gtk-4.0 is missing.") unless have_library("webkit2gtk-4.0")
6
+ pkg_config("--cflags --libs gtk+-3.0 webkit2gtk-4.0")
7
+ create_header
8
+ create_makefile 'webview/webview'
@@ -0,0 +1,3 @@
1
+ // +build !windows
2
+
3
+ #include "webview.h"
@@ -0,0 +1,1348 @@
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
+ inline std::string url_encode(const std::string s) {
142
+ std::string encoded;
143
+ for (unsigned int i = 0; i < s.length(); i++) {
144
+ auto c = s[i];
145
+ if (isalnum(c) || c == '-' || c == '_' || c == '.' || c == '~') {
146
+ encoded = encoded + c;
147
+ } else {
148
+ char hex[4];
149
+ snprintf(hex, sizeof(hex), "%%%02x", c);
150
+ encoded = encoded + hex;
151
+ }
152
+ }
153
+ return encoded;
154
+ }
155
+
156
+ inline std::string url_decode(const std::string s) {
157
+ std::string decoded;
158
+ for (unsigned int i = 0; i < s.length(); i++) {
159
+ if (s[i] == '%') {
160
+ int n;
161
+ n = std::stoul(s.substr(i + 1, 2), nullptr, 16);
162
+ decoded = decoded + static_cast<char>(n);
163
+ i = i + 2;
164
+ } else if (s[i] == '+') {
165
+ decoded = decoded + ' ';
166
+ } else {
167
+ decoded = decoded + s[i];
168
+ }
169
+ }
170
+ return decoded;
171
+ }
172
+
173
+ inline std::string html_from_uri(const std::string s) {
174
+ if (s.substr(0, 15) == "data:text/html,") {
175
+ return url_decode(s.substr(15));
176
+ }
177
+ return "";
178
+ }
179
+
180
+ inline int json_parse_c(const char *s, size_t sz, const char *key, size_t keysz,
181
+ const char **value, size_t *valuesz) {
182
+ enum {
183
+ JSON_STATE_VALUE,
184
+ JSON_STATE_LITERAL,
185
+ JSON_STATE_STRING,
186
+ JSON_STATE_ESCAPE,
187
+ JSON_STATE_UTF8
188
+ } state = JSON_STATE_VALUE;
189
+ const char *k = NULL;
190
+ int index = 1;
191
+ int depth = 0;
192
+ int utf8_bytes = 0;
193
+
194
+ if (key == NULL) {
195
+ index = keysz;
196
+ keysz = 0;
197
+ }
198
+
199
+ *value = NULL;
200
+ *valuesz = 0;
201
+
202
+ for (; sz > 0; s++, sz--) {
203
+ enum {
204
+ JSON_ACTION_NONE,
205
+ JSON_ACTION_START,
206
+ JSON_ACTION_END,
207
+ JSON_ACTION_START_STRUCT,
208
+ JSON_ACTION_END_STRUCT
209
+ } action = JSON_ACTION_NONE;
210
+ unsigned char c = *s;
211
+ switch (state) {
212
+ case JSON_STATE_VALUE:
213
+ if (c == ' ' || c == '\t' || c == '\n' || c == '\r' || c == ',' ||
214
+ c == ':') {
215
+ continue;
216
+ } else if (c == '"') {
217
+ action = JSON_ACTION_START;
218
+ state = JSON_STATE_STRING;
219
+ } else if (c == '{' || c == '[') {
220
+ action = JSON_ACTION_START_STRUCT;
221
+ } else if (c == '}' || c == ']') {
222
+ action = JSON_ACTION_END_STRUCT;
223
+ } else if (c == 't' || c == 'f' || c == 'n' || c == '-' ||
224
+ (c >= '0' && c <= '9')) {
225
+ action = JSON_ACTION_START;
226
+ state = JSON_STATE_LITERAL;
227
+ } else {
228
+ return -1;
229
+ }
230
+ break;
231
+ case JSON_STATE_LITERAL:
232
+ if (c == ' ' || c == '\t' || c == '\n' || c == '\r' || c == ',' ||
233
+ c == ']' || c == '}' || c == ':') {
234
+ state = JSON_STATE_VALUE;
235
+ s--;
236
+ sz++;
237
+ action = JSON_ACTION_END;
238
+ } else if (c < 32 || c > 126) {
239
+ return -1;
240
+ } // fallthrough
241
+ case JSON_STATE_STRING:
242
+ if (c < 32 || (c > 126 && c < 192)) {
243
+ return -1;
244
+ } else if (c == '"') {
245
+ action = JSON_ACTION_END;
246
+ state = JSON_STATE_VALUE;
247
+ } else if (c == '\\') {
248
+ state = JSON_STATE_ESCAPE;
249
+ } else if (c >= 192 && c < 224) {
250
+ utf8_bytes = 1;
251
+ state = JSON_STATE_UTF8;
252
+ } else if (c >= 224 && c < 240) {
253
+ utf8_bytes = 2;
254
+ state = JSON_STATE_UTF8;
255
+ } else if (c >= 240 && c < 247) {
256
+ utf8_bytes = 3;
257
+ state = JSON_STATE_UTF8;
258
+ } else if (c >= 128 && c < 192) {
259
+ return -1;
260
+ }
261
+ break;
262
+ case JSON_STATE_ESCAPE:
263
+ if (c == '"' || c == '\\' || c == '/' || c == 'b' || c == 'f' ||
264
+ c == 'n' || c == 'r' || c == 't' || c == 'u') {
265
+ state = JSON_STATE_STRING;
266
+ } else {
267
+ return -1;
268
+ }
269
+ break;
270
+ case JSON_STATE_UTF8:
271
+ if (c < 128 || c > 191) {
272
+ return -1;
273
+ }
274
+ utf8_bytes--;
275
+ if (utf8_bytes == 0) {
276
+ state = JSON_STATE_STRING;
277
+ }
278
+ break;
279
+ default:
280
+ return -1;
281
+ }
282
+
283
+ if (action == JSON_ACTION_END_STRUCT) {
284
+ depth--;
285
+ }
286
+
287
+ if (depth == 1) {
288
+ if (action == JSON_ACTION_START || action == JSON_ACTION_START_STRUCT) {
289
+ if (index == 0) {
290
+ *value = s;
291
+ } else if (keysz > 0 && index == 1) {
292
+ k = s;
293
+ } else {
294
+ index--;
295
+ }
296
+ } else if (action == JSON_ACTION_END ||
297
+ action == JSON_ACTION_END_STRUCT) {
298
+ if (*value != NULL && index == 0) {
299
+ *valuesz = (size_t)(s + 1 - *value);
300
+ return 0;
301
+ } else if (keysz > 0 && k != NULL) {
302
+ if (keysz == (size_t)(s - k - 1) && memcmp(key, k + 1, keysz) == 0) {
303
+ index = 0;
304
+ } else {
305
+ index = 2;
306
+ }
307
+ k = NULL;
308
+ }
309
+ }
310
+ }
311
+
312
+ if (action == JSON_ACTION_START_STRUCT) {
313
+ depth++;
314
+ }
315
+ }
316
+ return -1;
317
+ }
318
+
319
+ inline std::string json_escape(std::string s) {
320
+ // TODO: implement
321
+ return '"' + s + '"';
322
+ }
323
+
324
+ inline int json_unescape(const char *s, size_t n, char *out) {
325
+ int r = 0;
326
+ if (*s++ != '"') {
327
+ return -1;
328
+ }
329
+ while (n > 2) {
330
+ char c = *s;
331
+ if (c == '\\') {
332
+ s++;
333
+ n--;
334
+ switch (*s) {
335
+ case 'b':
336
+ c = '\b';
337
+ break;
338
+ case 'f':
339
+ c = '\f';
340
+ break;
341
+ case 'n':
342
+ c = '\n';
343
+ break;
344
+ case 'r':
345
+ c = '\r';
346
+ break;
347
+ case 't':
348
+ c = '\t';
349
+ break;
350
+ case '\\':
351
+ c = '\\';
352
+ break;
353
+ case '/':
354
+ c = '/';
355
+ break;
356
+ case '\"':
357
+ c = '\"';
358
+ break;
359
+ default: // TODO: support unicode decoding
360
+ return -1;
361
+ }
362
+ }
363
+ if (out != NULL) {
364
+ *out++ = c;
365
+ }
366
+ s++;
367
+ n--;
368
+ r++;
369
+ }
370
+ if (*s != '"') {
371
+ return -1;
372
+ }
373
+ if (out != NULL) {
374
+ *out = '\0';
375
+ }
376
+ return r;
377
+ }
378
+
379
+ inline std::string json_parse(const std::string s, const std::string key,
380
+ const int index) {
381
+ const char *value;
382
+ size_t value_sz;
383
+ if (key == "") {
384
+ json_parse_c(s.c_str(), s.length(), nullptr, index, &value, &value_sz);
385
+ } else {
386
+ json_parse_c(s.c_str(), s.length(), key.c_str(), key.length(), &value,
387
+ &value_sz);
388
+ }
389
+ if (value != nullptr) {
390
+ if (value[0] != '"') {
391
+ return std::string(value, value_sz);
392
+ }
393
+ int n = json_unescape(value, value_sz, nullptr);
394
+ if (n > 0) {
395
+ char *decoded = new char[n + 1];
396
+ json_unescape(value, value_sz, decoded);
397
+ std::string result(decoded, n);
398
+ delete[] decoded;
399
+ return result;
400
+ }
401
+ }
402
+ return "";
403
+ }
404
+
405
+ } // namespace webview
406
+
407
+ #if defined(WEBVIEW_GTK)
408
+ //
409
+ // ====================================================================
410
+ //
411
+ // This implementation uses webkit2gtk backend. It requires gtk+3.0 and
412
+ // webkit2gtk-4.0 libraries. Proper compiler flags can be retrieved via:
413
+ //
414
+ // pkg-config --cflags --libs gtk+-3.0 webkit2gtk-4.0
415
+ //
416
+ // ====================================================================
417
+ //
418
+ #include <JavaScriptCore/JavaScript.h>
419
+ #include <gtk/gtk.h>
420
+ #include <webkit2/webkit2.h>
421
+
422
+ namespace webview {
423
+
424
+ class gtk_webkit_engine {
425
+ public:
426
+ gtk_webkit_engine(bool debug, void *window)
427
+ : m_window(static_cast<GtkWidget *>(window)) {
428
+ gtk_init_check(0, NULL);
429
+ m_window = static_cast<GtkWidget *>(window);
430
+ if (m_window == nullptr) {
431
+ m_window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
432
+ }
433
+ g_signal_connect(G_OBJECT(m_window), "destroy",
434
+ G_CALLBACK(+[](GtkWidget *, gpointer arg) {
435
+ static_cast<gtk_webkit_engine *>(arg)->terminate();
436
+ }),
437
+ this);
438
+ // Initialize webview widget
439
+ m_webview = webkit_web_view_new();
440
+ WebKitUserContentManager *manager =
441
+ webkit_web_view_get_user_content_manager(WEBKIT_WEB_VIEW(m_webview));
442
+ g_signal_connect(manager, "script-message-received::external",
443
+ G_CALLBACK(+[](WebKitUserContentManager *,
444
+ WebKitJavascriptResult *r, gpointer arg) {
445
+ auto *w = static_cast<gtk_webkit_engine *>(arg);
446
+ #if WEBKIT_MAJOR_VERSION >= 2 && WEBKIT_MINOR_VERSION >= 22
447
+ JSCValue *value =
448
+ webkit_javascript_result_get_js_value(r);
449
+ char *s = jsc_value_to_string(value);
450
+ #else
451
+ JSGlobalContextRef ctx =
452
+ webkit_javascript_result_get_global_context(r);
453
+ JSValueRef value = webkit_javascript_result_get_value(r);
454
+ JSStringRef js = JSValueToStringCopy(ctx, value, NULL);
455
+ size_t n = JSStringGetMaximumUTF8CStringSize(js);
456
+ char *s = g_new(char, n);
457
+ JSStringGetUTF8CString(js, s, n);
458
+ JSStringRelease(js);
459
+ #endif
460
+ w->on_message(s);
461
+ g_free(s);
462
+ }),
463
+ this);
464
+ webkit_user_content_manager_register_script_message_handler(manager,
465
+ "external");
466
+ init("window.external={invoke:function(s){window.webkit.messageHandlers."
467
+ "external.postMessage(s);}}");
468
+
469
+ gtk_container_add(GTK_CONTAINER(m_window), GTK_WIDGET(m_webview));
470
+ gtk_widget_grab_focus(GTK_WIDGET(m_webview));
471
+
472
+ WebKitSettings *settings =
473
+ webkit_web_view_get_settings(WEBKIT_WEB_VIEW(m_webview));
474
+ webkit_settings_set_javascript_can_access_clipboard(settings, true);
475
+ if (debug) {
476
+ webkit_settings_set_enable_write_console_messages_to_stdout(settings,
477
+ true);
478
+ webkit_settings_set_enable_developer_extras(settings, true);
479
+ }
480
+
481
+ gtk_widget_show_all(m_window);
482
+ }
483
+ void *window() { return (void *)m_window; }
484
+ void run() { gtk_main(); }
485
+ void terminate() { gtk_main_quit(); }
486
+ void dispatch(std::function<void()> f) {
487
+ g_idle_add_full(G_PRIORITY_HIGH_IDLE, (GSourceFunc)([](void *f) -> int {
488
+ (*static_cast<dispatch_fn_t *>(f))();
489
+ return G_SOURCE_REMOVE;
490
+ }),
491
+ new std::function<void()>(f),
492
+ [](void *f) { delete static_cast<dispatch_fn_t *>(f); });
493
+ }
494
+
495
+ void set_title(const std::string title) {
496
+ gtk_window_set_title(GTK_WINDOW(m_window), title.c_str());
497
+ }
498
+
499
+ void set_size(int width, int height, int hints) {
500
+ gtk_window_set_resizable(GTK_WINDOW(m_window), hints != WEBVIEW_HINT_FIXED);
501
+ if (hints == WEBVIEW_HINT_NONE) {
502
+ gtk_window_resize(GTK_WINDOW(m_window), width, height);
503
+ } else if (hints == WEBVIEW_HINT_FIXED) {
504
+ gtk_widget_set_size_request(m_window, width, height);
505
+ } else {
506
+ GdkGeometry g;
507
+ g.min_width = g.max_width = width;
508
+ g.min_height = g.max_height = height;
509
+ GdkWindowHints h =
510
+ (hints == WEBVIEW_HINT_MIN ? GDK_HINT_MIN_SIZE : GDK_HINT_MAX_SIZE);
511
+ // This defines either MIN_SIZE, or MAX_SIZE, but not both:
512
+ gtk_window_set_geometry_hints(GTK_WINDOW(m_window), nullptr, &g, h);
513
+ }
514
+ }
515
+
516
+ void navigate(const std::string url) {
517
+ webkit_web_view_load_uri(WEBKIT_WEB_VIEW(m_webview), url.c_str());
518
+ }
519
+
520
+ void init(const std::string js) {
521
+ WebKitUserContentManager *manager =
522
+ webkit_web_view_get_user_content_manager(WEBKIT_WEB_VIEW(m_webview));
523
+ webkit_user_content_manager_add_script(
524
+ manager, webkit_user_script_new(
525
+ js.c_str(), WEBKIT_USER_CONTENT_INJECT_TOP_FRAME,
526
+ WEBKIT_USER_SCRIPT_INJECT_AT_DOCUMENT_START, NULL, NULL));
527
+ }
528
+
529
+ void eval(const std::string js) {
530
+ webkit_web_view_run_javascript(WEBKIT_WEB_VIEW(m_webview), js.c_str(), NULL,
531
+ NULL, NULL);
532
+ }
533
+
534
+ private:
535
+ virtual void on_message(const std::string msg) = 0;
536
+ GtkWidget *m_window;
537
+ GtkWidget *m_webview;
538
+ };
539
+
540
+ using browser_engine = gtk_webkit_engine;
541
+
542
+ } // namespace webview
543
+
544
+ #elif defined(WEBVIEW_COCOA)
545
+
546
+ //
547
+ // ====================================================================
548
+ //
549
+ // This implementation uses Cocoa WKWebView backend on macOS. It is
550
+ // written using ObjC runtime and uses WKWebView class as a browser runtime.
551
+ // You should pass "-framework Webkit" flag to the compiler.
552
+ //
553
+ // ====================================================================
554
+ //
555
+
556
+ #include <CoreGraphics/CoreGraphics.h>
557
+ #include <objc/objc-runtime.h>
558
+
559
+ #define NSBackingStoreBuffered 2
560
+
561
+ #define NSWindowStyleMaskResizable 8
562
+ #define NSWindowStyleMaskMiniaturizable 4
563
+ #define NSWindowStyleMaskTitled 1
564
+ #define NSWindowStyleMaskClosable 2
565
+
566
+ #define NSApplicationActivationPolicyRegular 0
567
+
568
+ #define WKUserScriptInjectionTimeAtDocumentStart 0
569
+
570
+ namespace webview {
571
+
572
+ // Helpers to avoid too much typing
573
+ id operator"" _cls(const char *s, std::size_t) { return (id)objc_getClass(s); }
574
+ SEL operator"" _sel(const char *s, std::size_t) { return sel_registerName(s); }
575
+ id operator"" _str(const char *s, std::size_t) {
576
+ return ((id(*)(id, SEL, const char *))objc_msgSend)(
577
+ "NSString"_cls, "stringWithUTF8String:"_sel, s);
578
+ }
579
+
580
+ class cocoa_wkwebview_engine {
581
+ public:
582
+ cocoa_wkwebview_engine(bool debug, void *window) {
583
+ // Application
584
+ id app = ((id(*)(id, SEL))objc_msgSend)("NSApplication"_cls,
585
+ "sharedApplication"_sel);
586
+ ((void (*)(id, SEL, long))objc_msgSend)(
587
+ app, "setActivationPolicy:"_sel, NSApplicationActivationPolicyRegular);
588
+
589
+ // Delegate
590
+ auto cls =
591
+ objc_allocateClassPair((Class) "NSResponder"_cls, "AppDelegate", 0);
592
+ class_addProtocol(cls, objc_getProtocol("NSTouchBarProvider"));
593
+ class_addMethod(cls, "applicationShouldTerminateAfterLastWindowClosed:"_sel,
594
+ (IMP)(+[](id, SEL, id) -> BOOL { return 1; }), "c@:@");
595
+ class_addMethod(cls, "userContentController:didReceiveScriptMessage:"_sel,
596
+ (IMP)(+[](id self, SEL, id, id msg) {
597
+ auto w =
598
+ (cocoa_wkwebview_engine *)objc_getAssociatedObject(
599
+ self, "webview");
600
+ assert(w);
601
+ w->on_message(((const char *(*)(id, SEL))objc_msgSend)(
602
+ ((id(*)(id, SEL))objc_msgSend)(msg, "body"_sel),
603
+ "UTF8String"_sel));
604
+ }),
605
+ "v@:@@");
606
+ objc_registerClassPair(cls);
607
+
608
+ auto delegate = ((id(*)(id, SEL))objc_msgSend)((id)cls, "new"_sel);
609
+ objc_setAssociatedObject(delegate, "webview", (id)this,
610
+ OBJC_ASSOCIATION_ASSIGN);
611
+ ((void (*)(id, SEL, id))objc_msgSend)(app, sel_registerName("setDelegate:"),
612
+ delegate);
613
+
614
+ // Main window
615
+ if (window == nullptr) {
616
+ m_window = ((id(*)(id, SEL))objc_msgSend)("NSWindow"_cls, "alloc"_sel);
617
+ m_window =
618
+ ((id(*)(id, SEL, CGRect, int, unsigned long, int))objc_msgSend)(
619
+ m_window, "initWithContentRect:styleMask:backing:defer:"_sel,
620
+ CGRectMake(0, 0, 0, 0), 0, NSBackingStoreBuffered, 0);
621
+ } else {
622
+ m_window = (id)window;
623
+ }
624
+
625
+ // Webview
626
+ auto config =
627
+ ((id(*)(id, SEL))objc_msgSend)("WKWebViewConfiguration"_cls, "new"_sel);
628
+ m_manager =
629
+ ((id(*)(id, SEL))objc_msgSend)(config, "userContentController"_sel);
630
+ m_webview = ((id(*)(id, SEL))objc_msgSend)("WKWebView"_cls, "alloc"_sel);
631
+
632
+ if (debug) {
633
+ // Equivalent Obj-C:
634
+ // [[config preferences] setValue:@YES forKey:@"developerExtrasEnabled"];
635
+ ((id(*)(id, SEL, id, id))objc_msgSend)(
636
+ ((id(*)(id, SEL))objc_msgSend)(config, "preferences"_sel),
637
+ "setValue:forKey:"_sel,
638
+ ((id(*)(id, SEL, BOOL))objc_msgSend)("NSNumber"_cls,
639
+ "numberWithBool:"_sel, 1),
640
+ "developerExtrasEnabled"_str);
641
+ }
642
+
643
+ // Equivalent Obj-C:
644
+ // [[config preferences] setValue:@YES forKey:@"fullScreenEnabled"];
645
+ ((id(*)(id, SEL, id, id))objc_msgSend)(
646
+ ((id(*)(id, SEL))objc_msgSend)(config, "preferences"_sel),
647
+ "setValue:forKey:"_sel,
648
+ ((id(*)(id, SEL, BOOL))objc_msgSend)("NSNumber"_cls,
649
+ "numberWithBool:"_sel, 1),
650
+ "fullScreenEnabled"_str);
651
+
652
+ // Equivalent Obj-C:
653
+ // [[config preferences] setValue:@YES forKey:@"javaScriptCanAccessClipboard"];
654
+ ((id(*)(id, SEL, id, id))objc_msgSend)(
655
+ ((id(*)(id, SEL))objc_msgSend)(config, "preferences"_sel),
656
+ "setValue:forKey:"_sel,
657
+ ((id(*)(id, SEL, BOOL))objc_msgSend)("NSNumber"_cls,
658
+ "numberWithBool:"_sel, 1),
659
+ "javaScriptCanAccessClipboard"_str);
660
+
661
+ // Equivalent Obj-C:
662
+ // [[config preferences] setValue:@YES forKey:@"DOMPasteAllowed"];
663
+ ((id(*)(id, SEL, id, id))objc_msgSend)(
664
+ ((id(*)(id, SEL))objc_msgSend)(config, "preferences"_sel),
665
+ "setValue:forKey:"_sel,
666
+ ((id(*)(id, SEL, BOOL))objc_msgSend)("NSNumber"_cls,
667
+ "numberWithBool:"_sel, 1),
668
+ "DOMPasteAllowed"_str);
669
+
670
+ ((void (*)(id, SEL, CGRect, id))objc_msgSend)(
671
+ m_webview, "initWithFrame:configuration:"_sel, CGRectMake(0, 0, 0, 0),
672
+ config);
673
+ ((void (*)(id, SEL, id, id))objc_msgSend)(
674
+ m_manager, "addScriptMessageHandler:name:"_sel, delegate,
675
+ "external"_str);
676
+
677
+ init(R"script(
678
+ window.external = {
679
+ invoke: function(s) {
680
+ window.webkit.messageHandlers.external.postMessage(s);
681
+ },
682
+ };
683
+ )script");
684
+ ((void (*)(id, SEL, id))objc_msgSend)(m_window, "setContentView:"_sel,
685
+ m_webview);
686
+ ((void (*)(id, SEL, id))objc_msgSend)(m_window, "makeKeyAndOrderFront:"_sel,
687
+ nullptr);
688
+ }
689
+ ~cocoa_wkwebview_engine() { close(); }
690
+ void *window() { return (void *)m_window; }
691
+ void terminate() {
692
+ close();
693
+ ((void (*)(id, SEL, id))objc_msgSend)("NSApp"_cls, "terminate:"_sel,
694
+ nullptr);
695
+ }
696
+ void run() {
697
+ id app = ((id(*)(id, SEL))objc_msgSend)("NSApplication"_cls,
698
+ "sharedApplication"_sel);
699
+ dispatch([&]() {
700
+ ((void (*)(id, SEL, BOOL))objc_msgSend)(
701
+ app, "activateIgnoringOtherApps:"_sel, 1);
702
+ });
703
+ ((void (*)(id, SEL))objc_msgSend)(app, "run"_sel);
704
+ }
705
+ void dispatch(std::function<void()> f) {
706
+ dispatch_async_f(dispatch_get_main_queue(), new dispatch_fn_t(f),
707
+ (dispatch_function_t)([](void *arg) {
708
+ auto f = static_cast<dispatch_fn_t *>(arg);
709
+ (*f)();
710
+ delete f;
711
+ }));
712
+ }
713
+ void set_title(const std::string title) {
714
+ ((void (*)(id, SEL, id))objc_msgSend)(
715
+ m_window, "setTitle:"_sel,
716
+ ((id(*)(id, SEL, const char *))objc_msgSend)(
717
+ "NSString"_cls, "stringWithUTF8String:"_sel, title.c_str()));
718
+ }
719
+ void set_size(int width, int height, int hints) {
720
+ auto style = NSWindowStyleMaskTitled | NSWindowStyleMaskClosable |
721
+ NSWindowStyleMaskMiniaturizable;
722
+ if (hints != WEBVIEW_HINT_FIXED) {
723
+ style = style | NSWindowStyleMaskResizable;
724
+ }
725
+ ((void (*)(id, SEL, unsigned long))objc_msgSend)(
726
+ m_window, "setStyleMask:"_sel, style);
727
+
728
+ if (hints == WEBVIEW_HINT_MIN) {
729
+ ((void (*)(id, SEL, CGSize))objc_msgSend)(
730
+ m_window, "setContentMinSize:"_sel, CGSizeMake(width, height));
731
+ } else if (hints == WEBVIEW_HINT_MAX) {
732
+ ((void (*)(id, SEL, CGSize))objc_msgSend)(
733
+ m_window, "setContentMaxSize:"_sel, CGSizeMake(width, height));
734
+ } else {
735
+ ((void (*)(id, SEL, CGRect, BOOL, BOOL))objc_msgSend)(
736
+ m_window, "setFrame:display:animate:"_sel,
737
+ CGRectMake(0, 0, width, height), 1, 0);
738
+ }
739
+ ((void (*)(id, SEL))objc_msgSend)(m_window, "center"_sel);
740
+ }
741
+ void navigate(const std::string url) {
742
+ auto nsurl = ((id(*)(id, SEL, id))objc_msgSend)(
743
+ "NSURL"_cls, "URLWithString:"_sel,
744
+ ((id(*)(id, SEL, const char *))objc_msgSend)(
745
+ "NSString"_cls, "stringWithUTF8String:"_sel, url.c_str()));
746
+
747
+ ((void (*)(id, SEL, id))objc_msgSend)(
748
+ m_webview, "loadRequest:"_sel,
749
+ ((id(*)(id, SEL, id))objc_msgSend)("NSURLRequest"_cls,
750
+ "requestWithURL:"_sel, nsurl));
751
+ }
752
+ void init(const std::string js) {
753
+ // Equivalent Obj-C:
754
+ // [m_manager addUserScript:[[WKUserScript alloc] initWithSource:[NSString stringWithUTF8String:js.c_str()] injectionTime:WKUserScriptInjectionTimeAtDocumentStart forMainFrameOnly:YES]]
755
+ ((void (*)(id, SEL, id))objc_msgSend)(
756
+ m_manager, "addUserScript:"_sel,
757
+ ((id(*)(id, SEL, id, long, BOOL))objc_msgSend)(
758
+ ((id(*)(id, SEL))objc_msgSend)("WKUserScript"_cls, "alloc"_sel),
759
+ "initWithSource:injectionTime:forMainFrameOnly:"_sel,
760
+ ((id(*)(id, SEL, const char *))objc_msgSend)(
761
+ "NSString"_cls, "stringWithUTF8String:"_sel, js.c_str()),
762
+ WKUserScriptInjectionTimeAtDocumentStart, 1));
763
+ }
764
+ void eval(const std::string js) {
765
+ ((void (*)(id, SEL, id, id))objc_msgSend)(
766
+ m_webview, "evaluateJavaScript:completionHandler:"_sel,
767
+ ((id(*)(id, SEL, const char *))objc_msgSend)(
768
+ "NSString"_cls, "stringWithUTF8String:"_sel, js.c_str()),
769
+ nullptr);
770
+ }
771
+
772
+ private:
773
+ virtual void on_message(const std::string msg) = 0;
774
+ void close() { ((void (*)(id, SEL))objc_msgSend)(m_window, "close"_sel); }
775
+ id m_window;
776
+ id m_webview;
777
+ id m_manager;
778
+ };
779
+
780
+ using browser_engine = cocoa_wkwebview_engine;
781
+
782
+ } // namespace webview
783
+
784
+ #elif defined(WEBVIEW_EDGE)
785
+
786
+ //
787
+ // ====================================================================
788
+ //
789
+ // This implementation uses Win32 API to create a native window. It can
790
+ // use either EdgeHTML or Edge/Chromium backend as a browser engine.
791
+ //
792
+ // ====================================================================
793
+ //
794
+
795
+ #define WIN32_LEAN_AND_MEAN
796
+ #include <Shlwapi.h>
797
+ #include <codecvt>
798
+ #include <stdlib.h>
799
+ #include <windows.h>
800
+
801
+ #pragma comment(lib, "user32.lib")
802
+ #pragma comment(lib, "Shlwapi.lib")
803
+
804
+ // EdgeHTML headers and libs
805
+ #include <objbase.h>
806
+ #include <winrt/Windows.Foundation.Collections.h>
807
+ #include <winrt/Windows.Foundation.h>
808
+ #include <winrt/Windows.Web.UI.Interop.h>
809
+ #pragma comment(lib, "windowsapp")
810
+
811
+ // Edge/Chromium headers and libs
812
+ #include "webview2.h"
813
+ #pragma comment(lib, "ole32.lib")
814
+ #pragma comment(lib, "oleaut32.lib")
815
+
816
+ namespace webview {
817
+
818
+ using msg_cb_t = std::function<void(const std::string)>;
819
+
820
+ // Common interface for EdgeHTML and Edge/Chromium
821
+ class browser {
822
+ public:
823
+ virtual ~browser() = default;
824
+ virtual bool embed(HWND, bool, msg_cb_t) = 0;
825
+ virtual void navigate(const std::string url) = 0;
826
+ virtual void eval(const std::string js) = 0;
827
+ virtual void init(const std::string js) = 0;
828
+ virtual void resize(HWND) = 0;
829
+ };
830
+
831
+ //
832
+ // EdgeHTML browser engine
833
+ //
834
+ using namespace winrt;
835
+ using namespace Windows::Foundation;
836
+ using namespace Windows::Web::UI;
837
+ using namespace Windows::Web::UI::Interop;
838
+
839
+ class edge_html : public browser {
840
+ public:
841
+ bool embed(HWND wnd, bool debug, msg_cb_t cb) override {
842
+ init_apartment(winrt::apartment_type::single_threaded);
843
+ auto process = WebViewControlProcess();
844
+ auto op = process.CreateWebViewControlAsync(reinterpret_cast<int64_t>(wnd),
845
+ Rect());
846
+ if (op.Status() != AsyncStatus::Completed) {
847
+ handle h(CreateEvent(nullptr, false, false, nullptr));
848
+ op.Completed([h = h.get()](auto, auto) { SetEvent(h); });
849
+ HANDLE hs[] = {h.get()};
850
+ DWORD i;
851
+ CoWaitForMultipleHandles(COWAIT_DISPATCH_WINDOW_MESSAGES |
852
+ COWAIT_DISPATCH_CALLS |
853
+ COWAIT_INPUTAVAILABLE,
854
+ INFINITE, 1, hs, &i);
855
+ }
856
+ m_webview = op.GetResults();
857
+ m_webview.Settings().IsScriptNotifyAllowed(true);
858
+ m_webview.IsVisible(true);
859
+ m_webview.ScriptNotify([=](auto const &sender, auto const &args) {
860
+ std::string s = winrt::to_string(args.Value());
861
+ cb(s.c_str());
862
+ });
863
+ m_webview.NavigationStarting([=](auto const &sender, auto const &args) {
864
+ m_webview.AddInitializeScript(winrt::to_hstring(init_js));
865
+ });
866
+ init("window.external.invoke = s => window.external.notify(s)");
867
+ return true;
868
+ }
869
+
870
+ void navigate(const std::string url) override {
871
+ std::string html = html_from_uri(url);
872
+ if (html != "") {
873
+ m_webview.NavigateToString(winrt::to_hstring(html));
874
+ } else {
875
+ Uri uri(winrt::to_hstring(url));
876
+ m_webview.Navigate(uri);
877
+ }
878
+ }
879
+
880
+ void init(const std::string js) override {
881
+ init_js = init_js + "(function(){" + js + "})();";
882
+ }
883
+
884
+ void eval(const std::string js) override {
885
+ m_webview.InvokeScriptAsync(
886
+ L"eval", single_threaded_vector<hstring>({winrt::to_hstring(js)}));
887
+ }
888
+
889
+ void resize(HWND wnd) override {
890
+ if (m_webview == nullptr) {
891
+ return;
892
+ }
893
+ RECT r;
894
+ GetClientRect(wnd, &r);
895
+ Rect bounds(r.left, r.top, r.right - r.left, r.bottom - r.top);
896
+ m_webview.Bounds(bounds);
897
+ }
898
+
899
+ private:
900
+ WebViewControl m_webview = nullptr;
901
+ std::string init_js = "";
902
+ };
903
+
904
+ //
905
+ // Edge/Chromium browser engine
906
+ //
907
+ class edge_chromium : public browser {
908
+ public:
909
+ bool embed(HWND wnd, bool debug, msg_cb_t cb) override {
910
+ CoInitializeEx(nullptr, 0);
911
+ std::atomic_flag flag = ATOMIC_FLAG_INIT;
912
+ flag.test_and_set();
913
+
914
+ char currentExePath[MAX_PATH];
915
+ GetModuleFileNameA(NULL, currentExePath, MAX_PATH);
916
+ char *currentExeName = PathFindFileNameA(currentExePath);
917
+
918
+ std::wstring_convert<std::codecvt_utf8_utf16<wchar_t>> wideCharConverter;
919
+ std::wstring userDataFolder =
920
+ wideCharConverter.from_bytes(std::getenv("APPDATA"));
921
+ std::wstring currentExeNameW = wideCharConverter.from_bytes(currentExeName);
922
+
923
+ HRESULT res = CreateCoreWebView2EnvironmentWithOptions(
924
+ nullptr, (userDataFolder + L"/" + currentExeNameW).c_str(), nullptr,
925
+ new webview2_com_handler(wnd, cb,
926
+ [&](ICoreWebView2Controller *controller) {
927
+ m_controller = controller;
928
+ m_controller->get_CoreWebView2(&m_webview);
929
+ m_webview->AddRef();
930
+ flag.clear();
931
+ }));
932
+ if (res != S_OK) {
933
+ CoUninitialize();
934
+ return false;
935
+ }
936
+ MSG msg = {};
937
+ while (flag.test_and_set() && GetMessage(&msg, NULL, 0, 0)) {
938
+ TranslateMessage(&msg);
939
+ DispatchMessage(&msg);
940
+ }
941
+ init("window.external={invoke:s=>window.chrome.webview.postMessage(s)}");
942
+ return true;
943
+ }
944
+
945
+ void resize(HWND wnd) override {
946
+ if (m_controller == nullptr) {
947
+ return;
948
+ }
949
+ RECT bounds;
950
+ GetClientRect(wnd, &bounds);
951
+ m_controller->put_Bounds(bounds);
952
+ }
953
+
954
+ void navigate(const std::string url) override {
955
+ auto wurl = to_lpwstr(url);
956
+ m_webview->Navigate(wurl);
957
+ delete[] wurl;
958
+ }
959
+
960
+ void init(const std::string js) override {
961
+ LPCWSTR wjs = to_lpwstr(js);
962
+ m_webview->AddScriptToExecuteOnDocumentCreated(wjs, nullptr);
963
+ delete[] wjs;
964
+ }
965
+
966
+ void eval(const std::string js) override {
967
+ LPCWSTR wjs = to_lpwstr(js);
968
+ m_webview->ExecuteScript(wjs, nullptr);
969
+ delete[] wjs;
970
+ }
971
+
972
+ private:
973
+ LPWSTR to_lpwstr(const std::string s) {
974
+ int n = MultiByteToWideChar(CP_UTF8, 0, s.c_str(), -1, NULL, 0);
975
+ wchar_t *ws = new wchar_t[n];
976
+ MultiByteToWideChar(CP_UTF8, 0, s.c_str(), -1, ws, n);
977
+ return ws;
978
+ }
979
+
980
+ ICoreWebView2 *m_webview = nullptr;
981
+ ICoreWebView2Controller *m_controller = nullptr;
982
+
983
+ class webview2_com_handler
984
+ : public ICoreWebView2CreateCoreWebView2EnvironmentCompletedHandler,
985
+ public ICoreWebView2CreateCoreWebView2ControllerCompletedHandler,
986
+ public ICoreWebView2WebMessageReceivedEventHandler,
987
+ public ICoreWebView2PermissionRequestedEventHandler {
988
+ using webview2_com_handler_cb_t =
989
+ std::function<void(ICoreWebView2Controller *)>;
990
+
991
+ public:
992
+ webview2_com_handler(HWND hwnd, msg_cb_t msgCb,
993
+ webview2_com_handler_cb_t cb)
994
+ : m_window(hwnd), m_msgCb(msgCb), m_cb(cb) {}
995
+ ULONG STDMETHODCALLTYPE AddRef() { return 1; }
996
+ ULONG STDMETHODCALLTYPE Release() { return 1; }
997
+ HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, LPVOID *ppv) {
998
+ return S_OK;
999
+ }
1000
+ HRESULT STDMETHODCALLTYPE Invoke(HRESULT res,
1001
+ ICoreWebView2Environment *env) {
1002
+ env->CreateCoreWebView2Controller(m_window, this);
1003
+ return S_OK;
1004
+ }
1005
+ HRESULT STDMETHODCALLTYPE Invoke(HRESULT res,
1006
+ ICoreWebView2Controller *controller) {
1007
+ controller->AddRef();
1008
+
1009
+ ICoreWebView2 *webview;
1010
+ ::EventRegistrationToken token;
1011
+ controller->get_CoreWebView2(&webview);
1012
+ webview->add_WebMessageReceived(this, &token);
1013
+ webview->add_PermissionRequested(this, &token);
1014
+
1015
+ m_cb(controller);
1016
+ return S_OK;
1017
+ }
1018
+ HRESULT STDMETHODCALLTYPE Invoke(
1019
+ ICoreWebView2 *sender, ICoreWebView2WebMessageReceivedEventArgs *args) {
1020
+ LPWSTR message;
1021
+ args->TryGetWebMessageAsString(&message);
1022
+
1023
+ std::wstring_convert<std::codecvt_utf8_utf16<wchar_t>> wideCharConverter;
1024
+ m_msgCb(wideCharConverter.to_bytes(message));
1025
+ sender->PostWebMessageAsString(message);
1026
+
1027
+ CoTaskMemFree(message);
1028
+ return S_OK;
1029
+ }
1030
+ HRESULT STDMETHODCALLTYPE
1031
+ Invoke(ICoreWebView2 *sender,
1032
+ ICoreWebView2PermissionRequestedEventArgs *args) {
1033
+ COREWEBVIEW2_PERMISSION_KIND kind;
1034
+ args->get_PermissionKind(&kind);
1035
+ if (kind == COREWEBVIEW2_PERMISSION_KIND_CLIPBOARD_READ) {
1036
+ args->put_State(COREWEBVIEW2_PERMISSION_STATE_ALLOW);
1037
+ }
1038
+ return S_OK;
1039
+ }
1040
+
1041
+ private:
1042
+ HWND m_window;
1043
+ msg_cb_t m_msgCb;
1044
+ webview2_com_handler_cb_t m_cb;
1045
+ };
1046
+ };
1047
+
1048
+ class win32_edge_engine {
1049
+ public:
1050
+ win32_edge_engine(bool debug, void *window) {
1051
+ if (window == nullptr) {
1052
+ HINSTANCE hInstance = GetModuleHandle(nullptr);
1053
+ HICON icon = (HICON)LoadImage(
1054
+ hInstance, IDI_APPLICATION, IMAGE_ICON, GetSystemMetrics(SM_CXSMICON),
1055
+ GetSystemMetrics(SM_CYSMICON), LR_DEFAULTCOLOR);
1056
+
1057
+ WNDCLASSEX wc;
1058
+ ZeroMemory(&wc, sizeof(WNDCLASSEX));
1059
+ wc.cbSize = sizeof(WNDCLASSEX);
1060
+ wc.hInstance = hInstance;
1061
+ wc.lpszClassName = "webview";
1062
+ wc.hIcon = icon;
1063
+ wc.hIconSm = icon;
1064
+ wc.lpfnWndProc =
1065
+ (WNDPROC)(+[](HWND hwnd, UINT msg, WPARAM wp, LPARAM lp) -> int {
1066
+ auto w = (win32_edge_engine *)GetWindowLongPtr(hwnd, GWLP_USERDATA);
1067
+ switch (msg) {
1068
+ case WM_SIZE:
1069
+ w->m_browser->resize(hwnd);
1070
+ break;
1071
+ case WM_CLOSE:
1072
+ DestroyWindow(hwnd);
1073
+ break;
1074
+ case WM_DESTROY:
1075
+ w->terminate();
1076
+ break;
1077
+ case WM_GETMINMAXINFO: {
1078
+ auto lpmmi = (LPMINMAXINFO)lp;
1079
+ if (w == nullptr) {
1080
+ return 0;
1081
+ }
1082
+ if (w->m_maxsz.x > 0 && w->m_maxsz.y > 0) {
1083
+ lpmmi->ptMaxSize = w->m_maxsz;
1084
+ lpmmi->ptMaxTrackSize = w->m_maxsz;
1085
+ }
1086
+ if (w->m_minsz.x > 0 && w->m_minsz.y > 0) {
1087
+ lpmmi->ptMinTrackSize = w->m_minsz;
1088
+ }
1089
+ } break;
1090
+ default:
1091
+ return DefWindowProc(hwnd, msg, wp, lp);
1092
+ }
1093
+ return 0;
1094
+ });
1095
+ RegisterClassEx(&wc);
1096
+ m_window = CreateWindow("webview", "", WS_OVERLAPPEDWINDOW, CW_USEDEFAULT,
1097
+ CW_USEDEFAULT, 640, 480, nullptr, nullptr,
1098
+ GetModuleHandle(nullptr), nullptr);
1099
+ SetWindowLongPtr(m_window, GWLP_USERDATA, (LONG_PTR)this);
1100
+ } else {
1101
+ m_window = *(static_cast<HWND *>(window));
1102
+ }
1103
+
1104
+ SetProcessDpiAwarenessContext(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE);
1105
+ ShowWindow(m_window, SW_SHOW);
1106
+ UpdateWindow(m_window);
1107
+ SetFocus(m_window);
1108
+
1109
+ auto cb =
1110
+ std::bind(&win32_edge_engine::on_message, this, std::placeholders::_1);
1111
+
1112
+ if (!m_browser->embed(m_window, debug, cb)) {
1113
+ m_browser = std::make_unique<webview::edge_html>();
1114
+ m_browser->embed(m_window, debug, cb);
1115
+ }
1116
+
1117
+ m_browser->resize(m_window);
1118
+ }
1119
+
1120
+ void run() {
1121
+ MSG msg;
1122
+ BOOL res;
1123
+ while ((res = GetMessage(&msg, nullptr, 0, 0)) != -1) {
1124
+ if (msg.hwnd) {
1125
+ TranslateMessage(&msg);
1126
+ DispatchMessage(&msg);
1127
+ continue;
1128
+ }
1129
+ if (msg.message == WM_APP) {
1130
+ auto f = (dispatch_fn_t *)(msg.lParam);
1131
+ (*f)();
1132
+ delete f;
1133
+ } else if (msg.message == WM_QUIT) {
1134
+ return;
1135
+ }
1136
+ }
1137
+ }
1138
+ void *window() { return (void *)m_window; }
1139
+ void terminate() { PostQuitMessage(0); }
1140
+ void dispatch(dispatch_fn_t f) {
1141
+ PostThreadMessage(m_main_thread, WM_APP, 0, (LPARAM) new dispatch_fn_t(f));
1142
+ }
1143
+
1144
+ void set_title(const std::string title) {
1145
+ SetWindowText(m_window, title.c_str());
1146
+ }
1147
+
1148
+ void set_size(int width, int height, int hints) {
1149
+ auto style = GetWindowLong(m_window, GWL_STYLE);
1150
+ if (hints == WEBVIEW_HINT_FIXED) {
1151
+ style &= ~(WS_THICKFRAME | WS_MAXIMIZEBOX);
1152
+ } else {
1153
+ style |= (WS_THICKFRAME | WS_MAXIMIZEBOX);
1154
+ }
1155
+ SetWindowLong(m_window, GWL_STYLE, style);
1156
+
1157
+ if (hints == WEBVIEW_HINT_MAX) {
1158
+ m_maxsz.x = width;
1159
+ m_maxsz.y = height;
1160
+ } else if (hints == WEBVIEW_HINT_MIN) {
1161
+ m_minsz.x = width;
1162
+ m_minsz.y = height;
1163
+ } else {
1164
+ RECT r;
1165
+ r.left = r.top = 0;
1166
+ r.right = width;
1167
+ r.bottom = height;
1168
+ AdjustWindowRect(&r, WS_OVERLAPPEDWINDOW, 0);
1169
+ SetWindowPos(
1170
+ m_window, NULL, r.left, r.top, r.right - r.left, r.bottom - r.top,
1171
+ SWP_NOZORDER | SWP_NOACTIVATE | SWP_NOMOVE | SWP_FRAMECHANGED);
1172
+ m_browser->resize(m_window);
1173
+ }
1174
+ }
1175
+
1176
+ void navigate(const std::string url) { m_browser->navigate(url); }
1177
+ void eval(const std::string js) { m_browser->eval(js); }
1178
+ void init(const std::string js) { m_browser->init(js); }
1179
+
1180
+ private:
1181
+ virtual void on_message(const std::string msg) = 0;
1182
+
1183
+ HWND m_window;
1184
+ POINT m_minsz = POINT{0, 0};
1185
+ POINT m_maxsz = POINT{0, 0};
1186
+ DWORD m_main_thread = GetCurrentThreadId();
1187
+ std::unique_ptr<webview::browser> m_browser =
1188
+ std::make_unique<webview::edge_chromium>();
1189
+ };
1190
+
1191
+ using browser_engine = win32_edge_engine;
1192
+ } // namespace webview
1193
+
1194
+ #endif /* WEBVIEW_GTK, WEBVIEW_COCOA, WEBVIEW_EDGE */
1195
+
1196
+ namespace webview {
1197
+
1198
+ class webview : public browser_engine {
1199
+ public:
1200
+ webview(bool debug = false, void *wnd = nullptr)
1201
+ : browser_engine(debug, wnd) {}
1202
+
1203
+ void navigate(const std::string url) {
1204
+ if (url == "") {
1205
+ browser_engine::navigate("data:text/html," +
1206
+ url_encode("<html><body>Hello</body></html>"));
1207
+ return;
1208
+ }
1209
+ std::string html = html_from_uri(url);
1210
+ if (html != "") {
1211
+ browser_engine::navigate("data:text/html," + url_encode(html));
1212
+ } else {
1213
+ browser_engine::navigate(url);
1214
+ }
1215
+ }
1216
+
1217
+ using binding_t = std::function<void(std::string, std::string, void *)>;
1218
+ using binding_ctx_t = std::pair<binding_t *, void *>;
1219
+
1220
+ using sync_binding_t = std::function<std::string(std::string)>;
1221
+ using sync_binding_ctx_t = std::pair<webview *, sync_binding_t>;
1222
+
1223
+ void bind(const std::string name, sync_binding_t fn) {
1224
+ bind(
1225
+ name,
1226
+ [](std::string seq, std::string req, void *arg) {
1227
+ auto pair = static_cast<sync_binding_ctx_t *>(arg);
1228
+ pair->first->resolve(seq, 0, pair->second(req));
1229
+ },
1230
+ new sync_binding_ctx_t(this, fn));
1231
+ }
1232
+
1233
+ void bind(const std::string name, binding_t f, void *arg) {
1234
+ auto js = "(function() { var name = '" + name + "';" + R"(
1235
+ var RPC = window._rpc = (window._rpc || {nextSeq: 1});
1236
+ window[name] = function() {
1237
+ var seq = RPC.nextSeq++;
1238
+ var promise = new Promise(function(resolve, reject) {
1239
+ RPC[seq] = {
1240
+ resolve: resolve,
1241
+ reject: reject,
1242
+ };
1243
+ });
1244
+ window.external.invoke(JSON.stringify({
1245
+ id: seq,
1246
+ method: name,
1247
+ params: Array.prototype.slice.call(arguments),
1248
+ }));
1249
+ return promise;
1250
+ }
1251
+ })())";
1252
+ init(js);
1253
+ bindings[name] = new binding_ctx_t(new binding_t(f), arg);
1254
+ }
1255
+
1256
+ void resolve(const std::string seq, int status, const std::string result) {
1257
+ dispatch([=]() {
1258
+ if (status == 0) {
1259
+ eval("window._rpc[" + seq + "].resolve(" + result + "); window._rpc[" +
1260
+ seq + "] = undefined");
1261
+ } else {
1262
+ eval("window._rpc[" + seq + "].reject(" + result + "); window._rpc[" +
1263
+ seq + "] = undefined");
1264
+ }
1265
+ });
1266
+ }
1267
+
1268
+ private:
1269
+ void on_message(const std::string msg) {
1270
+ auto seq = json_parse(msg, "id", 0);
1271
+ auto name = json_parse(msg, "method", 0);
1272
+ auto args = json_parse(msg, "params", 0);
1273
+ if (bindings.find(name) == bindings.end()) {
1274
+ return;
1275
+ }
1276
+ auto fn = bindings[name];
1277
+ (*fn->first)(seq, args, fn->second);
1278
+ }
1279
+ std::map<std::string, binding_ctx_t *> bindings;
1280
+ };
1281
+ } // namespace webview
1282
+
1283
+ WEBVIEW_API webview_t webview_create(int debug, void *wnd) {
1284
+ return new webview::webview(debug, wnd);
1285
+ }
1286
+
1287
+ WEBVIEW_API void webview_destroy(webview_t w) {
1288
+ delete static_cast<webview::webview *>(w);
1289
+ }
1290
+
1291
+ WEBVIEW_API void webview_run(webview_t w) {
1292
+ static_cast<webview::webview *>(w)->run();
1293
+ }
1294
+
1295
+ WEBVIEW_API void webview_terminate(webview_t w) {
1296
+ static_cast<webview::webview *>(w)->terminate();
1297
+ }
1298
+
1299
+ WEBVIEW_API void webview_dispatch(webview_t w, void (*fn)(webview_t, void *),
1300
+ void *arg) {
1301
+ static_cast<webview::webview *>(w)->dispatch([=]() { fn(w, arg); });
1302
+ }
1303
+
1304
+ WEBVIEW_API void *webview_get_window(webview_t w) {
1305
+ return static_cast<webview::webview *>(w)->window();
1306
+ }
1307
+
1308
+ WEBVIEW_API void webview_set_title(webview_t w, const char *title) {
1309
+ static_cast<webview::webview *>(w)->set_title(title);
1310
+ }
1311
+
1312
+ WEBVIEW_API void webview_set_size(webview_t w, int width, int height,
1313
+ int hints) {
1314
+ static_cast<webview::webview *>(w)->set_size(width, height, hints);
1315
+ }
1316
+
1317
+ WEBVIEW_API void webview_navigate(webview_t w, const char *url) {
1318
+ static_cast<webview::webview *>(w)->navigate(url);
1319
+ }
1320
+
1321
+ WEBVIEW_API void webview_init(webview_t w, const char *js) {
1322
+ static_cast<webview::webview *>(w)->init(js);
1323
+ }
1324
+
1325
+ WEBVIEW_API void webview_eval(webview_t w, const char *js) {
1326
+ static_cast<webview::webview *>(w)->eval(js);
1327
+ }
1328
+
1329
+ WEBVIEW_API void webview_bind(webview_t w, const char *name,
1330
+ void (*fn)(const char *seq, const char *req,
1331
+ void *arg),
1332
+ void *arg) {
1333
+ static_cast<webview::webview *>(w)->bind(
1334
+ name,
1335
+ [=](std::string seq, std::string req, void *arg) {
1336
+ fn(seq.c_str(), req.c_str(), arg);
1337
+ },
1338
+ arg);
1339
+ }
1340
+
1341
+ WEBVIEW_API void webview_return(webview_t w, const char *seq, int status,
1342
+ const char *result) {
1343
+ static_cast<webview::webview *>(w)->resolve(seq, status, result);
1344
+ }
1345
+
1346
+ #endif /* WEBVIEW_HEADER */
1347
+
1348
+ #endif /* WEBVIEW_H */