janela 0.1.0

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.
Files changed (80) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +95 -0
  3. package/bin/janela.mjs +271 -0
  4. package/package.json +43 -0
  5. package/runtime/janela.ts +193 -0
  6. package/shim/wvshim.cc +201 -0
  7. package/templates/index.html +48 -0
  8. package/templates/janela.conf.json +10 -0
  9. package/templates/main.ts +37 -0
  10. package/vendor-webview/LICENSE +22 -0
  11. package/vendor-webview/core/include/webview/api.h +251 -0
  12. package/vendor-webview/core/include/webview/backends.hh +40 -0
  13. package/vendor-webview/core/include/webview/c_api_impl.hh +258 -0
  14. package/vendor-webview/core/include/webview/detail/backends/cocoa_webkit.hh +626 -0
  15. package/vendor-webview/core/include/webview/detail/backends/gtk_webkitgtk.hh +355 -0
  16. package/vendor-webview/core/include/webview/detail/backends/win32_edge.hh +909 -0
  17. package/vendor-webview/core/include/webview/detail/basic_result.hh +119 -0
  18. package/vendor-webview/core/include/webview/detail/engine_base.hh +382 -0
  19. package/vendor-webview/core/include/webview/detail/exceptions.hh +42 -0
  20. package/vendor-webview/core/include/webview/detail/json.hh +337 -0
  21. package/vendor-webview/core/include/webview/detail/native_library.hh +170 -0
  22. package/vendor-webview/core/include/webview/detail/optional.hh +118 -0
  23. package/vendor-webview/core/include/webview/detail/platform/darwin/cocoa/NSApplication.hh +107 -0
  24. package/vendor-webview/core/include/webview/detail/platform/darwin/cocoa/NSBundle.hh +56 -0
  25. package/vendor-webview/core/include/webview/detail/platform/darwin/cocoa/NSEvent.hh +71 -0
  26. package/vendor-webview/core/include/webview/detail/platform/darwin/cocoa/NSInvocation.hh +66 -0
  27. package/vendor-webview/core/include/webview/detail/platform/darwin/cocoa/NSMethodSignature.hh +52 -0
  28. package/vendor-webview/core/include/webview/detail/platform/darwin/cocoa/NSNotification.hh +51 -0
  29. package/vendor-webview/core/include/webview/detail/platform/darwin/cocoa/NSNumber.hh +53 -0
  30. package/vendor-webview/core/include/webview/detail/platform/darwin/cocoa/NSObject.hh +51 -0
  31. package/vendor-webview/core/include/webview/detail/platform/darwin/cocoa/NSOpenPanel.hh +71 -0
  32. package/vendor-webview/core/include/webview/detail/platform/darwin/cocoa/NSPoint.hh +53 -0
  33. package/vendor-webview/core/include/webview/detail/platform/darwin/cocoa/NSRect.hh +53 -0
  34. package/vendor-webview/core/include/webview/detail/platform/darwin/cocoa/NSSavePanel.hh +52 -0
  35. package/vendor-webview/core/include/webview/detail/platform/darwin/cocoa/NSSize.hh +53 -0
  36. package/vendor-webview/core/include/webview/detail/platform/darwin/cocoa/NSString.hh +87 -0
  37. package/vendor-webview/core/include/webview/detail/platform/darwin/cocoa/NSURL.hh +63 -0
  38. package/vendor-webview/core/include/webview/detail/platform/darwin/cocoa/NSURLRequest.hh +52 -0
  39. package/vendor-webview/core/include/webview/detail/platform/darwin/cocoa/NSValue.hh +56 -0
  40. package/vendor-webview/core/include/webview/detail/platform/darwin/cocoa/NSView.hh +91 -0
  41. package/vendor-webview/core/include/webview/detail/platform/darwin/cocoa/NSWindow.hh +137 -0
  42. package/vendor-webview/core/include/webview/detail/platform/darwin/cocoa/cocoa.hh +59 -0
  43. package/vendor-webview/core/include/webview/detail/platform/darwin/cocoa/types.hh +49 -0
  44. package/vendor-webview/core/include/webview/detail/platform/darwin/objc/Class.hh +55 -0
  45. package/vendor-webview/core/include/webview/detail/platform/darwin/objc/autoreleasepool.hh +70 -0
  46. package/vendor-webview/core/include/webview/detail/platform/darwin/objc/invoke.hh +75 -0
  47. package/vendor-webview/core/include/webview/detail/platform/darwin/objc/memory.hh +57 -0
  48. package/vendor-webview/core/include/webview/detail/platform/darwin/objc/objc.hh +47 -0
  49. package/vendor-webview/core/include/webview/detail/platform/darwin/webkit/WKOpenPanelParameters.hh +57 -0
  50. package/vendor-webview/core/include/webview/detail/platform/darwin/webkit/WKScriptMessage.hh +51 -0
  51. package/vendor-webview/core/include/webview/detail/platform/darwin/webkit/WKUserContentController.hh +61 -0
  52. package/vendor-webview/core/include/webview/detail/platform/darwin/webkit/WKUserScript.hh +71 -0
  53. package/vendor-webview/core/include/webview/detail/platform/darwin/webkit/WKWebView.hh +108 -0
  54. package/vendor-webview/core/include/webview/detail/platform/darwin/webkit/WKWebViewConfiguration.hh +60 -0
  55. package/vendor-webview/core/include/webview/detail/platform/darwin/webkit/webkit.hh +46 -0
  56. package/vendor-webview/core/include/webview/detail/platform/linux/gtk/compat.hh +137 -0
  57. package/vendor-webview/core/include/webview/detail/platform/linux/webkitgtk/compat.hh +142 -0
  58. package/vendor-webview/core/include/webview/detail/platform/linux/webkitgtk/dmabuf.hh +167 -0
  59. package/vendor-webview/core/include/webview/detail/platform/windows/com_init_wrapper.hh +126 -0
  60. package/vendor-webview/core/include/webview/detail/platform/windows/dpi.hh +161 -0
  61. package/vendor-webview/core/include/webview/detail/platform/windows/dwmapi.hh +67 -0
  62. package/vendor-webview/core/include/webview/detail/platform/windows/iid.hh +78 -0
  63. package/vendor-webview/core/include/webview/detail/platform/windows/ntdll.hh +58 -0
  64. package/vendor-webview/core/include/webview/detail/platform/windows/reg_key.hh +135 -0
  65. package/vendor-webview/core/include/webview/detail/platform/windows/shcore.hh +59 -0
  66. package/vendor-webview/core/include/webview/detail/platform/windows/theme.hh +76 -0
  67. package/vendor-webview/core/include/webview/detail/platform/windows/user32.hh +90 -0
  68. package/vendor-webview/core/include/webview/detail/platform/windows/version.hh +151 -0
  69. package/vendor-webview/core/include/webview/detail/platform/windows/webview2/loader.hh +391 -0
  70. package/vendor-webview/core/include/webview/detail/user_script.hh +76 -0
  71. package/vendor-webview/core/include/webview/detail/utility/string.hh +99 -0
  72. package/vendor-webview/core/include/webview/errors.h +67 -0
  73. package/vendor-webview/core/include/webview/errors.hh +85 -0
  74. package/vendor-webview/core/include/webview/json_deprecated.hh +61 -0
  75. package/vendor-webview/core/include/webview/macros.h +123 -0
  76. package/vendor-webview/core/include/webview/types.h +81 -0
  77. package/vendor-webview/core/include/webview/types.hh +48 -0
  78. package/vendor-webview/core/include/webview/version.h +67 -0
  79. package/vendor-webview/core/include/webview/webview.h +32 -0
  80. package/vendor-webview/core/include/webview.h +15 -0
