From 677d4a3da954834812876a62c86d2f85ea309f90 Mon Sep 17 00:00:00 2001 From: "Adam D. Ruppe" Date: Sat, 30 Nov 2019 23:20:22 -0500 Subject: [PATCH] initial add --- webview.d | 990 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 990 insertions(+) create mode 100644 webview.d diff --git a/webview.d b/webview.d new file mode 100644 index 0000000..ee70ae6 --- /dev/null +++ b/webview.d @@ -0,0 +1,990 @@ +/++ + 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.set_size(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 *get_window() { 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); + } + +// +// ==================================================================== +// +// This implementation uses webkit2gtk backend. It requires gtk+3.0 and +// webkit2gtk-4.0 libraries. Proper compiler flags can be retrieved via: +// +// pkg-config --cflags --libs gtk+-3.0 webkit2gtk-4.0 +// +// ==================================================================== +// +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 set_size(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); + set_size(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 set_size(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 set_size(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 = ""; + }; ++/ +} +