/**************************************************************************** MIT License Portions copyright (c) 2017 Serge Zaitsev https://axmolengine.github.io/ 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. ****************************************************************************/ #if defined(_WIN32) && defined(_AX_HAVE_WEBVIEW2) # include "UIWebViewImpl-win32.h" # include "UIWebView.h" # include "base/CCDirector.h" # include "platform/CCFileUtils.h" # include "platform/CCGLView.h" # include "ui/UIHelper.h" # include "rapidjson/document.h" # include "rapidjson/stringbuffer.h" # include "rapidjson/writer.h" # include "base/ccUtils.h" # define WIN32_LEAN_AND_MEAN # include # include # include # include # include # pragma comment(lib, "user32.lib") # pragma comment(lib, "Shlwapi.lib") // Edge/Chromium headers and libs # include "WebView2.h" # pragma comment(lib, "ole32.lib") # pragma comment(lib, "oleaut32.lib") # include "platform/CCPlatformConfig.h" # include "ntcvt/ntcvt.hpp" USING_NS_AX; using namespace rapidjson; using msg_cb_t = std::function; inline std::string htmlFromUri(std::string_view s) { if (s.substr(0, 15) == "data:text/html,") { return utils::urlDecode(s.substr(15)); } return ""; } inline int jsonUnescape(const char* s, size_t n, char* out) { int r = 0; if (*s++ != '"') { return -1; } while (n > 2) { char c = *s; if (c == '\\') { s++; n--; switch (*s) { case 'b': c = '\b'; break; case 'f': c = '\f'; break; case 'n': c = '\n'; break; case 'r': c = '\r'; break; case 't': c = '\t'; break; case '\\': c = '\\'; break; case '/': c = '/'; break; case '\"': c = '\"'; break; default: // TODO: support unicode decoding return -1; } } if (out != NULL) { *out++ = c; } s++; n--; r++; } if (*s != '"') { return -1; } if (out != NULL) { *out = '\0'; } return r; } // These are the results that must be returned by this method // assert(jsonParse(R"({"foo":"bar"})", "foo", -1) == "bar"); // assert(jsonParse(R"({"foo":""})", "foo", -1) == ""); // assert(jsonParse(R"(["foo", "bar", "baz"])", "", 0) == "foo"); // assert(jsonParse(R"(["foo", "bar", "baz"])", "", 2) == "baz"); // The following is a special case, where the exact json string is not returned due // to how rapidjson re-creates the nested object, original: "{"bar": 1}", parsed result: "{"bar":1}" // assert(jsonParse(R"({"foo": {"bar": 1}})", "foo", -1) == R"({"bar":1})"); inline std::string jsonParse(std::string_view s, std::string_view key, const int index) { const char* value = nullptr; size_t value_sz{}; StringBuffer sb; Writer writer(sb); Document d; d.Parse(s.data()); if (key.empty() && index > -1) { if (d.IsArray()) { auto&& jsonArray = d.GetArray(); if (SizeType(index) < jsonArray.Size()) { auto&& arrayValue = jsonArray[SizeType(index)]; value = arrayValue.GetString(); value_sz = arrayValue.GetStringLength(); } } } else { auto&& fieldItr = d.FindMember(key.data()); if (fieldItr != d.MemberEnd()) { auto&& jsonValue = fieldItr->value; if (jsonValue.IsString()) { value = jsonValue.GetString(); value_sz = jsonValue.GetStringLength(); } else { jsonValue.Accept(writer); value = sb.GetString(); value_sz = sb.GetLength(); } } } if (value != nullptr) { if (value[0] != '"') { return std::string(value, value_sz); } const auto n = jsonUnescape(value, value_sz, nullptr); if (n > 0) { const auto decoded = std::unique_ptr(new char[n + 1]); jsonUnescape(value, value_sz, decoded.get()); return std::string(decoded.get(), n); } } return ""; } template static std::string getUriStringFromArgs(ArgType* args) { if (args != nullptr) { LPWSTR uri; args->get_Uri(&uri); std::wstring ws(uri); std::string result = std::string(ws.begin(), ws.end()); return result; } return {}; } static std::string getDataURI(std::string_view data, std::string_view mime_type) { auto encodedData = utils::base64Encode(data); return std::string{"data:"}.append(mime_type).append(";base64,").append(utils::urlEncode(encodedData)); } static double getDeviceScaleFactor() { static double scale_factor = 1.0; static bool initialized = false; if (!initialized) { // This value is safe to cache for the life time of the app since the user // must logout to change the DPI setting. This value also applies to all // screens. # if AX_TARGET_PLATFORM != AX_PLATFORM_WINRT HDC screen_dc = ::GetDC(NULL); int dpi_x = GetDeviceCaps(screen_dc, LOGPIXELSX); scale_factor = static_cast(dpi_x) / 96.0; ::ReleaseDC(NULL, screen_dc); # endif initialized = true; } return scale_factor; } // declarate class Win32WebControl { public: Win32WebControl(); bool createWebView(const std::function& shouldStartLoading, const std::function& didFinishLoading, const std::function& didFailLoading, const std::function& onJsCallback); void removeWebView(); void setWebViewRect(const int left, const int top, const int width, const int height); void setJavascriptInterfaceScheme(std::string_view scheme); void loadHTMLString(std::string_view html, std::string_view baseURL); void loadURL(std::string_view url, bool cleanCachedData); void loadFile(std::string_view filePath); void stopLoading(); void reload() const; bool canGoBack() const; bool canGoForward() const; void goBack() const; void goForward() const; void evaluateJS(std::string_view js); void setScalesPageToFit(const bool scalesPageToFit); void setWebViewVisible(const bool visible) const; void setBounces(bool bounces); void setOpacityWebView(float opacity); float getOpacityWebView() const; void setBackgroundTransparent(); private: ICoreWebView2* m_webview = nullptr; ICoreWebView2Controller* m_controller = nullptr; HWND m_window{}; POINT m_minsz = POINT{0, 0}; POINT m_maxsz = POINT{0, 0}; DWORD m_main_thread = GetCurrentThreadId(); std::string m_jsScheme; bool _scalesPageToFit{}; std::function _shouldStartLoading; std::function _didFinishLoading; std::function _didFailLoading; std::function _onJsCallback; static bool s_isInitialized; static void lazyInit(); static LPWSTR to_lpwstr(std::string_view s) { const int n = MultiByteToWideChar(CP_UTF8, 0, s.data(), -1, NULL, 0); auto* ws = new wchar_t[n]; MultiByteToWideChar(CP_UTF8, 0, s.data(), -1, ws, n); return ws; } bool embed(HWND wnd, bool debug, msg_cb_t cb) { std::atomic_flag flag = ATOMIC_FLAG_INIT; flag.test_and_set(); char currentExePath[MAX_PATH]; GetModuleFileNameA(NULL, currentExePath, MAX_PATH); # if AX_TARGET_PLATFORM != AX_PLATFORM_WINRT const char* currentExeName = PathFindFileNameA(currentExePath); #else const char* currentExeName = "axmol-app"; #endif /* * Note: New OS feature 'Beta: Use Unicode UTF-8 for worldwide language support' since win10/win11 * - OFF: GetACP() equal to current system locale, such as chinese simplified is 936, english is 437 * - ON: GetACP() always equal to 65001(UTF-8) * Remark: * The macro CP_ACP for ntcvt::from_chars works for converting chraset from current code page(936,437,65001) to utf-16 */ std::wstring userDataFolder = ntcvt::from_chars(std::getenv("APPDATA"), CP_ACP); std::wstring currentExeNameW = ntcvt::from_chars(currentExeName, CP_ACP); HRESULT res = CreateCoreWebView2EnvironmentWithOptions( nullptr, (userDataFolder + L"/" + currentExeNameW).c_str(), nullptr, new webview2_com_handler( wnd, cb, [&](ICoreWebView2Controller* controller) { m_controller = controller; m_controller->get_CoreWebView2(&m_webview); m_webview->AddRef(); flag.clear(); }, [this](std::string_view url) -> bool { const auto scheme = url.substr(0, url.find_first_of(':')); if (scheme == m_jsScheme) { if (_onJsCallback) { _onJsCallback(url); } return true; } if (_shouldStartLoading && !url.empty()) { return _shouldStartLoading(url); } return true; }, [this]() { LPWSTR uri; this->m_webview->get_Source(&uri); std::wstring ws(uri); const auto result = std::string(ws.begin(), ws.end()); if (_didFinishLoading) _didFinishLoading(result); }, [this]() { LPWSTR uri; this->m_webview->get_Source(&uri); std::wstring ws(uri); const auto result = std::string(ws.begin(), ws.end()); if (_didFailLoading) _didFailLoading(result); }, [this](std::string_view url) { loadURL(url, false); })); if (res != S_OK) { CoUninitialize(); return false; } # if AX_TARGET_PLATFORM != AX_PLATFORM_WINRT MSG msg = {}; while (flag.test_and_set() && GetMessage(&msg, NULL, 0, 0)) { TranslateMessage(&msg); DispatchMessage(&msg); } #endif init("window.external={invoke:s=>window.chrome.webview.postMessage(s)}"); return true; } void resize(HWND wnd) { if (m_controller == nullptr) { return; } # if AX_TARGET_PLATFORM != AX_PLATFORM_WINRT RECT bounds; GetClientRect(wnd, &bounds); m_controller->put_Bounds(bounds); #endif } void navigate(std::string_view url) { auto wurl = to_lpwstr(url); m_webview->Navigate(wurl); delete[] wurl; } void init(std::string_view js) { LPCWSTR wjs = to_lpwstr(js); m_webview->AddScriptToExecuteOnDocumentCreated(wjs, nullptr); delete[] wjs; } void eval(std::string_view js) { LPCWSTR wjs = to_lpwstr(js); m_webview->ExecuteScript(wjs, nullptr); delete[] wjs; } void on_message(std::string_view msg) { const auto seq = jsonParse(msg, "id", 0); const auto name = jsonParse(msg, "method", 0); const auto args = jsonParse(msg, "params", 0); if (bindings.find(name) == bindings.end()) { return; } const auto fn = bindings[name]; (*fn->first)(seq, args, fn->second); } using binding_t = std::function; using binding_ctx_t = std::pair; std::map bindings; class webview2_com_handler : public ICoreWebView2CreateCoreWebView2EnvironmentCompletedHandler, public ICoreWebView2CreateCoreWebView2ControllerCompletedHandler, public ICoreWebView2WebMessageReceivedEventHandler, public ICoreWebView2PermissionRequestedEventHandler, public ICoreWebView2NavigationStartingEventHandler, public ICoreWebView2NavigationCompletedEventHandler, public ICoreWebView2NewWindowRequestedEventHandler, public ICoreWebView2ContentLoadingEventHandler, public ICoreWebView2DOMContentLoadedEventHandler { using webview2_com_handler_cb_t = std::function; public: webview2_com_handler(HWND hwnd, msg_cb_t msgCb, webview2_com_handler_cb_t cb, std::function navStartingCallback, std::function navCompleteCallback, std::function navErrorCallback, std::function loadUrlCallback) : m_window(hwnd) , m_msgCb(std::move(msgCb)) , m_cb(std::move(cb)) , m_navStartingCallback(std::move(navStartingCallback)) , m_navCompleteCallback(std::move(navCompleteCallback)) , m_navErrorCallback(std::move(navErrorCallback)) , m_loadUrlCallback(std::move(loadUrlCallback)) {} ULONG STDMETHODCALLTYPE AddRef() { return 1; } ULONG STDMETHODCALLTYPE Release() { return 1; } HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, LPVOID* ppv) { return S_OK; } // ICoreWebView2CreateCoreWebView2EnvironmentCompletedHandler HRESULT STDMETHODCALLTYPE Invoke(HRESULT res, ICoreWebView2Environment* env) { env->CreateCoreWebView2Controller(m_window, this); return S_OK; } // ICoreWebView2CreateCoreWebView2ControllerCompletedHandler HRESULT STDMETHODCALLTYPE Invoke(HRESULT res, ICoreWebView2Controller* controller) { controller->AddRef(); ICoreWebView2* webview; ::EventRegistrationToken token; controller->get_CoreWebView2(&webview); webview->add_WebMessageReceived(this, &token); webview->add_PermissionRequested(this, &token); webview->add_NavigationStarting(this, &token); webview->add_NavigationCompleted(this, &token); webview->add_ContentLoading(this, &token); webview->add_NewWindowRequested(this, &token); m_cb(controller); return S_OK; } // ICoreWebView2WebMessageReceivedEventHandler HRESULT STDMETHODCALLTYPE Invoke(ICoreWebView2* sender, ICoreWebView2WebMessageReceivedEventArgs* args) { LPWSTR message; args->TryGetWebMessageAsString(&message); std::wstring_convert> wideCharConverter; m_msgCb(wideCharConverter.to_bytes(message)); sender->PostWebMessageAsString(message); CoTaskMemFree(message); return S_OK; } // ICoreWebView2PermissionRequestedEventHandler HRESULT STDMETHODCALLTYPE Invoke(ICoreWebView2* sender, ICoreWebView2PermissionRequestedEventArgs* args) { COREWEBVIEW2_PERMISSION_KIND kind; args->get_PermissionKind(&kind); if (kind == COREWEBVIEW2_PERMISSION_KIND_CLIPBOARD_READ) { args->put_State(COREWEBVIEW2_PERMISSION_STATE_ALLOW); } return S_OK; } // ICoreWebView2NavigationStartingEventHandler HRESULT STDMETHODCALLTYPE Invoke(ICoreWebView2* sender, ICoreWebView2NavigationStartingEventArgs* args) { if (m_navStartingCallback) { const auto uriString = getUriStringFromArgs(args); if (!m_navStartingCallback(uriString)) { args->put_Cancel(true); } } return S_OK; } // ICoreWebView2NavigationCompletedEventHandler HRESULT STDMETHODCALLTYPE Invoke(ICoreWebView2* sender, ICoreWebView2NavigationCompletedEventArgs* args) { BOOL success; if (SUCCEEDED(args->get_IsSuccess(&success)) && success) { if (m_navCompleteCallback) { m_navCompleteCallback(); } } else { // example of how to get status error if required // COREWEBVIEW2_WEB_ERROR_STATUS status; // if (SUCCEEDED(args->get_WebErrorStatus(&status))) //{ //} if (m_navErrorCallback) { m_navErrorCallback(); } } return S_OK; } // ICoreWebView2ContentLoadingEventHandler HRESULT STDMETHODCALLTYPE Invoke(ICoreWebView2* sender, ICoreWebView2ContentLoadingEventArgs* args) { return S_OK; } // ICoreWebView2NewWindowRequestedEventHandler HRESULT STDMETHODCALLTYPE Invoke(ICoreWebView2* sender, ICoreWebView2NewWindowRequestedEventArgs* args) { args->put_Handled(true); return S_OK; } // ICoreWebView2NewWindowRequestedEventHandler HRESULT STDMETHODCALLTYPE Invoke(ICoreWebView2* sender, ICoreWebView2NewWindowRequestedEventArgs2* args) { args->put_Handled(true); if (m_loadUrlCallback) { const auto uriString = getUriStringFromArgs(args); m_loadUrlCallback(uriString); } args->put_Handled(true); return S_OK; } // ICoreWebView2DOMContentLoadedEventHandler HRESULT STDMETHODCALLTYPE Invoke(ICoreWebView2* sender, ICoreWebView2DOMContentLoadedEventArgs* args) { return S_OK; } private: HWND m_window; msg_cb_t m_msgCb; webview2_com_handler_cb_t m_cb; std::function m_navStartingCallback; std::function m_navCompleteCallback; std::function m_navErrorCallback; std::function m_loadUrlCallback; }; }; NS_AX_BEGIN namespace ui { WebViewImpl::WebViewImpl(WebView* webView) : _createSucceeded(false), _systemWebControl(nullptr), _webView(webView) { _systemWebControl = new Win32WebControl(); if (_systemWebControl == nullptr) { return; } _createSucceeded = _systemWebControl->createWebView( [this](std::string_view url) -> bool { const auto shouldStartLoading = _webView->getOnShouldStartLoading(); if (shouldStartLoading != nullptr) { return shouldStartLoading(_webView, url); } return true; }, [this](std::string_view url) { WebView::ccWebViewCallback didFinishLoading = _webView->getOnDidFinishLoading(); if (didFinishLoading != nullptr) { didFinishLoading(_webView, url); } }, [this](std::string_view url) { WebView::ccWebViewCallback didFailLoading = _webView->getOnDidFailLoading(); if (didFailLoading != nullptr) { didFailLoading(_webView, url); } }, [this](std::string_view url) { WebView::ccWebViewCallback onJsCallback = _webView->getOnJSCallback(); if (onJsCallback != nullptr) { onJsCallback(_webView, url); } }); } WebViewImpl::~WebViewImpl() { if (_systemWebControl != nullptr) { _systemWebControl->removeWebView(); delete _systemWebControl; _systemWebControl = nullptr; } } void WebViewImpl::loadData(const Data& data, std::string_view MIMEType, std::string_view encoding, std::string_view baseURL) { if (_createSucceeded) { const std::string dataString(reinterpret_cast(data.getBytes()), static_cast(data.getSize())); const auto url = getDataURI(dataString, MIMEType); _systemWebControl->loadURL(url, false); } } void WebViewImpl::loadHTMLString(std::string_view string, std::string_view baseURL) { if (_createSucceeded) { if (string.empty()) { _systemWebControl->loadHTMLString("data:text/html," + utils::urlEncode(""), baseURL); return; } const auto html = htmlFromUri(string); if (!html.empty()) { _systemWebControl->loadHTMLString("data:text/html," + utils::urlEncode(html), baseURL); } else { _systemWebControl->loadHTMLString(string, baseURL); } } } void WebViewImpl::loadURL(std::string_view url, bool cleanCachedData) { if (_createSucceeded) { _systemWebControl->loadURL(url, cleanCachedData); } } void WebViewImpl::loadFile(std::string_view fileName) { if (_createSucceeded) { const auto fullPath = FileUtils::getInstance()->fullPathForFilename(fileName); _systemWebControl->loadFile(fullPath); } } void WebViewImpl::stopLoading() { if (_createSucceeded) { _systemWebControl->stopLoading(); } } void WebViewImpl::reload() { if (_createSucceeded) { _systemWebControl->reload(); } } bool WebViewImpl::canGoBack() { if (_createSucceeded) { return _systemWebControl->canGoBack(); } return false; } bool WebViewImpl::canGoForward() { if (_createSucceeded) { return _systemWebControl->canGoForward(); } return false; } void WebViewImpl::goBack() { if (_createSucceeded) { _systemWebControl->goBack(); } } void WebViewImpl::goForward() { if (_createSucceeded) { _systemWebControl->goForward(); } } void WebViewImpl::setJavascriptInterfaceScheme(std::string_view scheme) { if (_createSucceeded) { _systemWebControl->setJavascriptInterfaceScheme(scheme); } } void WebViewImpl::evaluateJS(std::string_view js) { if (_createSucceeded) { _systemWebControl->evaluateJS(js); } } void WebViewImpl::setScalesPageToFit(const bool scalesPageToFit) { if (_createSucceeded) { _systemWebControl->setScalesPageToFit(scalesPageToFit); } } void WebViewImpl::draw(Renderer* renderer, Mat4 const& transform, uint32_t flags) { if (_createSucceeded && (flags & Node::FLAGS_TRANSFORM_DIRTY)) { const auto uiRect = ax::ui::Helper::convertBoundingBoxToScreen(_webView); _systemWebControl->setWebViewRect(static_cast(uiRect.origin.x), static_cast(uiRect.origin.y), static_cast(uiRect.size.width), static_cast(uiRect.size.height)); } } void WebViewImpl::setVisible(bool visible) { if (_createSucceeded) { _systemWebControl->setWebViewVisible(visible); } } void WebViewImpl::setBounces(bool bounces) { _systemWebControl->setBounces(bounces); } void WebViewImpl::setOpacityWebView(float opacity) { _systemWebControl->setOpacityWebView(opacity); } float WebViewImpl::getOpacityWebView() const { return _systemWebControl->getOpacityWebView(); } void WebViewImpl::setBackgroundTransparent() { _systemWebControl->setBackgroundTransparent(); } } // namespace ui NS_AX_END // namespace ax // // Implement Win32WebControl // bool Win32WebControl::s_isInitialized = false; void Win32WebControl::lazyInit() { # if AX_TARGET_PLATFORM != AX_PLATFORM_WINRT // reset the main windows style so that its drawing does not cover the webview sub window auto hwnd = ax::Director::getInstance()->getOpenGLView()->getWin32Window(); const auto style = GetWindowLong(hwnd, GWL_STYLE); SetWindowLong(hwnd, GWL_STYLE, style | WS_CLIPCHILDREN); CoInitialize(NULL); #endif } Win32WebControl::Win32WebControl() : _shouldStartLoading(nullptr), _didFinishLoading(nullptr), _didFailLoading(nullptr) { if (!s_isInitialized) { lazyInit(); } } bool Win32WebControl::createWebView(const std::function& shouldStartLoading, const std::function& didFinishLoading, const std::function& didFailLoading, const std::function& onJsCallback) { bool ret = false; # if AX_TARGET_PLATFORM != AX_PLATFORM_WINRT do { HWND hwnd = ax::Director::getInstance()->getOpenGLView()->getWin32Window(); HINSTANCE hInstance = GetModuleHandle(nullptr); WNDCLASSEX wc; ZeroMemory(&wc, sizeof(WNDCLASSEX)); wc.cbSize = sizeof(WNDCLASSEX); wc.hInstance = hInstance; wc.lpszClassName = L"webview"; wc.hIcon = nullptr; wc.hIconSm = nullptr; // wc.style = CS_HREDRAW | CS_VREDRAW | CS_DBLCLKS; wc.lpfnWndProc = (WNDPROC)(+[](HWND hwnd, UINT msg, WPARAM wp, LPARAM lp) -> int { auto w = (Win32WebControl*)GetWindowLongPtr(hwnd, GWLP_USERDATA); if (!w) { return DefWindowProc(hwnd, msg, wp, lp); } switch (msg) { case WM_SIZE: w->resize(hwnd); break; case WM_CLOSE: DestroyWindow(hwnd); break; case WM_GETMINMAXINFO: { auto lpmmi = (LPMINMAXINFO)lp; if (w == nullptr) { return 0; } if (w->m_maxsz.x > 0 && w->m_maxsz.y > 0) { lpmmi->ptMaxSize = w->m_maxsz; lpmmi->ptMaxTrackSize = w->m_maxsz; } if (w->m_minsz.x > 0 && w->m_minsz.y > 0) { lpmmi->ptMinTrackSize = w->m_minsz; } } break; default: return DefWindowProc(hwnd, msg, wp, lp); } return 0; }); RegisterClassEx(&wc); m_window = CreateWindow(L"webview", L"", WS_VISIBLE | WS_CHILD, 0, 0, 0, 0, hwnd, nullptr, hInstance, nullptr); SetWindowLongPtr(m_window, GWLP_USERDATA, (LONG_PTR)this); ShowWindow(m_window, SW_SHOW); UpdateWindow(m_window); SetFocus(m_window); auto cb = [this](std::string_view msg) { on_message(msg); }; if (!embed(m_window, false, cb)) { AXLOG("Cannot create edge chromium webview"); ret = false; break; } resize(m_window); ret = true; } while (0); if (!ret) { removeWebView(); } _shouldStartLoading = shouldStartLoading; _didFinishLoading = didFinishLoading; _didFailLoading = didFailLoading; _onJsCallback = onJsCallback; #endif return ret; } void Win32WebControl::removeWebView() { m_controller->Close(); m_controller = nullptr; m_webview = nullptr; } void Win32WebControl::setWebViewRect(const int left, const int top, const int width, const int height) { # if AX_TARGET_PLATFORM != AX_PLATFORM_WINRT if (m_controller == nullptr) { return; } RECT r; r.left = left; r.top = top; r.right = left + width; r.bottom = top - height; AdjustWindowRect(&r, WS_CHILD | WS_VISIBLE, 0); SetWindowPos(m_window, nullptr, left, top, width, height, SWP_NOZORDER); m_controller->put_ZoomFactor(_scalesPageToFit ? getDeviceScaleFactor() : 1.0); #endif } void Win32WebControl::setJavascriptInterfaceScheme(std::string_view scheme) { m_jsScheme = scheme; } void Win32WebControl::loadHTMLString(std::string_view html, std::string_view baseURL) { if (!html.empty()) { const auto whtml = to_lpwstr(html); m_webview->NavigateToString(whtml); delete[] whtml; } } void Win32WebControl::loadURL(std::string_view url, bool cleanCachedData) { if (cleanCachedData) { m_webview->CallDevToolsProtocolMethod(L"Network.clearBrowserCache", L"{}", nullptr); } navigate(url); } void Win32WebControl::loadFile(std::string_view filePath) { auto fullPath = ax::FileUtils::getInstance()->fullPathForFilename(filePath); if (fullPath.find("file:///") != 0) { if (fullPath[0] == '/') { fullPath = "file://" + fullPath; } else { fullPath = "file:///" + fullPath; } } loadURL(fullPath, false); } void Win32WebControl::stopLoading() { m_webview->Stop(); } void Win32WebControl::reload() const { m_webview->Reload(); } bool Win32WebControl::canGoBack() const { BOOL result; return SUCCEEDED(m_webview->get_CanGoBack(&result)) && result; } bool Win32WebControl::canGoForward() const { BOOL result; return SUCCEEDED(m_webview->get_CanGoForward(&result)) && result; } void Win32WebControl::goBack() const { m_webview->GoBack(); } void Win32WebControl::goForward() const { m_webview->GoForward(); } void Win32WebControl::evaluateJS(std::string_view js) { eval(js); } void Win32WebControl::setScalesPageToFit(const bool scalesPageToFit) { _scalesPageToFit = scalesPageToFit; m_controller->put_ZoomFactor(_scalesPageToFit ? getDeviceScaleFactor() : 1.0); } void Win32WebControl::setWebViewVisible(const bool visible) const { # if AX_TARGET_PLATFORM != AX_PLATFORM_WINRT ShowWindow(m_window, visible ? SW_SHOW : SW_HIDE); if (!visible) { // When the frame window is minimized set the browser window size to 0x0 to // reduce resource usage. SetWindowPos(m_window, nullptr, 0, 0, 0, 0, SWP_NOZORDER | SWP_NOMOVE | SWP_NOACTIVATE); } #endif } void Win32WebControl::setBounces(bool bounces) {} void Win32WebControl::setOpacityWebView(float opacity) {} float Win32WebControl::getOpacityWebView() const { return 1.0f; } void Win32WebControl::setBackgroundTransparent() { ICoreWebView2Controller2* viewController2; if (SUCCEEDED(m_controller->QueryInterface(__uuidof(ICoreWebView2Controller2), reinterpret_cast(&viewController2)))) { COREWEBVIEW2_COLOR color; viewController2->get_DefaultBackgroundColor(&color); color.A = 0; // Only supports 0 or 255. Other values not supported viewController2->put_DefaultBackgroundColor(color); } } #endif // AX_TARGET_PLATFORM == AX_PLATFORM_WIN32