1 /++
2 	A thin wrapper around common system webviews.
3 	Based on: https://github.com/zserge/webview
4 
5 	Work in progress. DO NOT USE YET as I am prolly gonna break everything.
6 +/
7 module arsd.webview;
8 
9 
10 /* Original https://github.com/zserge/webview notice below:
11  * MIT License
12  *
13  * Copyright (c) 2017 Serge Zaitsev
14  *
15  * Permission is hereby granted, free of charge, to any person obtaining a copy
16  * of this software and associated documentation files (the "Software"), to deal
17  * in the Software without restriction, including without limitation the rights
18  * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
19  * copies of the Software, and to permit persons to whom the Software is
20  * furnished to do so, subject to the following conditions:
21  *
22  * The above copyright notice and this permission notice shall be included in
23  * all copies or substantial portions of the Software.
24  *
25  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
26  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
27  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
28  * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
29  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
30  * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
31  * SOFTWARE.
32  */
33 
34 /*
35 	Port to D by Adam D. Ruppe, November 30, 2019
36 */
37 
38 version(Windows)
39 	version=WEBVIEW_EDGE;
40 else version(linux)
41 	version=WEBVIEW_GTK;
42 else version(OSX)
43 	version=WEBVIEW_COCOA;
44 
45 version(WEBVIEW_MSHTML)
46 	version=WindowsWindow;
47 version(WEBVIEW_EDGE)
48 	version=WindowsWindow;
49 
50 version(Demo)
51 void main() {
52 	auto wv = new WebView(true, null);
53 	wv.navigate("http://dpldocs.info/");
54 	wv.setTitle("omg a D webview");
55 	wv.setSize(500, 500, true);
56 	wv.eval("console.log('just testing');");
57 	wv.run();
58 }
59 
60 /++
61 
62 +/
63 class WebView : browser_engine {
64 
65 	/++
66 		Creates a new webview instance. If dbg is non-zero - developer tools will
67 		be enabled (if the platform supports them). Window parameter can be a
68 		pointer to the native window handle. If it's non-null - then child WebView
69 		is embedded into the given parent window. Otherwise a new window is created.
70 		Depending on the platform, a GtkWindow, NSWindow or HWND pointer can be
71 		passed here.
72 	+/
73 	this(bool dbg, void* window) {
74 		super(&on_message, dbg, window);
75 	}
76 
77 	extern(C)
78 	static void on_message(const char*) {}
79 
80 	/// Destroys a webview and closes the native window.
81 	void destroy() {
82 
83 	}
84 
85 	/// Runs the main loop until it's terminated. After this function exits - you
86 	/// must destroy the webview.
87 	override void run() { super.run(); }
88 
89 	/// Stops the main loop. It is safe to call this function from another other
90 	/// background thread.
91 	override void terminate() { super.terminate(); }
92 
93 	/+
94 	/// Posts a function to be executed on the main thread. You normally do not need
95 	/// to call this function, unless you want to tweak the native window.
96 	void dispatch(void function(WebView w, void *arg) fn, void *arg) {}
97 	+/
98 
99 	/// Returns a native window handle pointer. When using GTK backend the pointer
100 	/// is GtkWindow pointer, when using Cocoa backend the pointer is NSWindow
101 	/// pointer, when using Win32 backend the pointer is HWND pointer.
102 	void* getWindow() { return m_window; }
103 
104 	/// Updates the title of the native window. Must be called from the UI thread.
105 	override void setTitle(const char *title) { super.setTitle(title); }
106 
107 	/// Navigates webview to the given URL. URL may be a data URI.
108 	override void navigate(const char *url) { super.navigate(url); }
109 
110 	/// Injects JavaScript code at the initialization of the new page. Every time
111 	/// the webview will open a the new page - this initialization code will be
112 	/// executed. It is guaranteed that code is executed before window.onload.
113 	override void init(const char *js) { super.init(js); }
114 
115 	/// Evaluates arbitrary JavaScript code. Evaluation happens asynchronously, also
116 	/// the result of the expression is ignored. Use RPC bindings if you want to
117 	/// receive notifications about the results of the evaluation.
118 	override void eval(const char *js) { super.eval(js); }
119 
120 	/// Binds a native C callback so that it will appear under the given name as a
121 	/// global JavaScript function. Internally it uses webview_init(). Callback
122 	/// receives a request string and a user-provided argument pointer. Request
123 	/// string is a JSON array of all the arguments passed to the JavaScript
124 	/// function.
125 	void bind(const char *name, void function(const char *, void *) fn, void *arg) {}
126 
127 	/// Allows to return a value from the native binding. Original request pointer
128 	/// must be provided to help internal RPC engine match requests with responses.
129 	/// If status is zero - result is expected to be a valid JSON result value.
130 	/// If status is not zero - result is an error JSON object.
131 	void webview_return(const char *req, int status, const char *result) {}
132 
133   /*
134   void on_message(const char *msg) {
135     auto seq = json_parse(msg, "seq", 0);
136     auto name = json_parse(msg, "name", 0);
137     auto args = json_parse(msg, "args", 0);
138     auto fn = bindings[name];
139     if (fn == null) {
140       return;
141     }
142     std::async(std::launch::async, [=]() {
143       auto result = (*fn)(args);
144       dispatch([=]() {
145         eval(("var b = window['" + name + "'];b['callbacks'][" + seq + "](" +
146               result + ");b['callbacks'][" + seq +
147               "] = undefined;b['errors'][" + seq + "] = undefined;")
148                  .c_str());
149       });
150     });
151   }
152   std::map<std::string, binding_t *> bindings;
153 
154   alias binding_t = std::function<std::string(std::string)>;
155 
156   void bind(const char *name, binding_t f) {
157     auto js = "(function() { var name = '" + std::string(name) + "';" + R"(
158       window[name] = function() {
159         var me = window[name];
160         var errors = me['errors'];
161         var callbacks = me['callbacks'];
162         if (!callbacks) {
163           callbacks = {};
164           me['callbacks'] = callbacks;
165         }
166         if (!errors) {
167           errors = {};
168           me['errors'] = errors;
169         }
170         var seq = (me['lastSeq'] || 0) + 1;
171         me['lastSeq'] = seq;
172         var promise = new Promise(function(resolve, reject) {
173           callbacks[seq] = resolve;
174           errors[seq] = reject;
175         });
176         window.external.invoke(JSON.stringify({
177           name: name,
178           seq:seq,
179           args: Array.prototype.slice.call(arguments),
180         }));
181         return promise;
182       }
183     })())";
184     init(js.c_str());
185     bindings[name] = new binding_t(f);
186   }
187 
188 */
189 }
190 
191 private extern(C) {
192 	alias dispatch_fn_t = void function();
193 	alias msg_cb_t = void function(const char *msg);
194 }
195 
196 version(WEBVIEW_GTK) {
197 
198 	pragma(lib, "gtk-3");
199 	pragma(lib, "glib-2.0");
200 	pragma(lib, "gobject-2.0");
201 	pragma(lib, "webkit2gtk-4.0");
202 	pragma(lib, "javascriptcoregtk-4.0");
203 
204 	private extern(C) {
205 		import core.stdc.config;
206 		alias GtkWidget = void;
207 		enum GtkWindowType {
208 			GTK_WINDOW_TOPLEVEL = 0
209 		}
210 		bool gtk_init_check(int*, char***);
211 		GtkWidget* gtk_window_new(GtkWindowType);
212 		c_ulong g_signal_connect_data(void*, const char*, void* /* function pointer!!! */, void*, void*, int);
213 		GtkWidget* webkit_web_view_new();
214 		alias WebKitUserContentManager = void;
215 		WebKitUserContentManager* webkit_web_view_get_user_content_manager(GtkWidget*);
216 
217 		void gtk_container_add(GtkWidget*, GtkWidget*);
218 		void gtk_widget_grab_focus(GtkWidget*);
219 		void gtk_widget_show_all(GtkWidget*);
220 		void gtk_main();
221 		void gtk_main_quit();
222 		void webkit_web_view_load_uri(GtkWidget*, const char*);
223 		alias WebKitSettings = void;
224 		WebKitSettings* webkit_web_view_get_settings(GtkWidget*);
225 		void webkit_settings_set_enable_write_console_messages_to_stdout(WebKitSettings*, bool);
226 		void webkit_settings_set_enable_developer_extras(WebKitSettings*, bool);
227 		void webkit_user_content_manager_register_script_message_handler(WebKitUserContentManager*, const char*);
228 		alias JSCValue = void;
229 		alias WebKitJavascriptResult = void;
230 		JSCValue* webkit_javascript_result_get_js_value(WebKitJavascriptResult*);
231 		char* jsc_value_to_string(JSCValue*);
232 		void g_free(void*);
233 		void webkit_web_view_run_javascript(GtkWidget*, const char*, void*, void*, void*);
234 		alias WebKitUserScript = void;
235 		void webkit_user_content_manager_add_script(WebKitUserContentManager*, WebKitUserScript*);
236 		WebKitUserScript* webkit_user_script_new(const char*, WebKitUserContentInjectedFrames, WebKitUserScriptInjectionTime, const char*, const char*);
237 		enum WebKitUserContentInjectedFrames {
238 			WEBKIT_USER_CONTENT_INJECT_ALL_FRAMES,
239 			WEBKIT_USER_CONTENT_INJECT_TOP_FRAME
240 		}
241 		enum WebKitUserScriptInjectionTime {
242 			WEBKIT_USER_SCRIPT_INJECT_AT_DOCUMENT_START,
243 			WEBKIT_USER_SCRIPT_INJECT_AT_DOCUMENT_END
244 		}
245 		void gtk_window_set_title(GtkWidget*, const char*);
246 
247 		void gtk_window_set_resizable(GtkWidget*, bool);
248 		void gtk_window_set_default_size(GtkWidget*, int, int);
249 		void gtk_widget_set_size_request(GtkWidget*, int, int);
250 	}
251 
252 	private class browser_engine {
253 
254 		static extern(C)
255 		void ondestroy (GtkWidget *w, void* arg) {
256 			(cast(browser_engine) arg).terminate();
257 		}
258 
259 		static extern(C)
260 		void smr(WebKitUserContentManager* m, WebKitJavascriptResult* r, void* arg) {
261 			auto w = cast(browser_engine) arg;
262 			JSCValue *value = webkit_javascript_result_get_js_value(r);
263 			auto s = jsc_value_to_string(value);
264 			w.m_cb(s);
265 			g_free(s);
266 		}
267 
268 		this(msg_cb_t cb, bool dbg, void* window) {
269 			m_cb = cb;
270 
271 			gtk_init_check(null, null);
272 			m_window = cast(GtkWidget*) window;
273 			if (m_window == null)
274 				m_window = gtk_window_new(GtkWindowType.GTK_WINDOW_TOPLEVEL);
275 
276 			g_signal_connect_data(m_window, "destroy", &ondestroy, cast(void*) this, null, 0);
277 
278 			m_webview = webkit_web_view_new();
279 			WebKitUserContentManager* manager = webkit_web_view_get_user_content_manager(m_webview);
280 
281 			g_signal_connect_data(manager, "script-message-received::external", &smr, cast(void*) this, null, 0);
282 			webkit_user_content_manager_register_script_message_handler(manager, "external");
283 			init("window.external={invoke:function(s){window.webkit.messageHandlers.external.postMessage(s);}}");
284 
285 			gtk_container_add(m_window, m_webview);
286 			gtk_widget_grab_focus(m_webview);
287 
288 			if (dbg) {
289 				WebKitSettings *settings = webkit_web_view_get_settings(m_webview);
290 				webkit_settings_set_enable_write_console_messages_to_stdout(settings, true);
291 				webkit_settings_set_enable_developer_extras(settings, true);
292 			}
293 
294 			gtk_widget_show_all(m_window);
295 		}
296 		void run() { gtk_main(); }
297 		void terminate() { gtk_main_quit(); }
298 
299 		void navigate(const char *url) {
300 			webkit_web_view_load_uri(m_webview, url);
301 		}
302 
303 		void setTitle(const char* title) {
304 			gtk_window_set_title(m_window, title);
305 		}
306 
307 		/+
308 			void dispatch(std::function<void()> f) {
309 				g_idle_add_full(G_PRIORITY_HIGH_IDLE, (GSourceFunc)([](void *f) -> int {
310 							(*static_cast<dispatch_fn_t *>(f))();
311 							return G_SOURCE_REMOVE;
312 							}),
313 						new std::function<void()>(f),
314 						[](void *f) { delete static_cast<dispatch_fn_t *>(f); });
315 			}
316 		+/
317 
318 		void setSize(int width, int height, bool resizable) {
319 			gtk_window_set_resizable(m_window, resizable);
320 			if (resizable) {
321 				gtk_window_set_default_size(m_window, width, height);
322 			}
323 			gtk_widget_set_size_request(m_window, width, height);
324 		}
325 
326 		void init(const char *js) {
327 			WebKitUserContentManager *manager = webkit_web_view_get_user_content_manager(m_webview);
328 			webkit_user_content_manager_add_script(
329 				manager, webkit_user_script_new(
330 					js, WebKitUserContentInjectedFrames.WEBKIT_USER_CONTENT_INJECT_TOP_FRAME,
331 					WebKitUserScriptInjectionTime.WEBKIT_USER_SCRIPT_INJECT_AT_DOCUMENT_START, null, null));
332 			}
333 
334 		void eval(const char *js) {
335 			webkit_web_view_run_javascript(m_webview, js, null, null, null);
336 		}
337 
338 		protected:
339 		GtkWidget* m_window;
340 		GtkWidget* m_webview;
341 		msg_cb_t m_cb;
342 	}
343 } else version(WEBVIEW_COCOA) {
344 /+
345 
346 //
347 // ====================================================================
348 //
349 // This implementation uses Cocoa WKWebView backend on macOS. It is
350 // written using ObjC runtime and uses WKWebView class as a browser runtime.
351 // You should pass "-framework Webkit" flag to the compiler.
352 //
353 // ====================================================================
354 //
355 
356 #define OBJC_OLD_DISPATCH_PROTOTYPES 1
357 #include <CoreGraphics/CoreGraphics.h>
358 #include <objc/objc-runtime.h>
359 
360 #define NSBackingStoreBuffered 2
361 
362 #define NSWindowStyleMaskResizable 8
363 #define NSWindowStyleMaskMiniaturizable 4
364 #define NSWindowStyleMaskTitled 1
365 #define NSWindowStyleMaskClosable 2
366 
367 #define NSApplicationActivationPolicyRegular 0
368 
369 #define WKUserScriptInjectionTimeAtDocumentStart 0
370 
371 id operator"" _cls(const char *s, std::size_t sz) {
372   return (id)objc_getClass(s);
373 }
374 SEL operator"" _sel(const char *s, std::size_t sz) {
375   return sel_registerName(s);
376 }
377 id operator"" _str(const char *s, std::size_t sz) {
378   return objc_msgSend("NSString"_cls, "stringWithUTF8String:"_sel, s);
379 }
380 
381 class browser_engine {
382 public:
383   browser_engine(msg_cb_t cb, bool dbg, void *window) : m_cb(cb) {
384     // Application
385     id app = objc_msgSend("NSApplication"_cls, "sharedApplication"_sel);
386     objc_msgSend(app, "setActivationPolicy:"_sel,
387                  NSApplicationActivationPolicyRegular);
388 
389     // Delegate
390     auto cls = objc_allocateClassPair((Class) "NSObject"_cls, "AppDelegate", 0);
391     class_addProtocol(cls, objc_getProtocol("NSApplicationDelegate"));
392     class_addProtocol(cls, objc_getProtocol("WKScriptMessageHandler"));
393     class_addMethod(
394         cls, "applicationShouldTerminateAfterLastWindowClosed:"_sel,
395         (IMP)(+[](id self, SEL cmd, id notification) -> BOOL { return 1; }),
396         "c@:@");
397     class_addMethod(
398         cls, "userContentController:didReceiveScriptMessage:"_sel,
399         (IMP)(+[](id self, SEL cmd, id notification, id msg) {
400           auto w = (browser_engine *)objc_getAssociatedObject(self, "webview");
401           w->m_cb((const char *)objc_msgSend(objc_msgSend(msg, "body"_sel),
402                                              "UTF8String"_sel));
403         }),
404         "v@:@@");
405     objc_registerClassPair(cls);
406 
407     auto delegate = objc_msgSend((id)cls, "new"_sel);
408     objc_setAssociatedObject(delegate, "webview", (id)this,
409                              OBJC_ASSOCIATION_ASSIGN);
410     objc_msgSend(app, sel_registerName("setDelegate:"), delegate);
411 
412     // Main window
413     if (window is null) {
414       m_window = objc_msgSend("NSWindow"_cls, "alloc"_sel);
415       m_window = objc_msgSend(
416           m_window, "initWithContentRect:styleMask:backing:defer:"_sel,
417           CGRectMake(0, 0, 0, 0), 0, NSBackingStoreBuffered, 0);
418       setSize(480, 320, true);
419     } else {
420       m_window = (id)window;
421     }
422 
423     // Webview
424     auto config = objc_msgSend("WKWebViewConfiguration"_cls, "new"_sel);
425     m_manager = objc_msgSend(config, "userContentController"_sel);
426     m_webview = objc_msgSend("WKWebView"_cls, "alloc"_sel);
427     objc_msgSend(m_webview, "initWithFrame:configuration:"_sel,
428                  CGRectMake(0, 0, 0, 0), config);
429     objc_msgSend(m_manager, "addScriptMessageHandler:name:"_sel, delegate,
430                  "external"_str);
431     init(R"script(
432                       window.external = {
433                         invoke: function(s) {
434                           window.webkit.messageHandlers.external.postMessage(s);
435                         },
436                       };
437                      )script");
438     if (dbg) {
439       objc_msgSend(objc_msgSend(config, "preferences"_sel),
440                    "setValue:forKey:"_sel, 1, "developerExtrasEnabled"_str);
441     }
442     objc_msgSend(m_window, "setContentView:"_sel, m_webview);
443     objc_msgSend(m_window, "makeKeyAndOrderFront:"_sel, null);
444   }
445   ~browser_engine() { close(); }
446   void terminate() { close(); objc_msgSend("NSApp"_cls, "terminate:"_sel, null); }
447   void run() {
448     id app = objc_msgSend("NSApplication"_cls, "sharedApplication"_sel);
449     dispatch([&]() { objc_msgSend(app, "activateIgnoringOtherApps:"_sel, 1); });
450     objc_msgSend(app, "run"_sel);
451   }
452   void dispatch(std::function<void()> f) {
453     dispatch_async_f(dispatch_get_main_queue(), new dispatch_fn_t(f),
454                      (dispatch_function_t)([](void *arg) {
455                        auto f = static_cast<dispatch_fn_t *>(arg);
456                        (*f)();
457                        delete f;
458                      }));
459   }
460   void setTitle(const char *title) {
461     objc_msgSend(
462         m_window, "setTitle:"_sel,
463         objc_msgSend("NSString"_cls, "stringWithUTF8String:"_sel, title));
464   }
465   void setSize(int width, int height, bool resizable) {
466     auto style = NSWindowStyleMaskTitled | NSWindowStyleMaskClosable |
467                  NSWindowStyleMaskMiniaturizable;
468     if (resizable) {
469       style = style | NSWindowStyleMaskResizable;
470     }
471     objc_msgSend(m_window, "setStyleMask:"_sel, style);
472     objc_msgSend(m_window, "setFrame:display:animate:"_sel,
473                  CGRectMake(0, 0, width, height), 1, 0);
474   }
475   void navigate(const char *url) {
476     auto nsurl = objc_msgSend(
477         "NSURL"_cls, "URLWithString:"_sel,
478         objc_msgSend("NSString"_cls, "stringWithUTF8String:"_sel, url));
479     objc_msgSend(
480         m_webview, "loadRequest:"_sel,
481         objc_msgSend("NSURLRequest"_cls, "requestWithURL:"_sel, nsurl));
482   }
483   void init(const char *js) {
484     objc_msgSend(
485         m_manager, "addUserScript:"_sel,
486         objc_msgSend(
487             objc_msgSend("WKUserScript"_cls, "alloc"_sel),
488             "initWithSource:injectionTime:forMainFrameOnly:"_sel,
489             objc_msgSend("NSString"_cls, "stringWithUTF8String:"_sel, js),
490             WKUserScriptInjectionTimeAtDocumentStart, 1));
491   }
492   void eval(const char *js) {
493     objc_msgSend(m_webview, "evaluateJavaScript:completionHandler:"_sel,
494                  objc_msgSend("NSString"_cls, "stringWithUTF8String:"_sel, js),
495                  null);
496   }
497 
498 protected:
499   void close() { objc_msgSend(m_window, "close"_sel); }
500   id m_window;
501   id m_webview;
502   id m_manager;
503   msg_cb_t m_cb;
504 };
505 
506 +/
507 
508 } else version(WindowsWindow) {
509 /+
510 
511 	//
512 	// ====================================================================
513 	//
514 	// This implementation uses Win32 API to create a native window. It can
515 	// use either MSHTML or EdgeHTML backend as a browser engine.
516 	//
517 	// ====================================================================
518 	//
519 
520 	#define WIN32_LEAN_AND_MEAN
521 	#include <windows.h>
522 
523 	pragma(lib, "user32");
524 
525 	class browser_window {
526 	public:
527 	  browser_window(msg_cb_t cb, void *window) : m_cb(cb) {
528 	    if (window is null) {
529 	      WNDCLASSEX wc;
530 	      ZeroMemory(&wc, sizeof(WNDCLASSEX));
531 	      wc.cbSize = sizeof(WNDCLASSEX);
532 	      wc.hInstance = GetModuleHandle(null);
533 	      wc.lpszClassName = "webview";
534 	      wc.lpfnWndProc =
535 		  (WNDPROC)(+[](HWND hwnd, UINT msg, WPARAM wp, LPARAM lp) -> int {
536 		    auto w = (browser_window *)GetWindowLongPtr(hwnd, GWLP_USERDATA);
537 		    switch (msg) {
538 		    case WM_SIZE:
539 		      w->resize();
540 		      break;
541 		    case WM_CLOSE:
542 		      DestroyWindow(hwnd);
543 		      break;
544 		    case WM_DESTROY:
545 		      w->terminate();
546 		      break;
547 		    default:
548 		      return DefWindowProc(hwnd, msg, wp, lp);
549 		    }
550 		    return 0;
551 		  });
552 	      RegisterClassEx(&wc);
553 	      m_window = CreateWindow("webview", "", WS_OVERLAPPEDWINDOW, CW_USEDEFAULT,
554 				      CW_USEDEFAULT, 640, 480, null, null,
555 				      GetModuleHandle(null), null);
556 	      SetWindowLongPtr(m_window, GWLP_USERDATA, (LONG_PTR)this);
557 	    } else {
558 	      m_window = *(static_cast<HWND *>(window));
559 	    }
560 
561 	    ShowWindow(m_window, SW_SHOW);
562 	    UpdateWindow(m_window);
563 	    SetFocus(m_window);
564 	  }
565 
566 	  void run() {
567 	    MSG msg;
568 	    BOOL res;
569 	    while ((res = GetMessage(&msg, null, 0, 0)) != -1) {
570 	      if (msg.hwnd) {
571 		TranslateMessage(&msg);
572 		DispatchMessage(&msg);
573 		continue;
574 	      }
575 	      if (msg.message == WM_APP) {
576 		auto f = (dispatch_fn_t *)(msg.lParam);
577 		(*f)();
578 		delete f;
579 	      } else if (msg.message == WM_QUIT) {
580 		return;
581 	      }
582 	    }
583 	  }
584 
585 	  void terminate() { PostQuitMessage(0); }
586 	  void dispatch(dispatch_fn_t f) {
587 	    PostThreadMessage(m_main_thread, WM_APP, 0, (LPARAM) new dispatch_fn_t(f));
588 	  }
589 
590 	  void setTitle(const char *title) { SetWindowText(m_window, title); }
591 
592 	  void setSize(int width, int height, bool resizable) {
593 	    RECT r;
594 	    r.left = 50;
595 	    r.top = 50;
596 	    r.right = width;
597 	    r.bottom = height;
598 	    AdjustWindowRect(&r, WS_OVERLAPPEDWINDOW, 0);
599 	    SetWindowPos(m_window, null, r.left, r.top, r.right - r.left,
600 			 r.bottom - r.top,
601 			 SWP_NOZORDER | SWP_NOACTIVATE | SWP_FRAMECHANGED);
602 	  }
603 
604 	protected:
605 	  virtual void resize() {}
606 	  HWND m_window;
607 	  DWORD m_main_thread = GetCurrentThreadId();
608 	  msg_cb_t m_cb;
609 	};
610 +/
611 }
612 
613 version(WEBVIEW_MSHTML) {
614 /+
615 	#include <exdisp.h>
616 	#include <exdispid.h>
617 	#include <mshtmhst.h>
618 	#include <mshtml.h>
619 	#include <shobjidl.h>
620 	pragma(lib, "ole32");
621 	pragma(lib, "oleaut32");
622 
623 	#define DISPID_EXTERNAL_INVOKE 0x1000
624 
625 	class browser_engine : public browser_window,
626 			       public IOleClientSite,
627 			       public IOleInPlaceSite,
628 			       public IOleInPlaceFrame,
629 			       public IDocHostUIHandler,
630 			       public DWebBrowserEvents2 {
631 	public:
632 	  browser_engine(msg_cb_t cb, bool dbg, void *window)
633 	      : browser_window(cb, window) {
634 	    RECT rect;
635 	    LPCLASSFACTORY cf = null;
636 	    IOleObject *obj = null;
637 
638 	    fix_ie_compat_mode();
639 
640 	    OleInitialize(null);
641 	    CoGetClassObject(CLSID_WebBrowser,
642 			     CLSCTX_INPROC_SERVER | CLSCTX_INPROC_HANDLER, null,
643 			     IID_IClassFactory, (void **)&cf);
644 	    cf->CreateInstance(null, IID_IOleObject, (void **)&obj);
645 	    cf->Release();
646 
647 	    obj->SetClientSite(this);
648 	    OleSetContainedObject(obj, TRUE);
649 	    GetWindowRect(m_window, &rect);
650 	    obj->DoVerb(OLEIVERB_INPLACEACTIVATE, null, this, -1, m_window, &rect);
651 	    obj->QueryInterface(IID_IWebBrowser2, (void **)&m_webview);
652 
653 	    IConnectionPointContainer *cpc;
654 	    IConnectionPoint *cp;
655 	    DWORD cookie;
656 	    m_webview->QueryInterface(IID_IConnectionPointContainer, (void **)&cpc);
657 	    cpc->FindConnectionPoint(DIID_DWebBrowserEvents2, &cp);
658 	    cpc->Release();
659 	    cp->Advise(static_cast<IOleClientSite *>(this), &cookie);
660 
661 	    resize();
662 	    navigate("about:blank");
663 	  }
664 
665 	  ~browser_engine() { OleUninitialize(); }
666 
667 	  void navigate(const char *url) {
668 	    VARIANT v;
669 	    DWORD size = MultiByteToWideChar(CP_UTF8, 0, url, -1, 0, 0);
670 	    WCHAR *ws = (WCHAR *)GlobalAlloc(GMEM_FIXED, sizeof(WCHAR) * size);
671 	    MultiByteToWideChar(CP_UTF8, 0, url, -1, ws, size);
672 	    VariantInit(&v);
673 	    v.vt = VT_BSTR;
674 	    v.bstrVal = SysAllocString(ws);
675 	    m_webview->Navigate2(&v, null, null, null, null);
676 	    VariantClear(&v);
677 	  }
678 
679 	  void eval(const char *js) {
680 	    // TODO
681 	  }
682 
683 	private:
684 	  IWebBrowser2 *m_webview;
685 
686 	  int fix_ie_compat_mode() {
687 	    const char *WEBVIEW_KEY_FEATURE_BROWSER_EMULATION =
688 		"Software\\Microsoft\\Internet "
689 		"Explorer\\Main\\FeatureControl\\FEATURE_BROWSER_EMULATION";
690 	    HKEY hKey;
691 	    DWORD ie_version = 11000;
692 	    TCHAR appname[MAX_PATH + 1];
693 	    TCHAR *p;
694 	    if (GetModuleFileName(null, appname, MAX_PATH + 1) == 0) {
695 	      return -1;
696 	    }
697 	    for (p = &appname[strlen(appname) - 1]; p != appname && *p != '\\'; p--) {
698 	    }
699 	    p++;
700 	    if (RegCreateKey(HKEY_CURRENT_USER, WEBVIEW_KEY_FEATURE_BROWSER_EMULATION,
701 			     &hKey) != ERROR_SUCCESS) {
702 	      return -1;
703 	    }
704 	    if (RegSetValueEx(hKey, p, 0, REG_DWORD, (BYTE *)&ie_version,
705 			      sizeof(ie_version)) != ERROR_SUCCESS) {
706 	      RegCloseKey(hKey);
707 	      return -1;
708 	    }
709 	    RegCloseKey(hKey);
710 	    return 0;
711 	  }
712 
713 	  // Inheruted via browser_window
714 	  void resize() override {
715 	    RECT rect;
716 	    GetClientRect(m_window, &rect);
717 	    m_webview->put_Left(0);
718 	    m_webview->put_Top(0);
719 	    m_webview->put_Width(rect.right);
720 	    m_webview->put_Height(rect.bottom);
721 	    m_webview->put_Visible(VARIANT_TRUE);
722 	  }
723 
724 	  // Inherited via IUnknown
725 	  ULONG __stdcall AddRef(void) override { return 1; }
726 	  ULONG __stdcall Release(void) override { return 1; }
727 	  HRESULT __stdcall QueryInterface(REFIID riid, void **obj) override {
728 	    if (riid == IID_IUnknown || riid == IID_IOleClientSite) {
729 	      *obj = static_cast<IOleClientSite *>(this);
730 	      return S_OK;
731 	    }
732 	    if (riid == IID_IOleInPlaceSite) {
733 	      *obj = static_cast<IOleInPlaceSite *>(this);
734 	      return S_OK;
735 	    }
736 	    if (riid == IID_IDocHostUIHandler) {
737 	      *obj = static_cast<IDocHostUIHandler *>(this);
738 	      return S_OK;
739 	    }
740 	    if (riid == IID_IDispatch || riid == DIID_DWebBrowserEvents2) {
741 	      *obj = static_cast<IDispatch *>(this);
742 	      return S_OK;
743 	    }
744 	    *obj = null;
745 	    return E_NOINTERFACE;
746 	  }
747 
748 	  // Inherited via IOleClientSite
749 	  HRESULT __stdcall SaveObject(void) override { return E_NOTIMPL; }
750 	  HRESULT __stdcall GetMoniker(DWORD dwAssign, DWORD dwWhichMoniker,
751 				       IMoniker **ppmk) override {
752 	    return E_NOTIMPL;
753 	  }
754 	  HRESULT __stdcall GetContainer(IOleContainer **ppContainer) override {
755 	    *ppContainer = null;
756 	    return E_NOINTERFACE;
757 	  }
758 	  HRESULT __stdcall ShowObject(void) override { return S_OK; }
759 	  HRESULT __stdcall OnShowWindow(BOOL fShow) override { return S_OK; }
760 	  HRESULT __stdcall RequestNewObjectLayout(void) override { return E_NOTIMPL; }
761 
762 	  // Inherited via IOleInPlaceSite
763 	  HRESULT __stdcall GetWindow(HWND *phwnd) override {
764 	    *phwnd = m_window;
765 	    return S_OK;
766 	  }
767 	  HRESULT __stdcall ContextSensitiveHelp(BOOL fEnterMode) override {
768 	    return E_NOTIMPL;
769 	  }
770 	  HRESULT __stdcall CanInPlaceActivate(void) override { return S_OK; }
771 	  HRESULT __stdcall OnInPlaceActivate(void) override { return S_OK; }
772 	  HRESULT __stdcall OnUIActivate(void) override { return S_OK; }
773 	  HRESULT __stdcall GetWindowContext(
774 	      IOleInPlaceFrame **ppFrame, IOleInPlaceUIWindow **ppDoc,
775 	      LPRECT lprcPosRect, LPRECT lprcClipRect,
776 	      LPOLEINPLACEFRAMEINFO lpFrameInfo) override {
777 	    *ppFrame = static_cast<IOleInPlaceFrame *>(this);
778 	    *ppDoc = null;
779 	    lpFrameInfo->fMDIApp = FALSE;
780 	    lpFrameInfo->hwndFrame = m_window;
781 	    lpFrameInfo->haccel = 0;
782 	    lpFrameInfo->cAccelEntries = 0;
783 	    return S_OK;
784 	  }
785 	  HRESULT __stdcall Scroll(SIZE scrollExtant) override { return E_NOTIMPL; }
786 	  HRESULT __stdcall OnUIDeactivate(BOOL fUndoable) override { return S_OK; }
787 	  HRESULT __stdcall OnInPlaceDeactivate(void) override { return S_OK; }
788 	  HRESULT __stdcall DiscardUndoState(void) override { return E_NOTIMPL; }
789 	  HRESULT __stdcall DeactivateAndUndo(void) override { return E_NOTIMPL; }
790 	  HRESULT __stdcall OnPosRectChange(LPCRECT lprcPosRect) override {
791 	    IOleInPlaceObject *inplace;
792 	    m_webview->QueryInterface(IID_IOleInPlaceObject, (void **)&inplace);
793 	    inplace->SetObjectRects(lprcPosRect, lprcPosRect);
794 	    return S_OK;
795 	  }
796 
797 	  // Inherited via IDocHostUIHandler
798 	  HRESULT __stdcall ShowContextMenu(DWORD dwID, POINT *ppt,
799 					    IUnknown *pcmdtReserved,
800 					    IDispatch *pdispReserved) override {
801 	    return S_OK;
802 	  }
803 	  HRESULT __stdcall GetHostInfo(DOCHOSTUIINFO *pInfo) override {
804 	    pInfo->dwDoubleClick = DOCHOSTUIDBLCLK_DEFAULT;
805 	    pInfo->dwFlags = DOCHOSTUIFLAG_NO3DBORDER;
806 	    return S_OK;
807 	  }
808 	  HRESULT __stdcall ShowUI(DWORD dwID, IOleInPlaceActiveObject *pActiveObject,
809 				   IOleCommandTarget *pCommandTarget,
810 				   IOleInPlaceFrame *pFrame,
811 				   IOleInPlaceUIWindow *pDoc) override {
812 	    return S_OK;
813 	  }
814 	  HRESULT __stdcall HideUI(void) override { return S_OK; }
815 	  HRESULT __stdcall UpdateUI(void) override { return S_OK; }
816 	  HRESULT __stdcall EnableModeless(BOOL fEnable) override { return S_OK; }
817 	  HRESULT __stdcall OnDocWindowActivate(BOOL fActivate) override {
818 	    return S_OK;
819 	  }
820 	  HRESULT __stdcall OnFrameWindowActivate(BOOL fActivate) override {
821 	    return S_OK;
822 	  }
823 	  HRESULT __stdcall ResizeBorder(LPCRECT prcBorder,
824 					 IOleInPlaceUIWindow *pUIWindow,
825 					 BOOL fRameWindow) override {
826 	    return S_OK;
827 	  }
828 	  HRESULT __stdcall GetOptionKeyPath(LPOLESTR *pchKey, DWORD dw) override {
829 	    return S_FALSE;
830 	  }
831 	  HRESULT __stdcall GetDropTarget(IDropTarget *pDropTarget,
832 					  IDropTarget **ppDropTarget) override {
833 	    return E_NOTIMPL;
834 	  }
835 	  HRESULT __stdcall GetExternal(IDispatch **ppDispatch) override {
836 	    *ppDispatch = static_cast<IDispatch *>(this);
837 	    return S_OK;
838 	  }
839 	  HRESULT __stdcall TranslateUrl(DWORD dwTranslate, LPWSTR pchURLIn,
840 					 LPWSTR *ppchURLOut) override {
841 	    *ppchURLOut = null;
842 	    return S_FALSE;
843 	  }
844 	  HRESULT __stdcall FilterDataObject(IDataObject *pDO,
845 					     IDataObject **ppDORet) override {
846 	    *ppDORet = null;
847 	    return S_FALSE;
848 	  }
849 	  HRESULT __stdcall TranslateAcceleratorA(LPMSG lpMsg,
850 						  const GUID *pguidCmdGroup,
851 						  DWORD nCmdID) {
852 	    return S_FALSE;
853 	  }
854 
855 	  // Inherited via IOleInPlaceFrame
856 	  HRESULT __stdcall GetBorder(LPRECT lprectBorder) override { return S_OK; }
857 	  HRESULT __stdcall RequestBorderSpace(LPCBORDERWIDTHS pborderwidths) override {
858 	    return S_OK;
859 	  }
860 	  HRESULT __stdcall SetBorderSpace(LPCBORDERWIDTHS pborderwidths) override {
861 	    return S_OK;
862 	  }
863 	  HRESULT __stdcall SetActiveObject(IOleInPlaceActiveObject *pActiveObject,
864 					    LPCOLESTR pszObjName) override {
865 	    return S_OK;
866 	  }
867 	  HRESULT __stdcall InsertMenus(HMENU hmenuShared,
868 					LPOLEMENUGROUPWIDTHS lpMenuWidths) override {
869 	    return S_OK;
870 	  }
871 	  HRESULT __stdcall SetMenu(HMENU hmenuShared, HOLEMENU holemenu,
872 				    HWND hwndActiveObject) override {
873 	    return S_OK;
874 	  }
875 	  HRESULT __stdcall RemoveMenus(HMENU hmenuShared) override { return S_OK; }
876 	  HRESULT __stdcall SetStatusText(LPCOLESTR pszStatusText) override {
877 	    return S_OK;
878 	  }
879 	  HRESULT __stdcall TranslateAcceleratorA(LPMSG lpmsg, WORD wID) {
880 	    return S_OK;
881 	  }
882 
883 	  // Inherited via IDispatch
884 	  HRESULT __stdcall GetTypeInfoCount(UINT *pctinfo) override { return S_OK; }
885 	  HRESULT __stdcall GetTypeInfo(UINT iTInfo, LCID lcid,
886 					ITypeInfo **ppTInfo) override {
887 	    return S_OK;
888 	  }
889 	  HRESULT __stdcall GetIDsOfNames(REFIID riid, LPOLESTR *rgszNames, UINT cNames,
890 					  LCID lcid, DISPID *rgDispId) override {
891 	    *rgDispId = DISPID_EXTERNAL_INVOKE;
892 	    return S_OK;
893 	  }
894 	  HRESULT __stdcall Invoke(DISPID dispIdMember, REFIID riid, LCID lcid,
895 				   WORD wFlags, DISPPARAMS *pDispParams,
896 				   VARIANT *pVarResult, EXCEPINFO *pExcepInfo,
897 				   UINT *puArgErr) override {
898 	    if (dispIdMember == DISPID_NAVIGATECOMPLETE2) {
899 	    } else if (dispIdMember == DISPID_DOCUMENTCOMPLETE) {
900 	    } else if (dispIdMember == DISPID_EXTERNAL_INVOKE) {
901 	    }
902 	    return S_OK;
903 	  }
904 	};
905 +/
906 } else version(WEBVIEW_EDGE) {
907 
908 	// NOTE: this will prolly only work on Win 10 and maybe win 8.
909 	// but def not older ones. Will have to version it or dynamically
910 	// load. Should prolly make it opt-in to the old style, default to new w. multi-threading
911 
912 /+
913 	#include <objbase.h>
914 	#include <winrt/Windows.Foundation.h>
915 	#include <winrt/Windows.Web.UI.Interop.h>
916 
917 	#pragma comment(lib, "windowsapp")
918 
919 	using namespace winrt;
920 	using namespace Windows::Foundation;
921 	using namespace Windows::Web::UI;
922 	using namespace Windows::Web::UI::Interop;
923 
924 	class browser_engine : public browser_window {
925 	public:
926 	  browser_engine(msg_cb_t cb, bool dbg, void *window)
927 	      : browser_window(cb, window) {
928 	    init_apartment(winrt::apartment_type::single_threaded);
929 	    m_process = WebViewControlProcess();
930 	    auto op = m_process.CreateWebViewControlAsync(
931 		reinterpret_cast<int64_t>(m_window), Rect());
932 	    if (op.Status() != AsyncStatus::Completed) {
933 	      handle h(CreateEvent(null, false, false, null));
934 	      op.Completed([h = h.get()](auto, auto) { SetEvent(h); });
935 	      HANDLE hs[] = {h.get()};
936 	      DWORD i;
937 	      CoWaitForMultipleHandles(COWAIT_DISPATCH_WINDOW_MESSAGES |
938 					   COWAIT_DISPATCH_CALLS |
939 					   COWAIT_INPUTAVAILABLE,
940 				       INFINITE, 1, hs, &i);
941 	    }
942 	    m_webview = op.GetResults();
943 	    m_webview.Settings().IsScriptNotifyAllowed(true);
944 	    m_webview.IsVisible(true);
945 	    m_webview.ScriptNotify([=](auto const &sender, auto const &args) {
946 	      std::string s = winrt::to_string(args.Value());
947 	      m_cb(s.c_str());
948 	    });
949 	    m_webview.NavigationStarting([=](auto const &sender, auto const &args) {
950 	      m_webview.AddInitializeScript(winrt::to_hstring(init_js));
951 	    });
952 	    init("window.external.invoke = s => window.external.notify(s)");
953 	    resize();
954 	  }
955 
956 	  void navigate(const char *url) {
957 	    Uri uri(winrt::to_hstring(url));
958 	    // TODO: if url starts with 'data:text/html,' prefix then use it as a string
959 	    m_webview.Navigate(uri);
960 	    // m_webview.NavigateToString(winrt::to_hstring(url));
961 	  }
962 	  void init(const char *js) {
963 	    init_js = init_js + "(function(){" + js + "})();";
964 	  }
965 	  void eval(const char *js) {
966 	    m_webview.InvokeScriptAsync(
967 		L"eval", single_threaded_vector<hstring>({winrt::to_hstring(js)}));
968 	  }
969 
970 	private:
971 	  void resize() {
972 	    RECT r;
973 	    GetClientRect(m_window, &r);
974 	    Rect bounds(r.left, r.top, r.right - r.left, r.bottom - r.top);
975 	    m_webview.Bounds(bounds);
976 	  }
977 	  WebViewControlProcess m_process;
978 	  WebViewControl m_webview = null;
979 	  std::string init_js = "";
980 	};
981 +/
982 }
983