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