1717#include < Shellapi.h>
1818#include < ShlObj_core.h>
1919#include < winrt/windows.system.h>
20+
2021#include " App.h"
2122#include " AppStartPage.h"
2223#include " CheckFailure.h"
2829#include " ScenarioAddHostObject.h"
2930#include " ScenarioAuthentication.h"
3031#include " ScenarioCookieManagement.h"
32+ #include " ScenarioClientCertificateRequested.h"
3133#include " ScenarioCustomDownloadExperience.h"
3234#include " ScenarioDOMContentLoaded.h"
3335#include " ScenarioNavigateWithWebResourceRequest.h"
@@ -96,11 +98,11 @@ DWORD WINAPI DownloadAndInstallWV2RT(_In_ LPVOID lpParameter)
9698// Creates a new window which is a copy of the entire app, but on the same thread.
9799AppWindow::AppWindow (
98100 UINT creationModeId,
99- std::wstring initialUri,
100- std::wstring userDataFolderParam,
101+ std::wstring initialUri,
102+ std::wstring userDataFolderParam,
101103 bool isMainWindow,
102- std::function<void ()> webviewCreatedCallback,
103- bool customWindowRect,
104+ std::function<void ()> webviewCreatedCallback,
105+ bool customWindowRect,
104106 RECT windowRect,
105107 bool shouldHaveToolbar
106108 )
@@ -135,7 +137,7 @@ AppWindow::AppWindow(
135137 }
136138
137139 m_appBackgroundImageHandle = (HBITMAP)LoadImage (
138- NULL , L" AppBackground.bmp " , IMAGE_BITMAP, 0 , 0 , LR_LOADFROMFILE );
140+ g_hInstance, MAKEINTRESOURCE (IDI_WEBVIEW2_BACKGROUND) , IMAGE_BITMAP, 0 , 0 , LR_DEFAULTCOLOR );
139141 GetObject (m_appBackgroundImageHandle, sizeof (m_appBackgroundImage), &m_appBackgroundImage);
140142 m_memHdc = CreateCompatibleDC (GetDC (m_mainWindow));
141143 SelectObject (m_memHdc, m_appBackgroundImageHandle);
@@ -161,7 +163,7 @@ AppWindow::AppWindow(
161163 ShowWindow (m_mainWindow, g_nCmdShow);
162164 UpdateWindow (m_mainWindow);
163165
164- // If no WebVieRuntime installed, create new thread to do install/download.
166+ // If no WebView2 Runtime installed, create new thread to do install/download.
165167 // Otherwise just initialize webview.
166168 wil::unique_cotaskmem_string version_info;
167169 HRESULT hr = GetAvailableCoreWebView2BrowserVersionString (nullptr , &version_info);
@@ -277,20 +279,13 @@ bool AppWindow::HandleWindowMessage(
277279 {
278280 PAINTSTRUCT ps;
279281 HDC hdc;
280- RECT mainWindowBounds;
281- RECT webViewBounds = {0 };
282282 BeginPaint (hWnd, &ps);
283283
284284 hdc = GetDC (hWnd);
285- GetClientRect (hWnd, &mainWindowBounds);
286-
287- if (auto viewComponent = GetComponent<ViewComponent>())
288- {
289- webViewBounds = viewComponent->GetBounds ();
290- }
291285
292- StretchBlt (hdc, webViewBounds.left , webViewBounds.top , webViewBounds.right , webViewBounds.bottom ,
293- m_memHdc, 0 , 0 , m_appBackgroundImage.bmWidth , m_appBackgroundImage.bmHeight , SRCCOPY);
286+ StretchBlt (hdc, m_appBackgroundImageRect.left , m_appBackgroundImageRect.top ,
287+ m_appBackgroundImageRect.right , m_appBackgroundImageRect.bottom , m_memHdc,
288+ 0 , 0 , m_appBackgroundImage.bmWidth , m_appBackgroundImage.bmHeight , SRCCOPY);
294289
295290 EndPaint (hWnd, &ps);
296291 return true ;
@@ -466,6 +461,11 @@ bool AppWindow::ExecuteWebViewCommands(WPARAM wParam, LPARAM lParam)
466461 NewComponent<ScenarioCustomDownloadExperience>(this );
467462 return true ;
468463 }
464+ case IDM_SCENARIO_USE_DEFERRED_CUSTOM_CLIENT_CERTIFICATE_DIALOG:
465+ {
466+ NewComponent<ScenarioClientCertificateRequested>(this );
467+ return true ;
468+ }
469469 }
470470 return false ;
471471}
@@ -800,6 +800,12 @@ HRESULT AppWindow::OnCreateCoreWebView2ControllerCompleted(HRESULT result, ICore
800800 // browser might not have support for the latest version of the
801801 // ICoreWebView2_N interface.
802802 coreWebView2.query_to (&m_webView);
803+ // Save PID of the browser process serving last WebView created from our
804+ // CoreWebView2Environment. We know the controller was created with
805+ // S_OK, and it hasn't been closed (we haven't called Close and no
806+ // ProcessFailed event could have been raised yet) so the PID is
807+ // available.
808+ CHECK_FAILURE (m_webView->get_BrowserProcessId (&m_newestBrowserPid));
803809 // Create components. These will be deleted when the WebView is closed.
804810 NewComponent<FileComponent>(this );
805811 NewComponent<ProcessComponent>(this );
@@ -838,17 +844,10 @@ HRESULT AppWindow::OnCreateCoreWebView2ControllerCompleted(HRESULT result, ICore
838844 m_onWebViewFirstInitialized = nullptr ;
839845 }
840846
841- if (m_initialUri.empty ())
842- {
843- // StartPage uses initialized values of the WebView and Environment
844- // so we wait to call StartPage::GetUri until after the WebView is
845- // created.
846- m_initialUri = AppStartPage::GetUri (this );
847- }
848-
849847 if (m_initialUri != L" none" )
850848 {
851- CHECK_FAILURE (m_webView->Navigate (m_initialUri.c_str ()));
849+ std::wstring initialUri = m_initialUri.empty () ? AppStartPage::GetUri (this ) : m_initialUri;
850+ CHECK_FAILURE (m_webView->Navigate (initialUri.c_str ()));
852851 }
853852 }
854853 else
@@ -1090,49 +1089,134 @@ void AppWindow::ResizeEverything()
10901089 {
10911090 view->SetBounds (availableBounds);
10921091 }
1092+ m_appBackgroundImageRect = availableBounds;
10931093}
10941094
10951095// ! [Close]
10961096// Close the WebView and deinitialize related state. This doesn't close the app window.
10971097void AppWindow::CloseWebView (bool cleanupUserDataFolder)
10981098{
1099+ // 1. Delete components.
10991100 DeleteAllComponents ();
1101+
1102+ // 2. If cleanup needed and BrowserProcessExited event interface available,
1103+ // register to cleanup upon browser exit.
1104+ wil::com_ptr<ICoreWebView2ExperimentalEnvironment4> experimentalEnvironment4;
1105+ if (m_webViewEnvironment)
1106+ {
1107+ experimentalEnvironment4 =
1108+ m_webViewEnvironment.try_query <ICoreWebView2ExperimentalEnvironment4>();
1109+ }
1110+ if (cleanupUserDataFolder && experimentalEnvironment4)
1111+ {
1112+ // Before closing the WebView, register a handler with code to run once the
1113+ // browser process and associated processes are terminated.
1114+ CHECK_FAILURE (experimentalEnvironment4->add_BrowserProcessExited (
1115+ Callback<ICoreWebView2ExperimentalBrowserProcessExitedEventHandler>(
1116+ [experimentalEnvironment4, this ](
1117+ ICoreWebView2Environment* sender,
1118+ ICoreWebView2ExperimentalBrowserProcessExitedEventArgs* args) {
1119+ COREWEBVIEW2_BROWSER_PROCESS_EXIT_KIND kind;
1120+ UINT32 pid;
1121+ CHECK_FAILURE (args->get_BrowserProcessExitKind (&kind));
1122+ CHECK_FAILURE (args->get_BrowserProcessId (&pid));
1123+
1124+ // If a new WebView is created from this CoreWebView2Environment after
1125+ // the browser has exited but before our handler gets to run, a new
1126+ // browser process will be created and lock the user data folder
1127+ // again. Do not attempt to cleanup the user data folder in these
1128+ // cases. We check the PID of the exited browser process against the
1129+ // PID of the browser process to which our last CoreWebView2 attached.
1130+ if (pid == m_newestBrowserPid)
1131+ {
1132+ // Watch for graceful browser process exit. Let ProcessFailed event
1133+ // handler take care of failed browser process termination.
1134+ if (kind == COREWEBVIEW2_BROWSER_PROCESS_EXIT_KIND_NORMAL)
1135+ {
1136+ CHECK_FAILURE (experimentalEnvironment4->remove_BrowserProcessExited (
1137+ m_browserExitedEventToken));
1138+ // Release the environment only after the handler is invoked.
1139+ // Otherwise, there will be no environment to raise the event when
1140+ // the collection of WebView2 Runtime processes exit.
1141+ m_webViewEnvironment = nullptr ;
1142+ CleanupUserDataFolder ();
1143+ }
1144+ }
1145+ else
1146+ {
1147+ // The exiting process is not the last in use. Do not attempt cleanup
1148+ // as we might still have a webview open over the user data folder.
1149+ // Do not block from event handler.
1150+ RunAsync ([this ]() {
1151+ MessageBox (
1152+ m_mainWindow,
1153+ L" A new browser process prevented cleanup of the user data folder." ,
1154+ L" Cleanup User Data Folder" , MB_OK);
1155+ });
1156+ }
1157+
1158+ return S_OK;
1159+ })
1160+ .Get (),
1161+ &m_browserExitedEventToken));
1162+ }
1163+
1164+ // 3. Close the webview.
11001165 if (m_controller)
11011166 {
11021167 m_controller->Close ();
11031168 m_controller = nullptr ;
11041169 m_webView = nullptr ;
11051170 m_webView3 = nullptr ;
11061171 }
1107- m_webViewEnvironment = nullptr ;
1108- if (cleanupUserDataFolder)
1109- {
1110- // For non-UWP apps, the default user data folder {Executable File Name}.WebView2
1111- // is in the same directory next to the app executable. If end
1112- // developers specify userDataFolder during WebView environment
1113- // creation, they would need to pass in that explicit value here.
1114- // For more information about userDataFolder:
1115- // https://docs.microsoft.com/microsoft-edge/webview2/reference/win32/webview2-idl#createcorewebview2environmentwithoptions
1116- WCHAR userDataFolder[MAX_PATH] = L" " ;
1117- // Obtain the absolute path for relative paths that include "./" or "../"
1118- _wfullpath (
1119- userDataFolder, GetLocalPath (L" .WebView2" , true ).c_str (), MAX_PATH);
1120- std::wstring userDataFolderPath (userDataFolder);
1121-
1122- std::wstring message = L" Are you sure you want to clean up the user data folder at\n " ;
1123- message += userDataFolderPath;
1124- message += L" \n ?\n Warning: This action is not reversible.\n\n " ;
1125- message += L" Click No if there are other open WebView instances.\n " ;
1126-
1127- if (MessageBox (m_mainWindow, message.c_str (), L" Cleanup User Data Folder" , MB_YESNO) ==
1128- IDYES)
1172+
1173+ // 4. If BrowserProcessExited event interface is not available, release
1174+ // environment and proceed to cleanup immediately. If the interface is
1175+ // available, release environment only if not waiting for the event.
1176+ if (!experimentalEnvironment4)
1177+ {
1178+ m_webViewEnvironment = nullptr ;
1179+ if (cleanupUserDataFolder)
11291180 {
1130- CHECK_FAILURE ( DeleteFileRecursive (userDataFolderPath) );
1181+ CleanupUserDataFolder ( );
11311182 }
11321183 }
1184+ else if (!cleanupUserDataFolder)
1185+ {
1186+ // Release the environment object here only if no cleanup is needed.
1187+ // If cleanup is needed, the environment object release is deferred
1188+ // until the browser process exits, otherwise the handler for the
1189+ // BrowserProcessExited event will not be called.
1190+ m_webViewEnvironment = nullptr ;
1191+ }
11331192}
11341193// ! [Close]
11351194
1195+ void AppWindow::CleanupUserDataFolder ()
1196+ {
1197+ // For non-UWP apps, the default user data folder {Executable File Name}.WebView2
1198+ // is in the same directory next to the app executable. If end
1199+ // developers specify userDataFolder during WebView environment
1200+ // creation, they would need to pass in that explicit value here.
1201+ // For more information about userDataFolder:
1202+ // https://docs.microsoft.com/microsoft-edge/webview2/reference/win32/webview2-idl#createcorewebview2environmentwithoptions
1203+ WCHAR userDataFolder[MAX_PATH] = L" " ;
1204+ // Obtain the absolute path for relative paths that include "./" or "../"
1205+ _wfullpath (userDataFolder, GetLocalPath (L" .WebView2" , true ).c_str (), MAX_PATH);
1206+ std::wstring userDataFolderPath (userDataFolder);
1207+
1208+ std::wstring message = L" Are you sure you want to clean up the user data folder at\n " ;
1209+ message += userDataFolderPath;
1210+ message += L" \n ?\n Warning: This action is not reversible.\n\n " ;
1211+ message += L" Click No if there are other open WebView instances.\n " ;
1212+
1213+ if (MessageBox (m_mainWindow, message.c_str (), L" Cleanup User Data Folder" , MB_YESNO) ==
1214+ IDYES)
1215+ {
1216+ CHECK_FAILURE (DeleteFileRecursive (userDataFolderPath));
1217+ }
1218+ }
1219+
11361220HRESULT AppWindow::DeleteFileRecursive (std::wstring path)
11371221{
11381222 wil::com_ptr<IFileOperation> fileOperation;
@@ -1148,7 +1232,7 @@ HRESULT AppWindow::DeleteFileRecursive(std::wstring path)
11481232
11491233 // Add the operation
11501234 CHECK_FAILURE (fileOperation->DeleteItem (userDataFolder.get (), NULL ));
1151- CHECK_FAILURE ( userDataFolder-> Release () );
1235+ userDataFolder. reset ( );
11521236
11531237 // Perform the operation to delete the directory
11541238 CHECK_FAILURE (fileOperation->PerformOperations ());
0 commit comments