Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
79 changes: 78 additions & 1 deletion SampleApps/WebView2APISample/App.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,9 @@
#include <vector>

#include "AppWindow.h"
#include "CheckFailure.h"
#include "DpiUtil.h"
#include "ScenarioClusterEnvironment.h"

HINSTANCE g_hInstance;
int g_nCmdShow;
Expand Down Expand Up @@ -44,6 +46,12 @@ wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PWSTR lpCmdLine, int nCmd
DWORD creationModeId = IDM_CREATION_MODE_WINDOWED;
WebViewCreateOption opt;

// When launched with --clustername=..., this instance is a secondary host
// process that should join an existing shared cluster environment instead
// of creating a normal (private) environment.
bool joinCluster = false;
ClusterEnvironmentSpec clusterSpec;

if (lpCmdLine && lpCmdLine[0])
{
int paramCount = 0;
Expand Down Expand Up @@ -94,6 +102,64 @@ wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PWSTR lpCmdLine, int nCmd
{
userDataFolder = nextParam.substr(nextParam.find(L'=') + 1);
}
else if (NEXT_PARAM_CONTAINS(L"clustername="))
{
joinCluster = true;
clusterSpec.clusterName = nextParam.substr(nextParam.find(L'=') + 1);
}
else if (NEXT_PARAM_CONTAINS(L"clusterlang="))
{
clusterSpec.language = nextParam.substr(nextParam.find(L'=') + 1);
}
else if (NEXT_PARAM_CONTAINS(L"clusterargs="))
{
clusterSpec.additionalBrowserArguments =
nextParam.substr(nextParam.find(L'=') + 1);
}
else if (NEXT_PARAM_CONTAINS(L"clustersso="))
{
clusterSpec.allowSingleSignOn =
nextParam.substr(nextParam.find(L'=') + 1) == L"1";
}
else if (NEXT_PARAM_CONTAINS(L"clustertracking="))
{
clusterSpec.enableTrackingPrevention =
nextParam.substr(nextParam.find(L'=') + 1) == L"1";
}
else if (NEXT_PARAM_CONTAINS(L"clusterextensions="))
{
clusterSpec.areBrowserExtensionsEnabled =
nextParam.substr(nextParam.find(L'=') + 1) == L"1";
}
else if (NEXT_PARAM_CONTAINS(L"clusterisolation="))
{
clusterSpec.perHostProfileIsolation =
nextParam.substr(nextParam.find(L'=') + 1) == L"1";
}
else if (NEXT_PARAM_CONTAINS(L"clusterchannels="))
{
// wcstol rather than stoi: a malformed value should fall back to
// 0 (NONE) rather than throw out of command-line parsing. Masked
// because the value is cast to a flags enum, and a negative or
// oversized number would otherwise set every reserved bit.
const long channels =
wcstol(nextParam.substr(nextParam.find(L'=') + 1).c_str(), nullptr, 10);
clusterSpec.releaseChannels =
static_cast<COREWEBVIEW2_RELEASE_CHANNELS>(channels & kAllReleaseChannels);
}
else if (NEXT_PARAM_CONTAINS(L"clustersearchkind="))
{
// Parsed the same way the parent writes it, rather than by
// testing for L"1", so a value added to the enum later does not
// silently arrive as MOST_STABLE. Clamped for the same reason
// the channels are masked: this is cast to an enum.
const long searchKind =
wcstol(nextParam.substr(nextParam.find(L'=') + 1).c_str(), nullptr, 10);
clusterSpec.channelSearchKind =
searchKind == COREWEBVIEW2_CHANNEL_SEARCH_KIND_LEAST_STABLE
? COREWEBVIEW2_CHANNEL_SEARCH_KIND_LEAST_STABLE
: COREWEBVIEW2_CHANNEL_SEARCH_KIND_MOST_STABLE;
}
else if (NEXT_PARAM_CONTAINS(L"creationmode="))
{
nextParam = nextParam.substr(nextParam.find(L'=') + 1);
Expand Down Expand Up @@ -125,7 +191,18 @@ wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PWSTR lpCmdLine, int nCmd

DpiUtil::SetProcessDpiAwarenessContext(dpiAwarenessContext);

new AppWindow(creationModeId, opt, initialUri, userDataFolder, true);
if (joinCluster)
{
// COM is required before calling into the loader; AppWindow (created in
// the completion handler) will also initialize COM on this thread.
CHECK_FAILURE(OleInitialize(nullptr));
CreateOrJoinClusterAndOpenWindow(
clusterSpec, /*isMainWindow=*/true, /*parent=*/nullptr);
}
else
{
new AppWindow(creationModeId, opt, initialUri, userDataFolder, true);
}

int retVal = RunMessagePump();

Expand Down
63 changes: 63 additions & 0 deletions SampleApps/WebView2APISample/AppStartPage.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,62 @@ std::wstring GetRuntimePath(AppWindow* appWindow)
return ResolvePathAndTrimFile(runtimePath);
}

// Returns the actual user data folder in use by the current WebView2
// environment. Empty if the environment doesn't support querying it
// (ICoreWebView2Environment7 unavailable).
std::wstring GetUserDataFolder(AppWindow* appWindow)
{
wil::com_ptr<ICoreWebView2Environment> environment = appWindow->GetWebViewEnvironment();
auto environment7 = environment.try_query<ICoreWebView2Environment7>();
if (!environment7)
return L"";

wil::unique_cotaskmem_string userDataFolder;
if (FAILED(environment7->get_UserDataFolder(&userDataFolder)) || !userDataFolder)
return L"";
return userDataFolder.get();
}

// Classifies a user data folder as shared when it lives under the shared
// cluster root, mirroring the detection the browser itself uses.
std::wstring GetUserDataFolderKind(const std::wstring& userDataFolder)
{
if (userDataFolder.empty())
return L"Unknown";

std::wstring lowerUserDataFolder = userDataFolder;
std::transform(
lowerUserDataFolder.begin(), lowerUserDataFolder.end(), lowerUserDataFolder.begin(),
::towlower);
return lowerUserDataFolder.find(L"\\microsoft\\webview2clusters\\") != std::wstring::npos
? L"Shared (cluster)"
: L"Non-shared";
}

// Percent-encodes the characters that would otherwise be read as query string
// syntax. The folder carries a user-chosen name that may contain '&' or '=',
// either of which would split the value and blank the field.
std::wstring EscapeForQuery(const std::wstring& value)
{
static constexpr wchar_t kHexDigits[] = L"0123456789ABCDEF";
std::wstring escaped;
escaped.reserve(value.size());
for (const wchar_t c : value)
{
if (c == L'&' || c == L'=' || c == L'%' || c == L'#' || c == L'?' || c == L'+')
{
escaped.push_back(L'%');
escaped.push_back(kHexDigits[(c >> 4) & 0xF]);
escaped.push_back(kHexDigits[c & 0xF]);
}
else
{
escaped.push_back(c);
}
}
return escaped;
}

std::wstring GetUri(AppWindow* appWindow)
{
std::wstring uri = appWindow->GetLocalUri(L"AppStartPage.html", true);
Expand All @@ -102,6 +158,13 @@ std::wstring GetUri(AppWindow* appWindow)
uri += L"&runtimePath=";
uri += GetRuntimePath(appWindow);

std::wstring userDataFolder = GetUserDataFolder(appWindow);
uri += L"&userDataFolder=";
uri += EscapeForQuery(userDataFolder);

uri += L"&userDataFolderKind=";
uri += GetUserDataFolderKind(userDataFolder);

return uri;
}

Expand Down
26 changes: 24 additions & 2 deletions SampleApps/WebView2APISample/AppWindow.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
#include "ScenarioAddHostObject.h"
#include "ScenarioAuthentication.h"
#include "ScenarioClientCertificateRequested.h"
#include "ScenarioClusterEnvironment.h"
#include "ScenarioCookieManagement.h"
#include "ScenarioCustomDownloadExperience.h"
#include "ScenarioCustomScheme.h"
Expand Down Expand Up @@ -203,9 +204,10 @@ AppWindow::AppWindow(
UINT creationModeId, const WebViewCreateOption& opt, const std::wstring& initialUri,
const std::wstring& userDataFolderParam, bool isMainWindow,
std::function<void()> webviewCreatedCallback, bool customWindowRect, RECT windowRect,
bool shouldHaveToolbar, bool isPopup)
bool shouldHaveToolbar, bool isPopup, ICoreWebView2Environment* providedEnvironment)
: m_creationModeId(creationModeId), m_webviewOption(opt), m_initialUri(initialUri),
m_onWebViewFirstInitialized(webviewCreatedCallback), m_isPopupWindow(isPopup)
m_onWebViewFirstInitialized(webviewCreatedCallback), m_isPopupWindow(isPopup),
m_providedEnvironment(providedEnvironment)
{
// Initialize COM as STA.
CHECK_FAILURE(OleInitialize(NULL));
Expand Down Expand Up @@ -648,6 +650,18 @@ bool AppWindow::ExecuteWebViewCommands(WPARAM wParam, LPARAM lParam)
NewComponent<ScenarioCustomScheme>(this);
return true;
}
case IDM_SCENARIO_CLUSTER_ENVIRONMENT:
{
NewComponent<ScenarioClusterEnvironment>(
this, ScenarioClusterEnvironment::Mode::CreateOrJoin);
return true;
}
case IDM_SCENARIO_CLUSTER_ENVIRONMENT_GET_OPTIONS:
{
NewComponent<ScenarioClusterEnvironment>(
this, ScenarioClusterEnvironment::Mode::GetOptions);
return true;
}
case IDM_SCENARIO_CUSTOM_SCHEME_NAVIGATE:
{
NewComponent<ScenarioCustomSchemeNavigate>(this);
Expand Down Expand Up @@ -1841,6 +1855,14 @@ void AppWindow::InitializeWebView()
}
//! [CreateCoreWebView2EnvironmentWithOptions]

