/++ A thin wrapper around common system webviews. Based on: https://github.com/zserge/webview Work in progress. DO NOT USE YET as I am prolly gonna break everything. +/ module arsd.webview; /* Original https://github.com/zserge/webview notice below: * MIT License * * Copyright (c) 2017 Serge Zaitsev * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ /* Port to D by Adam D. Ruppe, November 30, 2019 */ version(Windows) version=WEBVIEW_EDGE; else version(linux) version=WEBVIEW_GTK; else version(OSX) version=WEBVIEW_COCOA; version(WEBVIEW_MSHTML) version=WindowsWindow; version(WEBVIEW_EDGE) version=WindowsWindow; version(Demo) void main() { auto wv = new WebView(true, null); wv.navigate("http://dpldocs.info/"); wv.setTitle("omg a D webview"); wv.setSize(500, 500, true); wv.eval("console.log('just testing');"); wv.run(); } /++ +/ class WebView : browser_engine { /++ Creates a new webview instance. If dbg is non-zero - developer tools will be enabled (if the platform supports them). Window parameter can be a pointer to the native window handle. If it's non-null - then child WebView is embedded into the given parent window. Otherwise a new window is created. Depending on the platform, a GtkWindow, NSWindow or HWND pointer can be passed here. +/ this(bool dbg, void* window) { super(&on_message, dbg, window); } extern(C) static void on_message(const char*) {} /// Destroys a webview and closes the native window. void destroy() { } /// Runs the main loop until it's terminated. After this function exits - you /// must destroy the webview. override void run() { super.run(); } /// Stops the main loop. It is safe to call this function from another other /// background thread. override void terminate() { super.terminate(); } /+ /// Posts a function to be executed on the main thread. You normally do not need /// to call this function, unless you want to tweak the native window. void dispatch(void function(WebView w, void *arg) fn, void *arg) {} +/ /// Returns a native window handle pointer. When using GTK backend the pointer /// is GtkWindow pointer, when using Cocoa backend the pointer is NSWindow /// pointer, when using Win32 backend the pointer is HWND pointer. void* getWindow() { return m_window; } /// Updates the title of the native window. Must be called from the UI thread. override void setTitle(const char *title) { super.setTitle(title); } /// Navigates webview to the given URL. URL may be a data URI. override void navigate(const char *url) { super.navigate(url); } /// Injects JavaScript code at the initialization of the new page. Every time /// the webview will open a the new page - this initialization code will be /// executed. It is guaranteed that code is executed before window.onload. override void init(const char *js) { super.init(js); } /// Evaluates arbitrary JavaScript code. Evaluation happens asynchronously, also /// the result of the expression is ignored. Use RPC bindings if you want to /// receive notifications about the results of the evaluation. override void eval(const char *js) { super.eval(js); } /// Binds a native C callback so that it will appear under the given name as a /// global JavaScript function. Internally it uses webview_init(). Callback /// receives a request string and a user-provided argument pointer. Request /// string is a JSON array of all the arguments passed to the JavaScript /// function. void bind(const char *name, void function(const char *, void *) fn, void *arg) {} /// Allows to return a value from the native binding. Original request pointer /// must be provided to help internal RPC engine match requests with responses. /// If status is zero - result is expected to be a valid JSON result value. /// If status is not zero - result is an error JSON object. void webview_return(const char *req, int status, const char *result) {} /* void on_message(const char *msg) { auto seq = json_parse(msg, "seq", 0); auto name = json_parse(msg, "name", 0); auto args = json_parse(msg, "args", 0); auto fn = bindings[name]; if (fn == null) { return; } std::async(std::launch::async, [=]() { auto result = (*fn)(args); dispatch([=]() { eval(("var b = window['" + name + "'];b['callbacks'][" + seq + "](" + result + ");b['callbacks'][" + seq + "] = undefined;b['errors'][" + seq + "] = undefined;") .c_str()); }); }); } std::map bindings; alias binding_t = std::function; void bind(const char *name, binding_t f) { auto js = "(function() { var name = '" + std::string(name) + "';" + R"( window[name] = function() { var me = window[name]; var errors = me['errors']; var callbacks = me['callbacks']; if (!callbacks) { callbacks = {}; me['callbacks'] = callbacks; } if (!errors) { errors = {}; me['errors'] = errors; } var seq = (me['lastSeq'] || 0) + 1; me['lastSeq'] = seq; var promise = new Promise(function(resolve, reject) { callbacks[seq] = resolve; errors[seq] = reject; }); window.external.invoke(JSON.stringify({ name: name, seq:seq, args: Array.prototype.slice.call(arguments), })); return promise; } })())"; init(js.c_str()); bindings[name] = new binding_t(f); } */ } private extern(C) { alias dispatch_fn_t = void function(); alias msg_cb_t = void function(const char *msg); } version(WEBVIEW_GTK) { pragma(lib, "gtk-3"); pragma(lib, "glib-2.0"); pragma(lib, "gobject-2.0"); pragma(lib, "webkit2gtk-4.0"); pragma(lib, "javascriptcoregtk-4.0"); private extern(C) { import core.stdc.config; alias GtkWidget = void; enum GtkWindowType { GTK_WINDOW_TOPLEVEL = 0 } bool gtk_init_check(int*, char***); GtkWidget* gtk_window_new(GtkWindowType); c_ulong g_signal_connect_data(void*, const char*, void* /* function pointer!!! */, void*, void*, int); GtkWidget* webkit_web_view_new(); alias WebKitUserContentManager = void; WebKitUserContentManager* webkit_web_view_get_user_content_manager(GtkWidget*); void gtk_container_add(GtkWidget*, GtkWidget*); void gtk_widget_grab_focus(GtkWidget*); void gtk_widget_show_all(GtkWidget*); void gtk_main(); void gtk_main_quit(); void webkit_web_view_load_uri(GtkWidget*, const char*); alias WebKitSettings = void; WebKitSettings* webkit_web_view_get_settings(GtkWidget*); void webkit_settings_set_enable_write_console_messages_to_stdout(WebKitSettings*, bool); void webkit_settings_set_enable_developer_extras(WebKitSettings*, bool); void webkit_user_content_manager_register_script_message_handler(WebKitUserContentManager*, const char*); alias JSCValue = void; alias WebKitJavascriptResult = void; JSCValue* webkit_javascript_result_get_js_value(WebKitJavascriptResult*); char* jsc_value_to_string(JSCValue*); void g_free(void*); void webkit_web_view_run_javascript(GtkWidget*, const char*, void*, void*, void*); alias WebKitUserScript = void; void webkit_user_content_manager_add_script(WebKitUserContentManager*, WebKitUserScript*); WebKitUserScript* webkit_user_script_new(const char*, WebKitUserContentInjectedFrames, WebKitUserScriptInjectionTime, const char*, const char*); enum WebKitUserContentInjectedFrames { WEBKIT_USER_CONTENT_INJECT_ALL_FRAMES, WEBKIT_USER_CONTENT_INJECT_TOP_FRAME } enum WebKitUserScriptInjectionTime { WEBKIT_USER_SCRIPT_INJECT_AT_DOCUMENT_START, WEBKIT_USER_SCRIPT_INJECT_AT_DOCUMENT_END } void gtk_window_set_title(GtkWidget*, const char*); void gtk_window_set_resizable(GtkWidget*, bool); void gtk_window_set_default_size(GtkWidget*, int, int); void gtk_widget_set_size_request(GtkWidget*, int, int); } private class browser_engine { static extern(C) void ondestroy (GtkWidget *w, void* arg) { (cast(browser_engine) arg).terminate(); } static extern(C) void smr(WebKitUserContentManager* m, WebKitJavascriptResult* r, void* arg) { auto w = cast(browser_engine) arg; JSCValue *value = webkit_javascript_result_get_js_value(r); auto s = jsc_value_to_string(value); w.m_cb(s); g_free(s); } this(msg_cb_t cb, bool dbg, void* window) { m_cb = cb; gtk_init_check(null, null); m_window = cast(GtkWidget*) window; if (m_window == null) m_window = gtk_window_new(GtkWindowType.GTK_WINDOW_TOPLEVEL); g_signal_connect_data(m_window, "destroy", &ondestroy, cast(void*) this, null, 0); m_webview = webkit_web_view_new(); WebKitUserContentManager* manager = webkit_web_view_get_user_content_manager(m_webview); g_signal_connect_data(manager, "script-message-received::external", &smr, cast(void*) this, null, 0); webkit_user_content_manager_register_script_message_handler(manager, "external"); init("window.external={invoke:function(s){window.webkit.messageHandlers.external.postMessage(s);}}"); gtk_container_add(m_window, m_webview); gtk_widget_grab_focus(m_webview); if (dbg) { WebKitSettings *settings = webkit_web_view_get_settings(m_webview); webkit_settings_set_enable_write_console_messages_to_stdout(settings, true); webkit_settings_set_enable_developer_extras(settings, true); } gtk_widget_show_all(m_window); } void run() { gtk_main(); } void terminate() { gtk_main_quit(); } void navigate(const char *url) { webkit_web_view_load_uri(m_webview, url); } void setTitle(const char* title) { gtk_window_set_title(m_window, title); } /+ void dispatch(std::function f) { g_idle_add_full(G_PRIORITY_HIGH_IDLE, (GSourceFunc)([](void *f) -> int { (*static_cast(f))(); return G_SOURCE_REMOVE; }), new std::function(f), [](void *f) { delete static_cast(f); }); } +/ void setSize(int width, int height, bool resizable) { gtk_window_set_resizable(m_window, resizable); if (resizable) { gtk_window_set_default_size(m_window, width, height); } gtk_widget_set_size_request(m_window, width, height); } void init(const char *js) { WebKitUserContentManager *manager = webkit_web_view_get_user_content_manager(m_webview); webkit_user_content_manager_add_script( manager, webkit_user_script_new( js, WebKitUserContentInjectedFrames.WEBKIT_USER_CONTENT_INJECT_TOP_FRAME, WebKitUserScriptInjectionTime.WEBKIT_USER_SCRIPT_INJECT_AT_DOCUMENT_START, null, null)); } void eval(const char *js) { webkit_web_view_run_javascript(m_webview, js, null, null, null); } protected: GtkWidget* m_window; GtkWidget* m_webview; msg_cb_t m_cb; } } else version(WEBVIEW_COCOA) { /+ // // ==================================================================== // // This implementation uses Cocoa WKWebView backend on macOS. It is // written using ObjC runtime and uses WKWebView class as a browser runtime. // You should pass "-framework Webkit" flag to the compiler. // // ==================================================================== // #define OBJC_OLD_DISPATCH_PROTOTYPES 1 #include #include #define NSBackingStoreBuffered 2 #define NSWindowStyleMaskResizable 8 #define NSWindowStyleMaskMiniaturizable 4 #define NSWindowStyleMaskTitled 1 #define NSWindowStyleMaskClosable 2 #define NSApplicationActivationPolicyRegular 0 #define WKUserScriptInjectionTimeAtDocumentStart 0 id operator"" _cls(const char *s, std::size_t sz) { return (id)objc_getClass(s); } SEL operator"" _sel(const char *s, std::size_t sz) { return sel_registerName(s); } id operator"" _str(const char *s, std::size_t sz) { return objc_msgSend("NSString"_cls, "stringWithUTF8String:"_sel, s); } class browser_engine { public: browser_engine(msg_cb_t cb, bool dbg, void *window) : m_cb(cb) { // Application id app = objc_msgSend("NSApplication"_cls, "sharedApplication"_sel); objc_msgSend(app, "setActivationPolicy:"_sel, NSApplicationActivationPolicyRegular); // Delegate auto cls = objc_allocateClassPair((Class) "NSObject"_cls, "AppDelegate", 0); class_addProtocol(cls, objc_getProtocol("NSApplicationDelegate")); class_addProtocol(cls, objc_getProtocol("WKScriptMessageHandler")); class_addMethod( cls, "applicationShouldTerminateAfterLastWindowClosed:"_sel, (IMP)(+[](id self, SEL cmd, id notification) -> BOOL { return 1; }), "c@:@"); class_addMethod( cls, "userContentController:didReceiveScriptMessage:"_sel, (IMP)(+[](id self, SEL cmd, id notification, id msg) { auto w = (browser_engine *)objc_getAssociatedObject(self, "webview"); w->m_cb((const char *)objc_msgSend(objc_msgSend(msg, "body"_sel), "UTF8String"_sel)); }), "v@:@@"); objc_registerClassPair(cls); auto delegate = objc_msgSend((id)cls, "new"_sel); objc_setAssociatedObject(delegate, "webview", (id)this, OBJC_ASSOCIATION_ASSIGN); objc_msgSend(app, sel_registerName("setDelegate:"), delegate); // Main window if (window is null) { m_window = objc_msgSend("NSWindow"_cls, "alloc"_sel); m_window = objc_msgSend( m_window, "initWithContentRect:styleMask:backing:defer:"_sel, CGRectMake(0, 0, 0, 0), 0, NSBackingStoreBuffered, 0); setSize(480, 320, true); } else { m_window = (id)window; } // Webview auto config = objc_msgSend("WKWebViewConfiguration"_cls, "new"_sel); m_manager = objc_msgSend(config, "userContentController"_sel); m_webview = objc_msgSend("WKWebView"_cls, "alloc"_sel); objc_msgSend(m_webview, "initWithFrame:configuration:"_sel, CGRectMake(0, 0, 0, 0), config); objc_msgSend(m_manager, "addScriptMessageHandler:name:"_sel, delegate, "external"_str); init(R"script( window.external = { invoke: function(s) { window.webkit.messageHandlers.external.postMessage(s); }, }; )script"); if (dbg) { objc_msgSend(objc_msgSend(config, "preferences"_sel), "setValue:forKey:"_sel, 1, "developerExtrasEnabled"_str); } objc_msgSend(m_window, "setContentView:"_sel, m_webview); objc_msgSend(m_window, "makeKeyAndOrderFront:"_sel, null); } ~browser_engine() { close(); } void terminate() { close(); objc_msgSend("NSApp"_cls, "terminate:"_sel, null); } void run() { id app = objc_msgSend("NSApplication"_cls, "sharedApplication"_sel); dispatch([&]() { objc_msgSend(app, "activateIgnoringOtherApps:"_sel, 1); }); objc_msgSend(app, "run"_sel); } void dispatch(std::function f) { dispatch_async_f(dispatch_get_main_queue(), new dispatch_fn_t(f), (dispatch_function_t)([](void *arg) { auto f = static_cast(arg); (*f)(); delete f; })); } void setTitle(const char *title) { objc_msgSend( m_window, "setTitle:"_sel, objc_msgSend("NSString"_cls, "stringWithUTF8String:"_sel, title)); } void setSize(int width, int height, bool resizable) { auto style = NSWindowStyleMaskTitled | NSWindowStyleMaskClosable | NSWindowStyleMaskMiniaturizable; if (resizable) { style = style | NSWindowStyleMaskResizable; } objc_msgSend(m_window, "setStyleMask:"_sel, style); objc_msgSend(m_window, "setFrame:display:animate:"_sel, CGRectMake(0, 0, width, height), 1, 0); } void navigate(const char *url) { auto nsurl = objc_msgSend( "NSURL"_cls, "URLWithString:"_sel, objc_msgSend("NSString"_cls, "stringWithUTF8String:"_sel, url)); objc_msgSend( m_webview, "loadRequest:"_sel, objc_msgSend("NSURLRequest"_cls, "requestWithURL:"_sel, nsurl)); } void init(const char *js) { objc_msgSend( m_manager, "addUserScript:"_sel, objc_msgSend( objc_msgSend("WKUserScript"_cls, "alloc"_sel), "initWithSource:injectionTime:forMainFrameOnly:"_sel, objc_msgSend("NSString"_cls, "stringWithUTF8String:"_sel, js), WKUserScriptInjectionTimeAtDocumentStart, 1)); } void eval(const char *js) { objc_msgSend(m_webview, "evaluateJavaScript:completionHandler:"_sel, objc_msgSend("NSString"_cls, "stringWithUTF8String:"_sel, js), null); } protected: void close() { objc_msgSend(m_window, "close"_sel); } id m_window; id m_webview; id m_manager; msg_cb_t m_cb; }; +/ } else version(WindowsWindow) { /+ // // ==================================================================== // // This implementation uses Win32 API to create a native window. It can // use either MSHTML or EdgeHTML backend as a browser engine. // // ==================================================================== // #define WIN32_LEAN_AND_MEAN #include pragma(lib, "user32"); class browser_window { public: browser_window(msg_cb_t cb, void *window) : m_cb(cb) { if (window is null) { WNDCLASSEX wc; ZeroMemory(&wc, sizeof(WNDCLASSEX)); wc.cbSize = sizeof(WNDCLASSEX); wc.hInstance = GetModuleHandle(null); wc.lpszClassName = "webview"; wc.lpfnWndProc = (WNDPROC)(+[](HWND hwnd, UINT msg, WPARAM wp, LPARAM lp) -> int { auto w = (browser_window *)GetWindowLongPtr(hwnd, GWLP_USERDATA); switch (msg) { case WM_SIZE: w->resize(); break; case WM_CLOSE: DestroyWindow(hwnd); break; case WM_DESTROY: w->terminate(); break; default: return DefWindowProc(hwnd, msg, wp, lp); } return 0; }); RegisterClassEx(&wc); m_window = CreateWindow("webview", "", WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, 640, 480, null, null, GetModuleHandle(null), null); SetWindowLongPtr(m_window, GWLP_USERDATA, (LONG_PTR)this); } else { m_window = *(static_cast(window)); } ShowWindow(m_window, SW_SHOW); UpdateWindow(m_window); SetFocus(m_window); } void run() { MSG msg; BOOL res; while ((res = GetMessage(&msg, null, 0, 0)) != -1) { if (msg.hwnd) { TranslateMessage(&msg); DispatchMessage(&msg); continue; } if (msg.message == WM_APP) { auto f = (dispatch_fn_t *)(msg.lParam); (*f)(); delete f; } else if (msg.message == WM_QUIT) { return; } } } void terminate() { PostQuitMessage(0); } void dispatch(dispatch_fn_t f) { PostThreadMessage(m_main_thread, WM_APP, 0, (LPARAM) new dispatch_fn_t(f)); } void setTitle(const char *title) { SetWindowText(m_window, title); } void setSize(int width, int height, bool resizable) { RECT r; r.left = 50; r.top = 50; r.right = width; r.bottom = height; AdjustWindowRect(&r, WS_OVERLAPPEDWINDOW, 0); SetWindowPos(m_window, null, r.left, r.top, r.right - r.left, r.bottom - r.top, SWP_NOZORDER | SWP_NOACTIVATE | SWP_FRAMECHANGED); } protected: virtual void resize() {} HWND m_window; DWORD m_main_thread = GetCurrentThreadId(); msg_cb_t m_cb; }; +/ } version(WEBVIEW_MSHTML) { /+ #include #include #include #include #include pragma(lib, "ole32"); pragma(lib, "oleaut32"); #define DISPID_EXTERNAL_INVOKE 0x1000 class browser_engine : public browser_window, public IOleClientSite, public IOleInPlaceSite, public IOleInPlaceFrame, public IDocHostUIHandler, public DWebBrowserEvents2 { public: browser_engine(msg_cb_t cb, bool dbg, void *window) : browser_window(cb, window) { RECT rect; LPCLASSFACTORY cf = null; IOleObject *obj = null; fix_ie_compat_mode(); OleInitialize(null); CoGetClassObject(CLSID_WebBrowser, CLSCTX_INPROC_SERVER | CLSCTX_INPROC_HANDLER, null, IID_IClassFactory, (void **)&cf); cf->CreateInstance(null, IID_IOleObject, (void **)&obj); cf->Release(); obj->SetClientSite(this); OleSetContainedObject(obj, TRUE); GetWindowRect(m_window, &rect); obj->DoVerb(OLEIVERB_INPLACEACTIVATE, null, this, -1, m_window, &rect); obj->QueryInterface(IID_IWebBrowser2, (void **)&m_webview); IConnectionPointContainer *cpc; IConnectionPoint *cp; DWORD cookie; m_webview->QueryInterface(IID_IConnectionPointContainer, (void **)&cpc); cpc->FindConnectionPoint(DIID_DWebBrowserEvents2, &cp); cpc->Release(); cp->Advise(static_cast(this), &cookie); resize(); navigate("about:blank"); } ~browser_engine() { OleUninitialize(); } void navigate(const char *url) { VARIANT v; DWORD size = MultiByteToWideChar(CP_UTF8, 0, url, -1, 0, 0); WCHAR *ws = (WCHAR *)GlobalAlloc(GMEM_FIXED, sizeof(WCHAR) * size); MultiByteToWideChar(CP_UTF8, 0, url, -1, ws, size); VariantInit(&v); v.vt = VT_BSTR; v.bstrVal = SysAllocString(ws); m_webview->Navigate2(&v, null, null, null, null); VariantClear(&v); } void eval(const char *js) { // TODO } private: IWebBrowser2 *m_webview; int fix_ie_compat_mode() { const char *WEBVIEW_KEY_FEATURE_BROWSER_EMULATION = "Software\\Microsoft\\Internet " "Explorer\\Main\\FeatureControl\\FEATURE_BROWSER_EMULATION"; HKEY hKey; DWORD ie_version = 11000; TCHAR appname[MAX_PATH + 1]; TCHAR *p; if (GetModuleFileName(null, appname, MAX_PATH + 1) == 0) { return -1; } for (p = &appname[strlen(appname) - 1]; p != appname && *p != '\\'; p--) { } p++; if (RegCreateKey(HKEY_CURRENT_USER, WEBVIEW_KEY_FEATURE_BROWSER_EMULATION, &hKey) != ERROR_SUCCESS) { return -1; } if (RegSetValueEx(hKey, p, 0, REG_DWORD, (BYTE *)&ie_version, sizeof(ie_version)) != ERROR_SUCCESS) { RegCloseKey(hKey); return -1; } RegCloseKey(hKey); return 0; } // Inheruted via browser_window void resize() override { RECT rect; GetClientRect(m_window, &rect); m_webview->put_Left(0); m_webview->put_Top(0); m_webview->put_Width(rect.right); m_webview->put_Height(rect.bottom); m_webview->put_Visible(VARIANT_TRUE); } // Inherited via IUnknown ULONG __stdcall AddRef(void) override { return 1; } ULONG __stdcall Release(void) override { return 1; } HRESULT __stdcall QueryInterface(REFIID riid, void **obj) override { if (riid == IID_IUnknown || riid == IID_IOleClientSite) { *obj = static_cast(this); return S_OK; } if (riid == IID_IOleInPlaceSite) { *obj = static_cast(this); return S_OK; } if (riid == IID_IDocHostUIHandler) { *obj = static_cast(this); return S_OK; } if (riid == IID_IDispatch || riid == DIID_DWebBrowserEvents2) { *obj = static_cast(this); return S_OK; } *obj = null; return E_NOINTERFACE; } // Inherited via IOleClientSite HRESULT __stdcall SaveObject(void) override { return E_NOTIMPL; } HRESULT __stdcall GetMoniker(DWORD dwAssign, DWORD dwWhichMoniker, IMoniker **ppmk) override { return E_NOTIMPL; } HRESULT __stdcall GetContainer(IOleContainer **ppContainer) override { *ppContainer = null; return E_NOINTERFACE; } HRESULT __stdcall ShowObject(void) override { return S_OK; } HRESULT __stdcall OnShowWindow(BOOL fShow) override { return S_OK; } HRESULT __stdcall RequestNewObjectLayout(void) override { return E_NOTIMPL; } // Inherited via IOleInPlaceSite HRESULT __stdcall GetWindow(HWND *phwnd) override { *phwnd = m_window; return S_OK; } HRESULT __stdcall ContextSensitiveHelp(BOOL fEnterMode) override { return E_NOTIMPL; } HRESULT __stdcall CanInPlaceActivate(void) override { return S_OK; } HRESULT __stdcall OnInPlaceActivate(void) override { return S_OK; } HRESULT __stdcall OnUIActivate(void) override { return S_OK; } HRESULT __stdcall GetWindowContext( IOleInPlaceFrame **ppFrame, IOleInPlaceUIWindow **ppDoc, LPRECT lprcPosRect, LPRECT lprcClipRect, LPOLEINPLACEFRAMEINFO lpFrameInfo) override { *ppFrame = static_cast(this); *ppDoc = null; lpFrameInfo->fMDIApp = FALSE; lpFrameInfo->hwndFrame = m_window; lpFrameInfo->haccel = 0; lpFrameInfo->cAccelEntries = 0; return S_OK; } HRESULT __stdcall Scroll(SIZE scrollExtant) override { return E_NOTIMPL; } HRESULT __stdcall OnUIDeactivate(BOOL fUndoable) override { return S_OK; } HRESULT __stdcall OnInPlaceDeactivate(void) override { return S_OK; } HRESULT __stdcall DiscardUndoState(void) override { return E_NOTIMPL; } HRESULT __stdcall DeactivateAndUndo(void) override { return E_NOTIMPL; } HRESULT __stdcall OnPosRectChange(LPCRECT lprcPosRect) override { IOleInPlaceObject *inplace; m_webview->QueryInterface(IID_IOleInPlaceObject, (void **)&inplace); inplace->SetObjectRects(lprcPosRect, lprcPosRect); return S_OK; } // Inherited via IDocHostUIHandler HRESULT __stdcall ShowContextMenu(DWORD dwID, POINT *ppt, IUnknown *pcmdtReserved, IDispatch *pdispReserved) override { return S_OK; } HRESULT __stdcall GetHostInfo(DOCHOSTUIINFO *pInfo) override { pInfo->dwDoubleClick = DOCHOSTUIDBLCLK_DEFAULT; pInfo->dwFlags = DOCHOSTUIFLAG_NO3DBORDER; return S_OK; } HRESULT __stdcall ShowUI(DWORD dwID, IOleInPlaceActiveObject *pActiveObject, IOleCommandTarget *pCommandTarget, IOleInPlaceFrame *pFrame, IOleInPlaceUIWindow *pDoc) override { return S_OK; } HRESULT __stdcall HideUI(void) override { return S_OK; } HRESULT __stdcall UpdateUI(void) override { return S_OK; } HRESULT __stdcall EnableModeless(BOOL fEnable) override { return S_OK; } HRESULT __stdcall OnDocWindowActivate(BOOL fActivate) override { return S_OK; } HRESULT __stdcall OnFrameWindowActivate(BOOL fActivate) override { return S_OK; } HRESULT __stdcall ResizeBorder(LPCRECT prcBorder, IOleInPlaceUIWindow *pUIWindow, BOOL fRameWindow) override { return S_OK; } HRESULT __stdcall GetOptionKeyPath(LPOLESTR *pchKey, DWORD dw) override { return S_FALSE; } HRESULT __stdcall GetDropTarget(IDropTarget *pDropTarget, IDropTarget **ppDropTarget) override { return E_NOTIMPL; } HRESULT __stdcall GetExternal(IDispatch **ppDispatch) override { *ppDispatch = static_cast(this); return S_OK; } HRESULT __stdcall TranslateUrl(DWORD dwTranslate, LPWSTR pchURLIn, LPWSTR *ppchURLOut) override { *ppchURLOut = null; return S_FALSE; } HRESULT __stdcall FilterDataObject(IDataObject *pDO, IDataObject **ppDORet) override { *ppDORet = null; return S_FALSE; } HRESULT __stdcall TranslateAcceleratorA(LPMSG lpMsg, const GUID *pguidCmdGroup, DWORD nCmdID) { return S_FALSE; } // Inherited via IOleInPlaceFrame HRESULT __stdcall GetBorder(LPRECT lprectBorder) override { return S_OK; } HRESULT __stdcall RequestBorderSpace(LPCBORDERWIDTHS pborderwidths) override { return S_OK; } HRESULT __stdcall SetBorderSpace(LPCBORDERWIDTHS pborderwidths) override { return S_OK; } HRESULT __stdcall SetActiveObject(IOleInPlaceActiveObject *pActiveObject, LPCOLESTR pszObjName) override { return S_OK; } HRESULT __stdcall InsertMenus(HMENU hmenuShared, LPOLEMENUGROUPWIDTHS lpMenuWidths) override { return S_OK; } HRESULT __stdcall SetMenu(HMENU hmenuShared, HOLEMENU holemenu, HWND hwndActiveObject) override { return S_OK; } HRESULT __stdcall RemoveMenus(HMENU hmenuShared) override { return S_OK; } HRESULT __stdcall SetStatusText(LPCOLESTR pszStatusText) override { return S_OK; } HRESULT __stdcall TranslateAcceleratorA(LPMSG lpmsg, WORD wID) { return S_OK; } // Inherited via IDispatch HRESULT __stdcall GetTypeInfoCount(UINT *pctinfo) override { return S_OK; } HRESULT __stdcall GetTypeInfo(UINT iTInfo, LCID lcid, ITypeInfo **ppTInfo) override { return S_OK; } HRESULT __stdcall GetIDsOfNames(REFIID riid, LPOLESTR *rgszNames, UINT cNames, LCID lcid, DISPID *rgDispId) override { *rgDispId = DISPID_EXTERNAL_INVOKE; return S_OK; } HRESULT __stdcall Invoke(DISPID dispIdMember, REFIID riid, LCID lcid, WORD wFlags, DISPPARAMS *pDispParams, VARIANT *pVarResult, EXCEPINFO *pExcepInfo, UINT *puArgErr) override { if (dispIdMember == DISPID_NAVIGATECOMPLETE2) { } else if (dispIdMember == DISPID_DOCUMENTCOMPLETE) { } else if (dispIdMember == DISPID_EXTERNAL_INVOKE) { } return S_OK; } }; +/ } else version(WEBVIEW_EDGE) { /+ #include #include #include #pragma comment(lib, "windowsapp") using namespace winrt; using namespace Windows::Foundation; using namespace Windows::Web::UI; using namespace Windows::Web::UI::Interop; class browser_engine : public browser_window { public: browser_engine(msg_cb_t cb, bool dbg, void *window) : browser_window(cb, window) { init_apartment(winrt::apartment_type::single_threaded); m_process = WebViewControlProcess(); auto op = m_process.CreateWebViewControlAsync( reinterpret_cast(m_window), Rect()); if (op.Status() != AsyncStatus::Completed) { handle h(CreateEvent(null, false, false, null)); op.Completed([h = h.get()](auto, auto) { SetEvent(h); }); HANDLE hs[] = {h.get()}; DWORD i; CoWaitForMultipleHandles(COWAIT_DISPATCH_WINDOW_MESSAGES | COWAIT_DISPATCH_CALLS | COWAIT_INPUTAVAILABLE, INFINITE, 1, hs, &i); } m_webview = op.GetResults(); m_webview.Settings().IsScriptNotifyAllowed(true); m_webview.IsVisible(true); m_webview.ScriptNotify([=](auto const &sender, auto const &args) { std::string s = winrt::to_string(args.Value()); m_cb(s.c_str()); }); m_webview.NavigationStarting([=](auto const &sender, auto const &args) { m_webview.AddInitializeScript(winrt::to_hstring(init_js)); }); init("window.external.invoke = s => window.external.notify(s)"); resize(); } void navigate(const char *url) { Uri uri(winrt::to_hstring(url)); // TODO: if url starts with 'data:text/html,' prefix then use it as a string m_webview.Navigate(uri); // m_webview.NavigateToString(winrt::to_hstring(url)); } void init(const char *js) { init_js = init_js + "(function(){" + js + "})();"; } void eval(const char *js) { m_webview.InvokeScriptAsync( L"eval", single_threaded_vector({winrt::to_hstring(js)})); } private: void resize() { RECT r; GetClientRect(m_window, &r); Rect bounds(r.left, r.top, r.right - r.left, r.bottom - r.top); m_webview.Bounds(bounds); } WebViewControlProcess m_process; WebViewControl m_webview = null; std::string init_js = ""; }; +/ }