@@ -0,0 +1,909 @@
1
+ /*
2
+ * MIT License
3
+ *
4
+ * Copyright (c) 2017 Serge Zaitsev
5
+ * Copyright (c) 2022 Steffen André Langnes
6
+ *
7
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
8
+ * of this software and associated documentation files (the "Software"), to deal
9
+ * in the Software without restriction, including without limitation the rights
10
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11
+ * copies of the Software, and to permit persons to whom the Software is
12
+ * furnished to do so, subject to the following conditions:
13
+ *
14
+ * The above copyright notice and this permission notice shall be included in
15
+ * all copies or substantial portions of the Software.
16
+ *
17
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
23
+ * SOFTWARE.
24
+ */
25
+
26
+ #ifndef WEBVIEW_BACKENDS_WIN32_EDGE_HH
27
+ #define WEBVIEW_BACKENDS_WIN32_EDGE_HH
28
+
29
+ #if defined(__cplusplus) && !defined(WEBVIEW_HEADER)
30
+
31
+ #include "../../macros.h"
32
+
33
+ #if defined(WEBVIEW_PLATFORM_WINDOWS) && defined(WEBVIEW_EDGE)
34
+
35
+ //
36
+ // ====================================================================
37
+ //
38
+ // This implementation uses Win32 API to create a native window. It
39
+ // uses Edge/Chromium webview2 backend as a browser engine.
40
+ //
41
+ // ====================================================================
42
+ //
43
+
44
+ #include "../../errors.hh"
45
+ #include "../../types.hh"
46
+ #include "../engine_base.hh"
47
+ #include "../native_library.hh"
48
+ #include "../platform/windows/com_init_wrapper.hh"
49
+ #include "../platform/windows/dpi.hh"
50
+ #include "../platform/windows/iid.hh"
51
+ #include "../platform/windows/reg_key.hh"
52
+ #include "../platform/windows/theme.hh"
53
+ #include "../platform/windows/version.hh"
54
+ #include "../platform/windows/webview2/loader.hh"
55
+ #include "../user_script.hh"
56
+ #include "../utility/string.hh"
57
+
58
+ #include <atomic>
59
+ #include <cstdlib>
60
+ #include <functional>
61
+ #include <list>
62
+ #include <memory>
63
+ #include <utility>
64
+
65
+ #ifndef WIN32_LEAN_AND_MEAN
66
+ #define WIN32_LEAN_AND_MEAN
67
+ #endif
68
+
69
+ #include <windows.h>
70
+
71
+ #include <objbase.h>
72
+ #include <shlobj.h>
73
+ #include <shlwapi.h>
74
+
75
+ #ifdef _MSC_VER
76
+ #pragma comment(lib, "ole32.lib")
77
+ #pragma comment(lib, "shell32.lib")
78
+ #pragma comment(lib, "shlwapi.lib")
79
+ #pragma comment(lib, "user32.lib")
80
+ #pragma comment(lib, "version.lib")
81
+ #endif
82
+
83
+ namespace webview {
84
+ namespace detail {
85
+
86
+ using msg_cb_t = std::function<void(const std::string)>;
87
+
88
+ class webview2_com_handler
89
+ : public ICoreWebView2CreateCoreWebView2EnvironmentCompletedHandler,
90
+ public ICoreWebView2CreateCoreWebView2ControllerCompletedHandler,
91
+ public ICoreWebView2WebMessageReceivedEventHandler,
92
+ public ICoreWebView2PermissionRequestedEventHandler {
93
+ using webview2_com_handler_cb_t =
94
+ std::function<void(ICoreWebView2Controller *, ICoreWebView2 *webview)>;
95
+
96
+ public:
97
+ webview2_com_handler(HWND hwnd, msg_cb_t msgCb, webview2_com_handler_cb_t cb)
98
+ : m_window(hwnd), m_msgCb(msgCb), m_cb(cb) {}
99
+
100
+ virtual ~webview2_com_handler() = default;
101
+ webview2_com_handler(const webview2_com_handler &other) = delete;
102
+ webview2_com_handler &operator=(const webview2_com_handler &other) = delete;
103
+ webview2_com_handler(webview2_com_handler &&other) = delete;
104
+ webview2_com_handler &operator=(webview2_com_handler &&other) = delete;
105
+
106
+ ULONG STDMETHODCALLTYPE AddRef() { return ++m_ref_count; }
107
+ ULONG STDMETHODCALLTYPE Release() {
108
+ if (m_ref_count > 1) {
109
+ return --m_ref_count;
110
+ }
111
+ delete this;
112
+ return 0;
113
+ }
114
+ HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, LPVOID *ppv) {
115
+ using namespace mswebview2::cast_info;
116
+
117
+ if (!ppv) {
118
+ return E_POINTER;
119
+ }
120
+
121
+ // All of the COM interfaces we implement should be added here regardless
122
+ // of whether they are required.
123
+ // This is just to be on the safe side in case the WebView2 Runtime ever
124
+ // requests a pointer to an interface we implement.
125
+ // The WebView2 Runtime must at the very least be able to get a pointer to
126
+ // ICoreWebView2CreateCoreWebView2EnvironmentCompletedHandler when we use
127
+ // our custom WebView2 loader implementation, and observations have shown
128
+ // that it is the only interface requested in this case. None have been
129
+ // observed to be requested when using the official WebView2 loader.
130
+
131
+ if (cast_if_equal_iid(this, riid, controller_completed, ppv) ||
132
+ cast_if_equal_iid(this, riid, environment_completed, ppv) ||
133
+ cast_if_equal_iid(this, riid, message_received, ppv) ||
134
+ cast_if_equal_iid(this, riid, permission_requested, ppv)) {
135
+ return S_OK;
136
+ }
137
+
138
+ return E_NOINTERFACE;
139
+ }
140
+ HRESULT STDMETHODCALLTYPE Invoke(HRESULT res, ICoreWebView2Environment *env) {
141
+ if (SUCCEEDED(res)) {
142
+ res = env->CreateCoreWebView2Controller(m_window, this);
143
+ if (SUCCEEDED(res)) {
144
+ return S_OK;
145
+ }
146
+ }
147
+ try_create_environment();
148
+ return S_OK;
149
+ }
150
+ HRESULT STDMETHODCALLTYPE Invoke(HRESULT res,
151
+ ICoreWebView2Controller *controller) {
152
+ if (FAILED(res)) {
153
+ // See try_create_environment() regarding
154
+ // HRESULT_FROM_WIN32(ERROR_INVALID_STATE).
155
+ // The result is E_ABORT if the parent window has been destroyed already.
156
+ switch (res) {
157
+ case HRESULT_FROM_WIN32(ERROR_INVALID_STATE):
158
+ case E_ABORT:
159
+ return S_OK;
160
+ }
161
+ try_create_environment();
162
+ return S_OK;
163
+ }
164
+
165
+ ICoreWebView2 *webview;
166
+ ::EventRegistrationToken token;
167
+ controller->get_CoreWebView2(&webview);
168
+ webview->add_WebMessageReceived(this, &token);
169
+ webview->add_PermissionRequested(this, &token);
170
+
171
+ m_cb(controller, webview);
172
+ return S_OK;
173
+ }
174
+ HRESULT STDMETHODCALLTYPE
175
+ Invoke(ICoreWebView2 * /*sender*/,
176
+ ICoreWebView2WebMessageReceivedEventArgs *args) {
177
+ LPWSTR message{};
178
+ auto res = args->TryGetWebMessageAsString(&message);
179
+ if (SUCCEEDED(res)) {
180
+ m_msgCb(narrow_string(message));
181
+ }
182
+
183
+ CoTaskMemFree(message);
184
+ return S_OK;
185
+ }
186
+ HRESULT STDMETHODCALLTYPE
187
+ Invoke(ICoreWebView2 * /*sender*/,
188
+ ICoreWebView2PermissionRequestedEventArgs *args) {
189
+ COREWEBVIEW2_PERMISSION_KIND kind;
190
+ args->get_PermissionKind(&kind);
191
+ if (kind == COREWEBVIEW2_PERMISSION_KIND_CLIPBOARD_READ) {
192
+ args->put_State(COREWEBVIEW2_PERMISSION_STATE_ALLOW);
193
+ }
194
+ return S_OK;
195
+ }
196
+
197
+ // Set the function that will perform the initiating logic for creating
198
+ // the WebView2 environment.
199
+ void set_attempt_handler(std::function<HRESULT()> attempt_handler) noexcept {
200
+ m_attempt_handler = attempt_handler;
201
+ }
202
+
203
+ // Retry creating a WebView2 environment.
204
+ // The initiating logic for creating the environment is defined by the
205
+ // caller of set_attempt_handler().
206
+ void try_create_environment() noexcept {
207
+ // WebView creation fails with HRESULT_FROM_WIN32(ERROR_INVALID_STATE) if
208
+ // a running instance using the same user data folder exists, and the
209
+ // Environment objects have different EnvironmentOptions.
210
+ // Source: https://docs.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/icorewebview2environment?view=webview2-1.0.1150.38
211
+ if (m_attempts < m_max_attempts) {
212
+ ++m_attempts;
213
+ auto res = m_attempt_handler();
214
+ if (SUCCEEDED(res)) {
215
+ return;
216
+ }
217
+ // Not entirely sure if this error code only applies to
218
+ // CreateCoreWebView2Controller so we check here as well.
219
+ if (res == HRESULT_FROM_WIN32(ERROR_INVALID_STATE)) {
220
+ return;
221
+ }
222
+ // Wait for m_sleep_ms before trying again.
223
+ Sleep(m_sleep_ms);
224
+ try_create_environment();
225
+ return;
226
+ }
227
+ // Give up.
228
+ m_cb(nullptr, nullptr);
229
+ }
230
+
231
+ private:
232
+ HWND m_window;
233
+ msg_cb_t m_msgCb;
234
+ webview2_com_handler_cb_t m_cb;
235
+ std::atomic<ULONG> m_ref_count{1};
236
+ std::function<HRESULT()> m_attempt_handler;
237
+ unsigned int m_max_attempts = 60;
238
+ unsigned int m_sleep_ms = 200;
239
+ unsigned int m_attempts = 0;
240
+ };
241
+
242
+ class webview2_user_script_added_handler
243
+ : public ICoreWebView2AddScriptToExecuteOnDocumentCreatedCompletedHandler {
244
+ public:
245
+ using callback_fn = std::function<void(HRESULT errorCode, LPCWSTR id)>;
246
+
247
+ webview2_user_script_added_handler(callback_fn cb) : m_cb{cb} {}
248
+
249
+ virtual ~webview2_user_script_added_handler() = default;
250
+ webview2_user_script_added_handler(
251
+ const webview2_user_script_added_handler &other) = delete;
252
+ webview2_user_script_added_handler &
253
+ operator=(const webview2_user_script_added_handler &other) = delete;
254
+ webview2_user_script_added_handler(
255
+ webview2_user_script_added_handler &&other) = delete;
256
+ webview2_user_script_added_handler &
257
+ operator=(webview2_user_script_added_handler &&other) = delete;
258
+
259
+ ULONG STDMETHODCALLTYPE AddRef() { return ++m_ref_count; }
260
+ ULONG STDMETHODCALLTYPE Release() {
261
+ if (m_ref_count > 1) {
262
+ return --m_ref_count;
263
+ }
264
+ delete this;
265
+ return 0;
266
+ }
267
+
268
+ HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, LPVOID *ppv) {
269
+ using namespace mswebview2::cast_info;
270
+
271
+ if (!ppv) {
272
+ return E_POINTER;
273
+ }
274
+
275
+ if (cast_if_equal_iid(this, riid,
276
+ add_script_to_execute_on_document_created_completed,
277
+ ppv)) {
278
+ return S_OK;
279
+ }
280
+
281
+ return E_NOINTERFACE;
282
+ }
283
+
284
+ HRESULT STDMETHODCALLTYPE Invoke(HRESULT res, LPCWSTR id) {
285
+ m_cb(res, id);
286
+ return S_OK;
287
+ }
288
+
289
+ private:
290
+ callback_fn m_cb;
291
+ std::atomic<ULONG> m_ref_count{1};
292
+ };
293
+
294
+ class user_script::impl {
295
+ public:
296
+ impl(const std::wstring &id, const std::wstring &code)
297
+ : m_id{id}, m_code{code} {}
298
+
299
+ impl(const impl &) = delete;
300
+ impl &operator=(const impl &) = delete;
301
+ impl(impl &&) = delete;
302
+ impl &operator=(impl &&) = delete;
303
+
304
+ const std::wstring &get_id() const { return m_id; }
305
+ const std::wstring &get_code() const { return m_code; }
306
+
307
+ private:
308
+ std::wstring m_id;
309
+ std::wstring m_code;
310
+ };
311
+
312
+ class win32_edge_engine : public engine_base {
313
+ public:
314
+ win32_edge_engine(bool debug, void *window) : engine_base{!window} {
315
+ window_init(window);
316
+ window_settings(debug);
317
+ dispatch_size_default();
318
+ }
319
+
320
+ virtual ~win32_edge_engine() {
321
+ if (m_com_handler) {
322
+ m_com_handler->Release();
323
+ m_com_handler = nullptr;
324
+ }
325
+ if (m_webview) {
326
+ m_webview->Release();
327
+ m_webview = nullptr;
328
+ }
329
+ if (m_controller) {
330
+ m_controller->Release();
331
+ m_controller = nullptr;
332
+ }
333
+ // Replace wndproc to avoid callbacks and other bad things during
334
+ // destruction.
335
+ auto wndproc = reinterpret_cast<LONG_PTR>(
336
+ +[](HWND hwnd, UINT msg, WPARAM wp, LPARAM lp) -> LRESULT {
337
+ return DefWindowProcW(hwnd, msg, wp, lp);
338
+ });
339
+ if (m_widget) {
340
+ SetWindowLongPtrW(m_widget, GWLP_WNDPROC, wndproc);
341
+ }
342
+ if (m_window && owns_window()) {
343
+ SetWindowLongPtrW(m_window, GWLP_WNDPROC, wndproc);
344
+ }
345
+ if (m_widget) {
346
+ DestroyWindow(m_widget);
347
+ m_widget = nullptr;
348
+ }
349
+ if (m_window) {
350
+ if (owns_window()) {
351
+ DestroyWindow(m_window);
352
+ on_window_destroyed(true);
353
+ }
354
+ m_window = nullptr;
355
+ }
356
+ if (owns_window()) {
357
+ // Not strictly needed for windows to close immediately but aligns
358
+ // behavior across backends.
359
+ deplete_run_loop_event_queue();
360
+ }
361
+ // We need the message window in order to deplete the event queue.
362
+ if (m_message_window) {
363
+ SetWindowLongPtrW(m_message_window, GWLP_WNDPROC, wndproc);
364
+ DestroyWindow(m_message_window);
365
+ m_message_window = nullptr;
366
+ }
367
+ }
368
+
369
+ win32_edge_engine(const win32_edge_engine &other) = delete;
370
+ win32_edge_engine &operator=(const win32_edge_engine &other) = delete;
371
+ win32_edge_engine(win32_edge_engine &&other) = delete;
372
+ win32_edge_engine &operator=(win32_edge_engine &&other) = delete;
373
+
374
+ protected:
375
+ noresult run_impl() override {
376
+ MSG msg;
377
+ while (GetMessageW(&msg, nullptr, 0, 0) > 0) {
378
+ TranslateMessage(&msg);
379
+ DispatchMessageW(&msg);
380
+ }
381
+ return {};
382
+ }
383
+ result<void *> window_impl() override {
384
+ if (m_window) {
385
+ return m_window;
386
+ }
387
+ return error_info{WEBVIEW_ERROR_INVALID_STATE};
388
+ }
389
+ result<void *> widget_impl() override {
390
+ if (m_widget) {
391
+ return m_widget;
392
+ }
393
+ return error_info{WEBVIEW_ERROR_INVALID_STATE};
394
+ }
395
+ result<void *> browser_controller_impl() override {
396
+ if (m_controller) {
397
+ return m_controller;
398
+ }
399
+ return error_info{WEBVIEW_ERROR_INVALID_STATE};
400
+ }
401
+ noresult terminate_impl() override {
402
+ PostQuitMessage(0);
403
+ return {};
404
+ }
405
+ noresult dispatch_impl(dispatch_fn_t f) override {
406
+ PostMessageW(m_message_window, WM_APP, 0, (LPARAM) new dispatch_fn_t(f));
407
+ return {};
408
+ }
409
+
410
+ noresult set_title_impl(const std::string &title) override {
411
+ SetWindowTextW(m_window, widen_string(title).c_str());
412
+ return {};
413
+ }
414
+
415
+ noresult set_size_impl(int width, int height, webview_hint_t hints) override {
416
+ auto style = GetWindowLong(m_window, GWL_STYLE);
417
+ if (hints == WEBVIEW_HINT_FIXED) {
418
+ style &= ~(WS_THICKFRAME | WS_MAXIMIZEBOX);
419
+ } else {
420
+ style |= (WS_THICKFRAME | WS_MAXIMIZEBOX);
421
+ }
422
+ SetWindowLong(m_window, GWL_STYLE, style);
423
+
424
+ if (hints == WEBVIEW_HINT_MAX) {
425
+ m_maxsz.x = width;
426
+ m_maxsz.y = height;
427
+ } else if (hints == WEBVIEW_HINT_MIN) {
428
+ m_minsz.x = width;
429
+ m_minsz.y = height;
430
+ } else {
431
+ auto dpi = get_window_dpi(m_window);
432
+ m_dpi = dpi;
433
+ auto scaled_size =
434
+ scale_size(width, height, get_default_window_dpi(), dpi);
435
+ auto frame_size =
436
+ make_window_frame_size(m_window, scaled_size.cx, scaled_size.cy, dpi);
437
+ SetWindowPos(m_window, nullptr, 0, 0, frame_size.cx, frame_size.cy,
438
+ SWP_NOZORDER | SWP_NOACTIVATE | SWP_NOMOVE |
439
+ SWP_FRAMECHANGED);
440
+ }
441
+ return window_show();
442
+ }
443
+
444
+ noresult navigate_impl(const std::string &url) override {
445
+ auto wurl = widen_string(url);
446
+ m_webview->Navigate(wurl.c_str());
447
+ return {};
448
+ }
449
+
450
+ noresult eval_impl(const std::string &js) override {
451
+ // TODO: Skip if no content has begun loading yet. Can't check with
452
+ // ICoreWebView2::get_Source because it returns "about:blank".
453
+ auto wjs = widen_string(js);
454
+ m_webview->ExecuteScript(wjs.c_str(), nullptr);
455
+ return {};
456
+ }
457
+
458
+ noresult set_html_impl(const std::string &html) override {
459
+ m_webview->NavigateToString(widen_string(html).c_str());
460
+ return {};
461
+ }
462
+
463
+ user_script add_user_script_impl(const std::string &js) override {
464
+ auto wjs = widen_string(js);
465
+ std::wstring script_id;
466
+ bool done{};
467
+ webview2_user_script_added_handler handler{[&](HRESULT res, LPCWSTR id) {
468
+ if (SUCCEEDED(res)) {
469
+ script_id = id;
470
+ }
471
+ done = true;
472
+ }};
473
+ auto res =
474
+ m_webview->AddScriptToExecuteOnDocumentCreated(wjs.c_str(), &handler);
475
+ if (SUCCEEDED(res)) {
476
+ // We want to guard against executing the default `set_size` prematurely
477
+ set_default_size_guard(true);
478
+ // Sadly we need to pump the event loop in order to get the script ID.
479
+ run_event_loop_while([&] { return !done; });
480
+ // The user's `set_size` may have been executed from the depleted event queue,
481
+ // and if so, guard against putting the default `set_size` back onto the queue.
482
+ if (!m_is_window_shown) {
483
+ set_default_size_guard(false);
484
+ dispatch_size_default();
485
+ }
486
+ }
487
+ // TODO: There's a non-zero chance that we didn't get the script ID.
488
+ // We need to convey the error somehow.
489
+ return user_script{
490
+ js, user_script::impl_ptr{new user_script::impl{script_id, wjs},
491
+ [](user_script::impl *p) { delete p; }}};
492
+ }
493
+
494
+ void
495
+ remove_all_user_scripts_impl(const std::list<user_script> &scripts) override {
496
+ for (const auto &script : scripts) {
497
+ const auto &id = script.get_impl().get_id();
498
+ m_webview->RemoveScriptToExecuteOnDocumentCreated(id.c_str());
499
+ }
500
+ }
501
+
502
+ bool are_user_scripts_equal_impl(const user_script &first,
503
+ const user_script &second) override {
504
+ const auto &first_id = first.get_impl().get_id();
505
+ const auto &second_id = second.get_impl().get_id();
506
+ return first_id == second_id;
507
+ }
508
+
509
+ private:
510
+ void window_init(void *window) {
511
+ if (!is_webview2_available()) {
512
+ throw exception{WEBVIEW_ERROR_MISSING_DEPENDENCY,
513
+ "WebView2 is unavailable"};
514
+ }
515
+
516
+ HINSTANCE hInstance = GetModuleHandle(nullptr);
517
+
518
+ if (owns_window()) {
519
+ m_com_init = {COINIT_APARTMENTTHREADED};
520
+ enable_dpi_awareness();
521
+
522
+ HICON icon = (HICON)LoadImage(
523
+ hInstance, IDI_APPLICATION, IMAGE_ICON, GetSystemMetrics(SM_CXICON),
524
+ GetSystemMetrics(SM_CYICON), LR_DEFAULTCOLOR);
525
+
526
+ // Create a top-level window.
527
+ WNDCLASSEXW wc;
528
+ ZeroMemory(&wc, sizeof(WNDCLASSEX));
529
+ wc.cbSize = sizeof(WNDCLASSEX);
530
+ wc.hInstance = hInstance;
531
+ wc.lpszClassName = L"webview";
532
+ wc.hIcon = icon;
533
+ wc.lpfnWndProc = (WNDPROC)(+[](HWND hwnd, UINT msg, WPARAM wp,
534
+ LPARAM lp) -> LRESULT {
535
+ win32_edge_engine *w{};
536
+
537
+ if (msg == WM_NCCREATE) {
538
+ auto *lpcs{reinterpret_cast<LPCREATESTRUCT>(lp)};
539
+ w = static_cast<win32_edge_engine *>(lpcs->lpCreateParams);
540
+ w->m_window = hwnd;
541
+ SetWindowLongPtrW(hwnd, GWLP_USERDATA, reinterpret_cast<LONG_PTR>(w));
542
+ enable_non_client_dpi_scaling_if_needed(hwnd);
543
+ apply_window_theme(hwnd);
544
+ } else {
545
+ w = reinterpret_cast<win32_edge_engine *>(
546
+ GetWindowLongPtrW(hwnd, GWLP_USERDATA));
547
+ }
548
+
549
+ if (!w) {
550
+ return DefWindowProcW(hwnd, msg, wp, lp);
551
+ }
552
+
553
+ switch (msg) {
554
+ case WM_SIZE:
555
+ w->resize_widget();
556
+ break;
557
+ case WM_CLOSE:
558
+ DestroyWindow(hwnd);
559
+ break;
560
+ case WM_DESTROY:
561
+ w->m_window = nullptr;
562
+ SetWindowLongPtrW(hwnd, GWLP_USERDATA, 0);
563
+ w->on_window_destroyed();
564
+ break;
565
+ case WM_GETMINMAXINFO: {
566
+ auto lpmmi = (LPMINMAXINFO)lp;
567
+ if (w->m_maxsz.x > 0 && w->m_maxsz.y > 0) {
568
+ lpmmi->ptMaxSize = w->m_maxsz;
569
+ lpmmi->ptMaxTrackSize = w->m_maxsz;
570
+ }
571
+ if (w->m_minsz.x > 0 && w->m_minsz.y > 0) {
572
+ lpmmi->ptMinTrackSize = w->m_minsz;
573
+ }
574
+ } break;
575
+ case 0x02E4 /*WM_GETDPISCALEDSIZE*/: {
576
+ auto dpi = static_cast<int>(wp);
577
+ auto *size{reinterpret_cast<SIZE *>(lp)};
578
+ *size = w->get_scaled_size(w->m_dpi, dpi);
579
+ return TRUE;
580
+ }
581
+ case 0x02E0 /*WM_DPICHANGED*/: {
582
+ // Windows 10: The size we get here is exactly what we supplied to WM_GETDPISCALEDSIZE.
583
+ // Windows 11: The size we get here is NOT what we supplied to WM_GETDPISCALEDSIZE.
584
+ // Due to this difference, don't use the suggested bounds.
585
+ auto dpi = static_cast<int>(HIWORD(wp));
586
+ w->on_dpi_changed(dpi);
587
+ break;
588
+ }
589
+ case WM_SETTINGCHANGE: {
590
+ auto *area = reinterpret_cast<const wchar_t *>(lp);
591
+ if (area) {
592
+ w->on_system_setting_change(area);
593
+ }
594
+ break;
595
+ }
596
+ case WM_ACTIVATE:
597
+ if (LOWORD(wp) != WA_INACTIVE) {
598
+ w->focus_webview();
599
+ }
600
+ break;
601
+ default:
602
+ return DefWindowProcW(hwnd, msg, wp, lp);
603
+ }
604
+ return 0;
605
+ });
606
+ RegisterClassExW(&wc);
607
+
608
+ CreateWindowW(L"webview", L"", WS_OVERLAPPEDWINDOW, CW_USEDEFAULT,
609
+ CW_USEDEFAULT, 0, 0, nullptr, nullptr, hInstance, this);
610
+ if (!m_window) {
611
+ throw exception{WEBVIEW_ERROR_INVALID_STATE, "Window is null"};
612
+ }
613
+ on_window_created();
614
+
615
+ m_dpi = get_window_dpi(m_window);
616
+ } else {
617
+ m_window = IsWindow(static_cast<HWND>(window))
618
+ ? static_cast<HWND>(window)
619
+ : *(static_cast<HWND *>(window));
620
+ m_dpi = get_window_dpi(m_window);
621
+ }
622
+ // Create a window that WebView2 will be embedded into.
623
+ WNDCLASSEXW widget_wc{};
624
+ widget_wc.cbSize = sizeof(WNDCLASSEX);
625
+ widget_wc.hInstance = hInstance;
626
+ widget_wc.lpszClassName = L"webview_widget";
627
+ widget_wc.lpfnWndProc = (WNDPROC)(+[](HWND hwnd, UINT msg, WPARAM wp,
628
+ LPARAM lp) -> LRESULT {
629
+ win32_edge_engine *w{};
630
+
631
+ if (msg == WM_NCCREATE) {
632
+ auto *lpcs{reinterpret_cast<LPCREATESTRUCT>(lp)};
633
+ w = static_cast<win32_edge_engine *>(lpcs->lpCreateParams);
634
+ w->m_widget = hwnd;
635
+ SetWindowLongPtrW(hwnd, GWLP_USERDATA, reinterpret_cast<LONG_PTR>(w));
636
+ } else {
637
+ w = reinterpret_cast<win32_edge_engine *>(
638
+ GetWindowLongPtrW(hwnd, GWLP_USERDATA));
639
+ }
640
+
641
+ if (!w) {
642
+ return DefWindowProcW(hwnd, msg, wp, lp);
643
+ }
644
+
645
+ switch (msg) {
646
+ case WM_SIZE:
647
+ w->resize_webview();
648
+ break;
649
+ case WM_DESTROY:
650
+ w->m_widget = nullptr;
651
+ SetWindowLongPtrW(hwnd, GWLP_USERDATA, 0);
652
+ break;
653
+ default:
654
+ return DefWindowProcW(hwnd, msg, wp, lp);
655
+ }
656
+ return 0;
657
+ });
658
+ RegisterClassExW(&widget_wc);
659
+ CreateWindowExW(WS_EX_CONTROLPARENT, L"webview_widget", nullptr, WS_CHILD,
660
+ 0, 0, 0, 0, m_window, nullptr, hInstance, this);
661
+ if (!m_widget) {
662
+ throw exception{WEBVIEW_ERROR_INVALID_STATE, "Widget window is null"};
663
+ }
664
+
665
+ // Create a message-only window for internal messaging.
666
+ WNDCLASSEXW message_wc{};
667
+ message_wc.cbSize = sizeof(WNDCLASSEX);
668
+ message_wc.hInstance = hInstance;
669
+ message_wc.lpszClassName = L"webview_message";
670
+ message_wc.lpfnWndProc = (WNDPROC)(+[](HWND hwnd, UINT msg, WPARAM wp,
671
+ LPARAM lp) -> LRESULT {
672
+ win32_edge_engine *w{};
673
+
674
+ if (msg == WM_NCCREATE) {
675
+ auto *lpcs{reinterpret_cast<LPCREATESTRUCT>(lp)};
676
+ w = static_cast<win32_edge_engine *>(lpcs->lpCreateParams);
677
+ w->m_message_window = hwnd;
678
+ SetWindowLongPtrW(hwnd, GWLP_USERDATA, reinterpret_cast<LONG_PTR>(w));
679
+ } else {
680
+ w = reinterpret_cast<win32_edge_engine *>(
681
+ GetWindowLongPtrW(hwnd, GWLP_USERDATA));
682
+ }
683
+
684
+ if (!w) {
685
+ return DefWindowProcW(hwnd, msg, wp, lp);
686
+ }
687
+
688
+ switch (msg) {
689
+ case WM_APP:
690
+ if (auto f = (dispatch_fn_t *)(lp)) {
691
+ (*f)();
692
+ delete f;
693
+ }
694
+ break;
695
+ case WM_DESTROY:
696
+ w->m_message_window = nullptr;
697
+ SetWindowLongPtrW(hwnd, GWLP_USERDATA, 0);
698
+ break;
699
+ default:
700
+ return DefWindowProcW(hwnd, msg, wp, lp);
701
+ }
702
+ return 0;
703
+ });
704
+ RegisterClassExW(&message_wc);
705
+ CreateWindowExW(0, L"webview_message", nullptr, 0, 0, 0, 0, 0, HWND_MESSAGE,
706
+ nullptr, hInstance, this);
707
+ if (!m_message_window) {
708
+ throw exception{WEBVIEW_ERROR_INVALID_STATE, "Message window is null"};
709
+ }
710
+ }
711
+
712
+ void window_settings(bool debug) {
713
+ auto cb =
714
+ std::bind(&win32_edge_engine::on_message, this, std::placeholders::_1);
715
+ embed(m_widget, debug, cb).ensure_ok();
716
+ }
717
+
718
+ noresult window_show() {
719
+ if (owns_window() && !m_is_window_shown) {
720
+ ShowWindow(m_window, SW_SHOW);
721
+ UpdateWindow(m_window);
722
+ SetFocus(m_window);
723
+ m_is_window_shown = true;
724
+ }
725
+ return {};
726
+ }
727
+ noresult embed(HWND wnd, bool debug, msg_cb_t cb) {
728
+ std::atomic_flag flag = ATOMIC_FLAG_INIT;
729
+ flag.test_and_set();
730
+
731
+ wchar_t currentExePath[MAX_PATH];
732
+ GetModuleFileNameW(nullptr, currentExePath, MAX_PATH);
733
+ wchar_t *currentExeName = PathFindFileNameW(currentExePath);
734
+
735
+ wchar_t dataPath[MAX_PATH];
736
+ if (!SUCCEEDED(
737
+ SHGetFolderPathW(nullptr, CSIDL_APPDATA, nullptr, 0, dataPath))) {
738
+ return error_info{WEBVIEW_ERROR_UNSPECIFIED, "SHGetFolderPathW failed"};
739
+ }
740
+ wchar_t userDataFolder[MAX_PATH];
741
+ PathCombineW(userDataFolder, dataPath, currentExeName);
742
+
743
+ m_com_handler = new webview2_com_handler(
744
+ wnd, cb,
745
+ [&](ICoreWebView2Controller *controller, ICoreWebView2 *webview) {
746
+ if (!controller || !webview) {
747
+ flag.clear();
748
+ return;
749
+ }
750
+ controller->AddRef();
751
+ webview->AddRef();
752
+ m_controller = controller;
753
+ m_webview = webview;
754
+ flag.clear();
755
+ });
756
+
757
+ m_com_handler->set_attempt_handler([&] {
758
+ return m_webview2_loader.create_environment_with_options(
759
+ nullptr, userDataFolder, nullptr, m_com_handler);
760
+ });
761
+ m_com_handler->try_create_environment();
762
+
763
+ // Pump the message loop until WebView2 has finished initialization.
764
+ bool got_quit_msg = false;
765
+ MSG msg;
766
+ while (flag.test_and_set() && GetMessageW(&msg, nullptr, 0, 0) >= 0) {
767
+ if (msg.message == WM_QUIT) {
768
+ got_quit_msg = true;
769
+ break;
770
+ }
771
+ TranslateMessage(&msg);
772
+ DispatchMessageW(&msg);
773
+ }
774
+ if (got_quit_msg) {
775
+ return error_info{WEBVIEW_ERROR_CANCELED};
776
+ }
777
+ if (!m_controller || !m_webview) {
778
+ return error_info{WEBVIEW_ERROR_INVALID_STATE};
779
+ }
780
+ ICoreWebView2Settings *settings = nullptr;
781
+ auto res = m_webview->get_Settings(&settings);
782
+ if (res != S_OK) {
783
+ return error_info{WEBVIEW_ERROR_UNSPECIFIED, "get_Settings failed"};
784
+ }
785
+ res = settings->put_AreDevToolsEnabled(debug ? TRUE : FALSE);
786
+ if (res != S_OK) {
787
+ return error_info{WEBVIEW_ERROR_UNSPECIFIED,
788
+ "put_AreDevToolsEnabled failed"};
789
+ }
790
+ res = settings->put_IsStatusBarEnabled(FALSE);
791
+ if (res != S_OK) {
792
+ return error_info{WEBVIEW_ERROR_UNSPECIFIED,
793
+ "put_IsStatusBarEnabled failed"};
794
+ }
795
+ add_init_script("function(message) {\n\
796
+ return window.chrome.webview.postMessage(message);\n\
797
+ }");
798
+ resize_webview();
799
+ m_controller->put_IsVisible(TRUE);
800
+ ShowWindow(m_widget, SW_SHOW);
801
+ UpdateWindow(m_widget);
802
+ if (owns_window()) {
803
+ focus_webview();
804
+ }
805
+ return {};
806
+ }
807
+
808
+ void resize_widget() {
809
+ if (m_widget) {
810
+ RECT r{};
811
+ if (GetClientRect(GetParent(m_widget), &r)) {
812
+ MoveWindow(m_widget, r.left, r.top, r.right - r.left, r.bottom - r.top,
813
+ TRUE);
814
+ }
815
+ }
816
+ }
817
+
818
+ void resize_webview() {
819
+ if (m_widget && m_controller) {
820
+ RECT bounds{};
821
+ if (GetClientRect(m_widget, &bounds)) {
822
+ m_controller->put_Bounds(bounds);
823
+ }
824
+ }
825
+ }
826
+
827
+ void focus_webview() {
828
+ if (m_controller) {
829
+ m_controller->MoveFocus(COREWEBVIEW2_MOVE_FOCUS_REASON_PROGRAMMATIC);
830
+ }
831
+ }
832
+
833
+ bool is_webview2_available() const noexcept {
834
+ LPWSTR version_info = nullptr;
835
+ auto res = m_webview2_loader.get_available_browser_version_string(
836
+ nullptr, &version_info);
837
+ // The result will be equal to HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND)
838
+ // if the WebView2 runtime is not installed.
839
+ auto ok = SUCCEEDED(res) && version_info;
840
+ if (version_info) {
841
+ CoTaskMemFree(version_info);
842
+ }
843
+ return ok;
844
+ }
845
+
846
+ void on_dpi_changed(int dpi) {
847
+ auto scaled_size = get_scaled_size(m_dpi, dpi);
848
+ auto frame_size =
849
+ make_window_frame_size(m_window, scaled_size.cx, scaled_size.cy, dpi);
850
+ SetWindowPos(m_window, nullptr, 0, 0, frame_size.cx, frame_size.cy,
851
+ SWP_NOZORDER | SWP_NOACTIVATE | SWP_NOMOVE | SWP_FRAMECHANGED);
852
+ m_dpi = dpi;
853
+ }
854
+
855
+ SIZE get_size() const {
856
+ RECT bounds;
857
+ GetClientRect(m_window, &bounds);
858
+ auto width = bounds.right - bounds.left;
859
+ auto height = bounds.bottom - bounds.top;
860
+ return {width, height};
861
+ }
862
+
863
+ SIZE get_scaled_size(int from_dpi, int to_dpi) const {
864
+ auto size = get_size();
865
+ return scale_size(size.cx, size.cy, from_dpi, to_dpi);
866
+ }
867
+
868
+ void on_system_setting_change(const wchar_t *area) {
869
+ // Detect light/dark mode change in system.
870
+ if (lstrcmpW(area, L"ImmersiveColorSet") == 0) {
871
+ apply_window_theme(m_window);
872
+ }
873
+ }
874
+
875
+ void run_event_loop_while(std::function<bool()> fn) override {
876
+ MSG msg;
877
+ while (fn() && GetMessageW(&msg, nullptr, 0, 0) > 0) {
878
+ TranslateMessage(&msg);
879
+ DispatchMessageW(&msg);
880
+ }
881
+ }
882
+
883
+ // The app is expected to call CoInitializeEx before
884
+ // CreateCoreWebView2EnvironmentWithOptions.
885
+ // Source: https://docs.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/webview2-idl#createcorewebview2environmentwithoptions
886
+ com_init_wrapper m_com_init;
887
+ HWND m_window = nullptr;
888
+ HWND m_widget = nullptr;
889
+ HWND m_message_window = nullptr;
890
+ POINT m_minsz = POINT{0, 0};
891
+ POINT m_maxsz = POINT{0, 0};
892
+ DWORD m_main_thread = GetCurrentThreadId();
893
+ ICoreWebView2 *m_webview = nullptr;
894
+ ICoreWebView2Controller *m_controller = nullptr;
895
+ webview2_com_handler *m_com_handler = nullptr;
896
+ mswebview2::loader m_webview2_loader;
897
+ int m_dpi{};
898
+ bool m_is_window_shown{};
899
+ };
900
+
901
+ } // namespace detail
902
+
903
+ using browser_engine = detail::win32_edge_engine;
904
+
905
+ } // namespace webview
906
+
907
+ #endif // defined(WEBVIEW_PLATFORM_WINDOWS) && defined(WEBVIEW_EDGE)
908
+ #endif // defined(__cplusplus) && !defined(WEBVIEW_HEADER)
909
+ #endif // WEBVIEW_BACKENDS_WIN32_EDGE_HH