// When a pre-created environment was provided, host the WebView in it
// directly instead of creating a new environment.
if (m_providedEnvironment)
{
OnCreateEnvironmentCompleted(S_OK, m_providedEnvironment.get());
return;
}

std::wstring args;
// Page Interaction Restriction Manager requires msPageInteractionManagerWebview2 to be
// enabled from the args, as by default it's disabled in the browser. If you want to
Expand Down
6 changes: 5 additions & 1 deletion SampleApps/WebView2APISample/AppWindow.h
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ class AppWindow
const std::wstring& initialUri = L"", const std::wstring& userDataFolderParam = L"",
bool isMainWindow = false, std::function<void()> webviewCreatedCallback = nullptr,
bool customWindowRect = false, RECT windowRect = {0}, bool shouldHaveToolbar = true,
bool isPopup = false);
bool isPopup = false, ICoreWebView2Environment* providedEnvironment = nullptr);

~AppWindow();

Expand Down Expand Up @@ -269,6 +269,10 @@ class AppWindow
int m_refCount = 1;
bool m_isClosed = false;

// When non-null, InitializeWebView uses this pre-created environment
// instead of creating its own via CreateCoreWebView2EnvironmentWithOptions.
wil::com_ptr<ICoreWebView2Environment> m_providedEnvironment;

// The following is state that belongs with the webview, and should
// be reinitialized along with it. Everything here is undefined when
// m_webView is null.
Expand Down
Loading
Loading