// // Created by yangbin on 2021/11/12. // #include #include #include #include #include #include #include "web_view.h" #include "utils.h" #include "strconv.h" namespace webview_window { static LRESULT CALLBACK WndProc(HWND const window, UINT const message, WPARAM const wparam, LPARAM const lparam) noexcept { return DefWindowProc(window, message, wparam, lparam); } const auto kWebViewClassName = _T("web_view_window_web_view"); using namespace Microsoft::WRL; WebView::WebView( std::shared_ptr> method_channel, int64_t web_view_id, std::wstring userDataFolder, std::function on_web_view_created ) : method_channel_(std::move(method_channel)), web_view_id_(web_view_id), user_data_folder_(std::move(userDataFolder)), on_web_view_created_callback_(std::move(on_web_view_created)) { RegisterWindowClass(kWebViewClassName, WndProc); view_window_ = wil::unique_hwnd(::CreateWindowEx( 0, kWebViewClassName, L"", WS_CHILD | WS_VISIBLE, 0, 0, 0, 0, HWND_MESSAGE, nullptr, ::GetModuleHandle(nullptr), nullptr)); assert(view_window_ != nullptr); if (!view_window_) { on_web_view_created_callback_(S_FALSE); return; } CreateCoreWebView2EnvironmentWithOptions( nullptr, user_data_folder_.c_str(), nullptr, Callback( [this](HRESULT result, ICoreWebView2Environment *env) -> HRESULT { if (!SUCCEEDED(result)) { on_web_view_created_callback_(result); return S_OK; } env->CreateCoreWebView2Controller( view_window_.get(), Callback( [this](HRESULT result, ICoreWebView2Controller *controller) -> HRESULT { on_web_view_created_callback_(result); if (SUCCEEDED(result)) { webview_controller_ = controller; OnWebviewControllerCreated(); } return S_OK; }).Get()); return S_OK; }).Get()); } void WebView::OnWebviewControllerCreated() { if (!webview_controller_) { return; } webview_controller_->get_CoreWebView2(&webview_); if (!webview_) { std::cerr << "failed to get core webview" << std::endl; return; } ICoreWebView2Settings *settings; webview_->get_Settings(&settings); settings->put_IsScriptEnabled(true); settings->put_IsZoomControlEnabled(false); settings->put_AreDefaultContextMenusEnabled(false); settings->put_IsStatusBarEnabled(false); settings->put_IsWebMessageEnabled(true); ICoreWebView2Settings2 *settings2; auto hr = settings->QueryInterface(IID_PPV_ARGS(&settings2)); if (SUCCEEDED(hr)) { LPWSTR user_agent[256]; settings2->get_UserAgent(user_agent); default_user_agent_ = std::wstring(*user_agent); } UpdateBounds(); // Always use single window to load web page. webview_->add_NewWindowRequested( Callback( [](ICoreWebView2 *sender, ICoreWebView2NewWindowRequestedEventArgs *args) { LPWSTR url; args->get_Uri(&url); sender->Navigate(url); args->put_Handled(true); return S_OK; }).Get(), nullptr); webview_->add_ContentLoading( Callback( [](ICoreWebView2 *sender, ICoreWebView2ContentLoadingEventArgs *args) { return S_OK; }).Get(), nullptr); webview_->add_HistoryChanged( Callback( [this](ICoreWebView2 *sender, IUnknown *args) { auto method_args = flutter::EncodableMap{ {flutter::EncodableValue("id"), flutter::EncodableValue(web_view_id_)}, {flutter::EncodableValue("canGoBack"), flutter::EncodableValue(CanGoBack())}, {flutter::EncodableValue("canGoForward"), flutter::EncodableValue(CanGoForward())}, }; method_channel_->InvokeMethod("onHistoryChanged", std::make_unique(method_args)); return S_OK; } ).Get(), nullptr); webview_->add_NavigationStarting( Callback( [this](ICoreWebView2 *sender, ICoreWebView2NavigationStartingEventArgs *args) { method_channel_->InvokeMethod( "onNavigationStarted", std::make_unique(flutter::EncodableMap{ {flutter::EncodableValue("id"), flutter::EncodableValue(web_view_id_)}, })); LPWSTR uri; args->get_Uri(&uri); method_channel_->InvokeMethod( "onUrlRequested", std::make_unique(flutter::EncodableMap{ {flutter::EncodableValue("id"), flutter::EncodableValue(web_view_id_)}, {flutter::EncodableValue("url"), flutter::EncodableValue(wide_to_utf8(std::wstring(uri)))}, })); return S_OK; } ).Get(), nullptr); webview_->add_NavigationCompleted( Callback( [this](ICoreWebView2 *sender, ICoreWebView2NavigationCompletedEventArgs *args) { auto method_args = flutter::EncodableMap{ {flutter::EncodableValue("id"), flutter::EncodableValue(web_view_id_)}, }; method_channel_->InvokeMethod("onNavigationCompleted", std::make_unique(method_args)); return S_OK; } ).Get(), nullptr); webview_->add_WebMessageReceived( Callback( [this](ICoreWebView2 *sender, ICoreWebView2WebMessageReceivedEventArgs *args) { wil::unique_cotaskmem_string messageRaw; HRESULT hrString = args->TryGetWebMessageAsString(&messageRaw); if (FAILED(hrString)) { if (hrString == E_INVALIDARG) { // web message was not a string --> should only happen if it was a JSON object HRESULT hrJson = args->get_WebMessageAsJson(&messageRaw); if (FAILED(hrJson)) { return hrJson; } } else { return hrString; } } method_channel_->InvokeMethod( "onWebMessageReceived", std::make_unique(flutter::EncodableMap{ {flutter::EncodableValue("id"), flutter::EncodableValue(web_view_id_)}, {flutter::EncodableValue("message"), flutter::EncodableValue(wide_to_utf8(std::wstring(messageRaw.get())))}, })); return S_OK; } ).Get(), nullptr); } void WebView::UpdateBounds() { // Resize WebView to fit the bounds of the parent window RECT bounds; GetClientRect(view_window_.get(), &bounds); webview_controller_->put_Bounds(bounds); } void WebView::Navigate(const std::wstring &url) { if (webview_) { webview_->Navigate(url.c_str()); } else { std::cerr << "webview not created" << std::endl; } } void WebView::AddScriptToExecuteOnDocumentCreated(const std::wstring &javaScript) { if (webview_) { webview_->AddScriptToExecuteOnDocumentCreated(javaScript.c_str(), nullptr); } } void WebView::SetApplicationNameForUserAgent(const std::wstring &name) { if (webview_) { ICoreWebView2Settings *settings; webview_->get_Settings(&settings); ICoreWebView2Settings2 *settings2; auto hr = settings->QueryInterface(IID_PPV_ARGS(&settings2)); if (SUCCEEDED(hr)) { settings2->put_UserAgent((default_user_agent_ + name).c_str()); } } } void WebView::GoBack() { if (webview_) { webview_->GoBack(); } } void WebView::GoForward() { if (webview_) { webview_->GoForward(); } } void WebView::Reload() { if (webview_) { webview_->Reload(); } } void WebView::Stop() { if (webview_) { webview_->Stop(); } } void WebView::openDevToolsWindow() { if (webview_) { webview_->OpenDevToolsWindow(); } } bool WebView::CanGoBack() const { if (webview_) { BOOL can_go_back; webview_->get_CanGoBack(&can_go_back); return can_go_back; } return false; } bool WebView::CanGoForward() const { if (webview_) { BOOL can_go_forward; webview_->get_CanGoForward(&can_go_forward); return can_go_forward; } return false; } void WebView::ExecuteJavaScript(const std::wstring &javaScript, std::unique_ptr> completer) { if (webview_) { webview_->ExecuteScript( javaScript.c_str(), Callback( [completer(std::move(completer))](HRESULT error, PCWSTR result) -> HRESULT { if (error != S_OK) { completer->Error("0", "Error executing JavaScript"); } else { completer->Success(flutter::EncodableValue(wide_to_utf8(std::wstring(result)))); } return S_OK; }).Get()); } else { completer->Error("0", "webview not created"); } } void WebView::PostWebMessageAsString(const std::wstring &webmessage, std::unique_ptr> completer) { if (webview_) { if (webview_->PostWebMessageAsString( webmessage.c_str()) == NOERROR) { completer->Success(); } else { completer->Error("0", "Error posting webmessage as String"); } } else { completer->Error("0", "webview not created"); } } void WebView::PostWebMessageAsJson(const std::wstring& webmessage, std::unique_ptr> completer) { if (webview_) { if (webview_->PostWebMessageAsJson( webmessage.c_str()) == NOERROR) { completer->Success(); } else { completer->Error("0", "Error posting webmessage as JSON"); } } else { completer->Error("0", "webview not created"); } } WebView::~WebView() { if (webview_) { webview_->Stop(); } if (webview_controller_) { webview_controller_->Close(); } } }