From d95c4da710dcc87eb5280378f3125b7f83094ad4 Mon Sep 17 00:00:00 2001 From: Andrii Kurdiumov Date: Fri, 22 Nov 2019 00:34:57 +0200 Subject: [PATCH 1/3] Add draft implementation of the WebView using EdgeHtml control - During initialization failures aplication eixting - No custom scheme support - No loading files from file:// scheme - No communication with host support - Work only in STA (seems to be MSEdge limitation) I would like to implement more modern c++/winrt, but that requires version C++ 17. And that seems to be another request. --- src/WebWindow.Native/IAsyncOperationHelper.h | 107 ++++++++++++ src/WebWindow.Native/WebWindow.Native.vcxproj | 3 +- .../WebWindow.Native.vcxproj.filters | 3 + src/WebWindow.Native/WebWindow.Windows.cpp | 157 +++++++++++++++++- src/WebWindow.Native/WebWindow.h | 13 +- testassets/HelloWorldApp/Program.cs | 2 + 6 files changed, 279 insertions(+), 6 deletions(-) create mode 100644 src/WebWindow.Native/IAsyncOperationHelper.h diff --git a/src/WebWindow.Native/IAsyncOperationHelper.h b/src/WebWindow.Native/IAsyncOperationHelper.h new file mode 100644 index 0000000..c8f8d0a --- /dev/null +++ b/src/WebWindow.Native/IAsyncOperationHelper.h @@ -0,0 +1,107 @@ +#pragma once + +#include + +namespace AsyncOpHelpers +{ + template + HRESULT WaitForCompletionAndGetResults( + ABI::Windows::Foundation::IAsyncOperation* operationIn, + TIResults* results) + { + typedef ABI::Windows::Foundation::IAsyncOperation TIOperation; + typedef ABI::Windows::Foundation::IAsyncOperationCompletedHandler TIDelegate; + + class EventDelegate : + public Microsoft::WRL::RuntimeClass, + TIDelegate, + Microsoft::WRL::FtmBase> + { + public: + EventDelegate() + : _status(AsyncStatus::Started), _hEventCompleted(nullptr) + { + } + + HRESULT RuntimeClassInitialize() + { + _hEventCompleted = CreateEventEx(nullptr, nullptr, 0, EVENT_ALL_ACCESS); + + return _hEventCompleted == nullptr ? HRESULT_FROM_WIN32(GetLastError()) : S_OK; + } + + + HRESULT __stdcall Invoke( + _In_ TIOperation*, + _In_ AsyncStatus status) + { + _status = status; + SetEvent(_hEventCompleted); + return S_OK; + } + + HANDLE GetEvent() + { + return _hEventCompleted; + } + + AsyncStatus GetStatus() + { + return _status; + } + + private: + ~EventDelegate() + { + CloseHandle(_hEventCompleted); + } + + AsyncStatus _status; + HANDLE _hEventCompleted; + }; + + + Microsoft::WRL::ComPtr operation = operationIn; + Microsoft::WRL::ComPtr eventCallback; + + HRESULT hr = Microsoft::WRL::MakeAndInitialize(&eventCallback); + + + if (SUCCEEDED(hr)) + { + hr = operation->put_Completed(eventCallback.Get()); + + if (SUCCEEDED(hr)) + { + HANDLE waitForEvents[1] = {eventCallback->GetEvent()}; + DWORD handleCount = ARRAYSIZE(waitForEvents); + DWORD handleIndex = 0; + HRESULT hr = CoWaitForMultipleHandles(0, INFINITE, handleCount, waitForEvents, &handleIndex); + + if (SUCCEEDED(hr) && handleIndex != 0) + { + hr = HRESULT_FROM_WIN32(ERROR_CANCELLED); + } + + if (SUCCEEDED(hr)) + { + if (eventCallback->GetStatus() == AsyncStatus::Completed) + { + hr = operation->GetResults(results); + } + else + { + Microsoft::WRL::ComPtr asyncInfo; + + if (SUCCEEDED(operation->QueryInterface(IID_PPV_ARGS(&asyncInfo)))) + { + asyncInfo->get_ErrorCode(&hr); + } + } + } + } + } + + return hr; + } +} diff --git a/src/WebWindow.Native/WebWindow.Native.vcxproj b/src/WebWindow.Native/WebWindow.Native.vcxproj index e8d4df2..884aa04 100644 --- a/src/WebWindow.Native/WebWindow.Native.vcxproj +++ b/src/WebWindow.Native/WebWindow.Native.vcxproj @@ -108,7 +108,7 @@ true Windows - kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;shlwapi.lib;%(AdditionalDependencies) + kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;shlwapi.lib;runtimeobject.lib;%(AdditionalDependencies) @@ -152,6 +152,7 @@ + diff --git a/src/WebWindow.Native/WebWindow.Native.vcxproj.filters b/src/WebWindow.Native/WebWindow.Native.vcxproj.filters index eaf12b1..9c41203 100644 --- a/src/WebWindow.Native/WebWindow.Native.vcxproj.filters +++ b/src/WebWindow.Native/WebWindow.Native.vcxproj.filters @@ -29,6 +29,9 @@ Header Files + + Header Files + diff --git a/src/WebWindow.Native/WebWindow.Windows.cpp b/src/WebWindow.Native/WebWindow.Windows.cpp index 5a5393f..729b4ea 100644 --- a/src/WebWindow.Native/WebWindow.Windows.cpp +++ b/src/WebWindow.Native/WebWindow.Windows.cpp @@ -1,4 +1,13 @@ #include "WebWindow.h" +#define WIN32_LEAN_AND_MEAN // Exclude rarely-used stuff from Windows headers +#include + +#include +#include +#include +#include +#include +#include #include #include #include @@ -6,11 +15,18 @@ #include #include #include +#include "IAsyncOperationHelper.h" +#include #define WM_USER_SHOWMESSAGE (WM_USER + 0x0001) #define WM_USER_INVOKE (WM_USER + 0x0002) +using namespace ABI::Windows::Foundation; +using namespace ABI::Windows::Storage::Streams; +using namespace ABI::Windows::Web::UI; +using namespace ABI::Windows::Web::UI::Interop; using namespace Microsoft::WRL; +using namespace Microsoft::WRL::Wrappers; LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam); LPCWSTR CLASS_NAME = L"WebWindow"; @@ -32,6 +48,70 @@ struct ShowMessageParams UINT type; }; +void CheckFailure(_In_ HRESULT hr) +{ + if (FAILED(hr)) + { + WCHAR message[512] = L""; + StringCchPrintf(message, ARRAYSIZE(message), L"Error: 0x%x", hr); + MessageBoxW(nullptr, message, nullptr, MB_OK); + ExitProcess(-1); + } +} + +template +Microsoft::WRL::ComPtr GetActivationFactoryFailFast(_In_z_ PCWSTR factoryClassName) +{ + ComPtr factoryInstance; + CheckFailure(RoGetActivationFactory( + HStringReference(factoryClassName).Get(), + IID_PPV_ARGS(&factoryInstance))); + return factoryInstance; +} + +template +Microsoft::WRL::ComPtr ActivateInstanceFailFast(_In_z_ PCWSTR className) +{ + ComPtr classInstanceAsInspectable; + ComPtr classInstance; + CheckFailure(RoActivateInstance( + HStringReference(className).Get(), + &classInstanceAsInspectable)); + CheckFailure(classInstanceAsInspectable.As(&classInstance)); + return classInstance; +} + +ComPtr CreateWinRtUri(_In_z_ PCWSTR uri, _In_ bool allowInvalidUri = false) +{ + auto uriRuntimeClassFactory = GetActivationFactoryFailFast(RuntimeClass_Windows_Foundation_Uri); + ComPtr uriRuntimeClass; + if (!allowInvalidUri) + { + CheckFailure(uriRuntimeClassFactory->CreateUri(HStringReference(uri).Get(), &uriRuntimeClass)); + } + else + { + uriRuntimeClassFactory->CreateUri(HStringReference(uri).Get(), &uriRuntimeClass); + } + return uriRuntimeClass; +} + +Rect HwndWindowRectToBoundsRect(_In_ HWND hwnd) +{ + RECT windowRect = { 0 }; + GetWindowRect(hwnd, &windowRect); + + Rect bounds = + { + 0, + 0, + static_cast(windowRect.right - windowRect.left), + static_cast(windowRect.bottom - windowRect.top) + }; + + return bounds; +} + void WebWindow::Register(HINSTANCE hInstance) { _hInstance = hInstance; @@ -120,6 +200,9 @@ LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) int width, height; webWindow->GetSize(&width, &height); webWindow->InvokeResized(width, height); + } + else { + } return 0; } @@ -148,6 +231,22 @@ void WebWindow::RefitContent() GetClientRect(_hWnd, &bounds); _webviewWindow->put_Bounds(bounds); } + else { + if (m_webViewControl) + { + RECT hwndBounds = { 0 }; + GetClientRect(_hWnd, &hwndBounds); + const int clientWidth = hwndBounds.right - hwndBounds.left; + const int clientHeight = hwndBounds.bottom - hwndBounds.top; + + SetWindowPos(_hWnd, nullptr, 0, 0, clientWidth, clientHeight - 0, SWP_NOZORDER); + + Rect bounds = HwndWindowRectToBoundsRect(_hWnd); + ComPtr site; + CheckFailure(m_webViewControl.As(&site)); + CheckFailure(site->put_Bounds(bounds)); + } + } } void WebWindow::SetTitle(AutoString title) @@ -291,9 +390,46 @@ void WebWindow::AttachWebView() if (envResult != S_OK) { - _com_error err(envResult); - LPCTSTR errMsg = err.ErrorMessage(); - MessageBox(_hWnd, errMsg, L"Error instantiating webview", MB_OK); + // winrt::init_apartment(winrt::apartment_type::single_threaded); + envResult = RoInitialize(RO_INIT_SINGLETHREADED); + + // Use default options if options weren't set on the App during App::RunNewInstance + if (!m_processOptions) + { + m_processOptions = ActivateInstanceFailFast(RuntimeClass_Windows_Web_UI_Interop_WebViewControlProcessOptions); + } + + // Use a default new process if one wasn't set on the App during App::RunNewInstance + if (!m_process) + { + ComPtr webViewControlProcessFactory = GetActivationFactoryFailFast(RuntimeClass_Windows_Web_UI_Interop_WebViewControlProcess); + CheckFailure(webViewControlProcessFactory->CreateWithOptions(m_processOptions.Get(), &m_process)); + } + + ComPtr> createWebViewAsyncOperation; + CheckFailure(m_process->CreateWebViewControlAsync( + reinterpret_cast(_hWnd), + HwndWindowRectToBoundsRect(_hWnd), + &createWebViewAsyncOperation)); + + CheckFailure(AsyncOpHelpers::WaitForCompletionAndGetResults(createWebViewAsyncOperation.Get(), m_webViewControl.ReleaseAndGetAddressOf())); + + EventRegistrationToken token = { 0 }; + HRESULT hr = m_webViewControl->add_ContentLoading(Callback>( + [this](IWebViewControl* webViewControl, IWebViewControlContentLoadingEventArgs* args) -> HRESULT + { + ComPtr uri; + CheckFailure(args->get_Uri(&uri)); + HString uriAsHString; + CheckFailure(uri->get_AbsoluteUri(uriAsHString.ReleaseAndGetAddressOf())); + //SetWindowText(m_addressbarWindow, uriAsHString.GetRawBuffer(nullptr)); + return S_OK; + }).Get(), &token); + CheckFailure(hr); + + //_com_error err(envResult); + //LPCTSTR errMsg = err.ErrorMessage(); + //MessageBox(_hWnd, errMsg, L"Error instantiating webview", MB_OK); } else { @@ -310,7 +446,20 @@ void WebWindow::AttachWebView() void WebWindow::NavigateToUrl(AutoString url) { - _webviewWindow->Navigate(url); + if (_webviewWindow) + { + _webviewWindow->Navigate(url); + } + else + { + ComPtr uri = CreateWinRtUri(url, true); + if (uri != nullptr) + { + //m_webViewControl->Navigate(uri.Get()); + CheckFailure(m_webViewControl->Navigate(uri.Get())); + } + } + delete[] urlW; } void WebWindow::NavigateToString(AutoString content) diff --git a/src/WebWindow.Native/WebWindow.h b/src/WebWindow.Native/WebWindow.h index ff9e5b5..ca5e992 100644 --- a/src/WebWindow.Native/WebWindow.h +++ b/src/WebWindow.Native/WebWindow.h @@ -7,7 +7,14 @@ #include #include #include -#include +#include +#include +#include +#include +#include "WebView2.h" +typedef void(__stdcall* ACTION)(); +typedef void(__stdcall* WebMessageReceivedCallback)(UTF8String message); +typedef void* (__stdcall *WebResourceRequestedCallback) (UTF8String url, int* outNumBytes, UTF8String *outContentType); typedef const wchar_t* AutoString; #else #ifdef OS_LINUX @@ -45,6 +52,10 @@ class WebWindow wil::com_ptr _webviewEnvironment; wil::com_ptr _webviewWindow; std::map _schemeToRequestHandler; + + Microsoft::WRL::ComPtr m_webViewControl; + Microsoft::WRL::ComPtr m_process; + Microsoft::WRL::ComPtr m_processOptions; void AttachWebView(); #elif OS_LINUX GtkWidget* _window; diff --git a/testassets/HelloWorldApp/Program.cs b/testassets/HelloWorldApp/Program.cs index c5fbf29..ddf52c0 100644 --- a/testassets/HelloWorldApp/Program.cs +++ b/testassets/HelloWorldApp/Program.cs @@ -7,6 +7,7 @@ namespace HelloWorldApp { class Program { + [STAThread] static void Main(string[] args) { var window = new WebWindow("My great app", options => @@ -24,6 +25,7 @@ static void Main(string[] args) }; window.NavigateToLocalFile("wwwroot/index.html"); + //window.NavigateToUrl("https://bing.com"); window.WaitForExit(); } } From dc9f027b461d4f15d549960b3e373c343eaa6a02 Mon Sep 17 00:00:00 2001 From: Andrii Kurdiumov Date: Fri, 22 Nov 2019 11:23:17 +0200 Subject: [PATCH 2/3] Add runtimeobject.lib to Release version --- src/WebWindow.Native/WebWindow.Native.vcxproj | 2 +- src/WebWindow.Native/WebWindow.Windows.cpp | 109 ++++++++++-------- src/WebWindow.Native/WebWindow.h | 5 +- 3 files changed, 66 insertions(+), 50 deletions(-) diff --git a/src/WebWindow.Native/WebWindow.Native.vcxproj b/src/WebWindow.Native/WebWindow.Native.vcxproj index 884aa04..3bbd019 100644 --- a/src/WebWindow.Native/WebWindow.Native.vcxproj +++ b/src/WebWindow.Native/WebWindow.Native.vcxproj @@ -143,7 +143,7 @@ true true Windows - kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;shlwapi.lib;%(AdditionalDependencies) + kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;shlwapi.lib;runtimeobject.lib;%(AdditionalDependencies) diff --git a/src/WebWindow.Native/WebWindow.Windows.cpp b/src/WebWindow.Native/WebWindow.Windows.cpp index 729b4ea..56587af 100644 --- a/src/WebWindow.Native/WebWindow.Windows.cpp +++ b/src/WebWindow.Native/WebWindow.Windows.cpp @@ -300,7 +300,7 @@ void WebWindow::Invoke(ACTION callback) waitInfo.completionNotifier.wait(uLock, [&] { return waitInfo.isCompleted; }); } -void WebWindow::AttachWebView() +bool WebWindow::AttachWebViewChromium() { std::atomic_flag flag = ATOMIC_FLAG_INIT; flag.test_and_set(); @@ -390,58 +390,76 @@ void WebWindow::AttachWebView() if (envResult != S_OK) { - // winrt::init_apartment(winrt::apartment_type::single_threaded); - envResult = RoInitialize(RO_INIT_SINGLETHREADED); + return false; + } - // Use default options if options weren't set on the App during App::RunNewInstance - if (!m_processOptions) - { - m_processOptions = ActivateInstanceFailFast(RuntimeClass_Windows_Web_UI_Interop_WebViewControlProcessOptions); - } + // Block until it's ready. This simplifies things for the caller, so they + // don't need to regard this process as async. + MSG msg = { }; + while (flag.test_and_set() && GetMessage(&msg, NULL, 0, 0)) + { + TranslateMessage(&msg); + DispatchMessage(&msg); + } - // Use a default new process if one wasn't set on the App during App::RunNewInstance - if (!m_process) - { - ComPtr webViewControlProcessFactory = GetActivationFactoryFailFast(RuntimeClass_Windows_Web_UI_Interop_WebViewControlProcess); - CheckFailure(webViewControlProcessFactory->CreateWithOptions(m_processOptions.Get(), &m_process)); - } + return true; +} - ComPtr> createWebViewAsyncOperation; - CheckFailure(m_process->CreateWebViewControlAsync( - reinterpret_cast(_hWnd), - HwndWindowRectToBoundsRect(_hWnd), - &createWebViewAsyncOperation)); - - CheckFailure(AsyncOpHelpers::WaitForCompletionAndGetResults(createWebViewAsyncOperation.Get(), m_webViewControl.ReleaseAndGetAddressOf())); - - EventRegistrationToken token = { 0 }; - HRESULT hr = m_webViewControl->add_ContentLoading(Callback>( - [this](IWebViewControl* webViewControl, IWebViewControlContentLoadingEventArgs* args) -> HRESULT - { - ComPtr uri; - CheckFailure(args->get_Uri(&uri)); - HString uriAsHString; - CheckFailure(uri->get_AbsoluteUri(uriAsHString.ReleaseAndGetAddressOf())); - //SetWindowText(m_addressbarWindow, uriAsHString.GetRawBuffer(nullptr)); - return S_OK; - }).Get(), &token); - CheckFailure(hr); +bool WebWindow::AttachWebViewEdge() +{ + // winrt::init_apartment(winrt::apartment_type::single_threaded); + HRESULT envResult = RoInitialize(RO_INIT_SINGLETHREADED); - //_com_error err(envResult); - //LPCTSTR errMsg = err.ErrorMessage(); - //MessageBox(_hWnd, errMsg, L"Error instantiating webview", MB_OK); + // Use default options if options weren't set on the App during App::RunNewInstance + if (!m_processOptions) + { + m_processOptions = ActivateInstanceFailFast(RuntimeClass_Windows_Web_UI_Interop_WebViewControlProcessOptions); } - else + + // Use a default new process if one wasn't set on the App during App::RunNewInstance + if (!m_process) { - // Block until it's ready. This simplifies things for the caller, so they - // don't need to regard this process as async. - MSG msg = { }; - while (flag.test_and_set() && GetMessage(&msg, NULL, 0, 0)) + ComPtr webViewControlProcessFactory = GetActivationFactoryFailFast(RuntimeClass_Windows_Web_UI_Interop_WebViewControlProcess); + CheckFailure(webViewControlProcessFactory->CreateWithOptions(m_processOptions.Get(), &m_process)); + } + + ComPtr> createWebViewAsyncOperation; + CheckFailure(m_process->CreateWebViewControlAsync( + reinterpret_cast(_hWnd), + HwndWindowRectToBoundsRect(_hWnd), + &createWebViewAsyncOperation)); + + CheckFailure(AsyncOpHelpers::WaitForCompletionAndGetResults(createWebViewAsyncOperation.Get(), m_webViewControl.ReleaseAndGetAddressOf())); + + EventRegistrationToken token = { 0 }; + HRESULT hr = m_webViewControl->add_ContentLoading(Callback>( + [this](IWebViewControl* webViewControl, IWebViewControlContentLoadingEventArgs* args) -> HRESULT { - TranslateMessage(&msg); - DispatchMessage(&msg); - } + ComPtr uri; + CheckFailure(args->get_Uri(&uri)); + HString uriAsHString; + CheckFailure(uri->get_AbsoluteUri(uriAsHString.ReleaseAndGetAddressOf())); + //SetWindowText(m_addressbarWindow, uriAsHString.GetRawBuffer(nullptr)); + return S_OK; + }).Get(), &token); + CheckFailure(hr); + + //_com_error err(envResult); + //LPCTSTR errMsg = err.ErrorMessage(); + //MessageBox(_hWnd, errMsg, L"Error instantiating webview", MB_OK); + + return envResult == S_OK; +} + +void WebWindow::AttachWebView() +{ + if (AttachWebViewChromium()) + { + return; } + + // Fallback to Edge rendering. + AttachWebViewEdge(); } void WebWindow::NavigateToUrl(AutoString url) @@ -459,7 +477,6 @@ void WebWindow::NavigateToUrl(AutoString url) CheckFailure(m_webViewControl->Navigate(uri.Get())); } } - delete[] urlW; } void WebWindow::NavigateToString(AutoString content) diff --git a/src/WebWindow.Native/WebWindow.h b/src/WebWindow.Native/WebWindow.h index ca5e992..2ed0bfc 100644 --- a/src/WebWindow.Native/WebWindow.h +++ b/src/WebWindow.Native/WebWindow.h @@ -12,9 +12,6 @@ #include #include #include "WebView2.h" -typedef void(__stdcall* ACTION)(); -typedef void(__stdcall* WebMessageReceivedCallback)(UTF8String message); -typedef void* (__stdcall *WebResourceRequestedCallback) (UTF8String url, int* outNumBytes, UTF8String *outContentType); typedef const wchar_t* AutoString; #else #ifdef OS_LINUX @@ -57,6 +54,8 @@ class WebWindow Microsoft::WRL::ComPtr m_process; Microsoft::WRL::ComPtr m_processOptions; void AttachWebView(); + bool AttachWebViewChromium(); + bool AttachWebViewEdge(); #elif OS_LINUX GtkWidget* _window; GtkWidget* _webview; From 5d6e4bee896c19b0c0f11d547e35617b7c0996e5 Mon Sep 17 00:00:00 2001 From: Andrii Kurdiumov Date: Sun, 22 Mar 2020 16:57:57 +0600 Subject: [PATCH 3/3] Unlock ony one working condition --- testassets/HelloWorldApp/Program.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/testassets/HelloWorldApp/Program.cs b/testassets/HelloWorldApp/Program.cs index ddf52c0..449be21 100644 --- a/testassets/HelloWorldApp/Program.cs +++ b/testassets/HelloWorldApp/Program.cs @@ -24,8 +24,8 @@ static void Main(string[] args) window.SendMessage("Got message: " + message); }; - window.NavigateToLocalFile("wwwroot/index.html"); - //window.NavigateToUrl("https://bing.com"); + //window.NavigateToLocalFile("wwwroot/index.html"); + window.NavigateToUrl("https://bing.com"); window.WaitForExit(); } }