API Review: Shared WebView2 Cluster Environment - #5654
Conversation
API proposal for a shared WebView2 cluster environment using the symmetric Create + synchronous Get model (Approach 2). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- (1.7) Rename PerExeProfileIsolation -> PerHostProfileIsolation for OS-neutrality - (1.6) Document per-Id persisted state in the Get reference doc-comment - (1.10) Add API-shape decisions section: new options type vs extending ICoreWebView2EnvironmentOptions, and WinRT mismatch-surfacing (result/status enum vs COMException) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Per repo spec conventions (template: 'talk about behavior, not implementation'), replace the storage/locking pseudocode (registry paths, AcquireLock/WriteRecord, WAIT_ABANDONED) with observable behavior guarantees, and condense the alternatives-considered section. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Top-level sections now exactly match the spec template (Background, Conceptual, Examples, API Details, Appendix). Remove the 'Open questions' section (no other spec has one) and the reviewer-facing 'API-shape decisions'/'Behavior guarantees' Appendix subsections; keep a lean 'Alternatives considered' and 'Relationship to existing options', matching how other specs use the Appendix. Also fix the C++ example's Get error handling and add Id constraints. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
CreateCoreWebView2ClusterEnvironment -> CreateOrJoinCoreWebView2ClusterEnvironment, the handler and the .NET/WinRT async method renamed to match. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The GUIDs were fabricated placeholders; real IIDs are allocated at implementation time. Matches specs like CustomDataPartition.md that omit uuid in the review IDL. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
No WebView2 spec exposes an HRESULT constant as API. Match the convention (e.g. MultiProfile.md) of throwing a COMException and comparing ex.HResult against the HRESULT value with a comment naming the error. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Trim API Details doc-comments to concise behavior, matching LMC reference conventions and the template style guide (describe behavior, not implementation). Removed implementation detail (storage/persistence 'State' note, Id->folder mapping rationale), design commentary (OS-neutral naming, sync justification), and conceptual/strategy narrative already covered in the Conceptual section. Standardized on 'options' over 'pinned set' jargon. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Drop internal shell/widgets example - Use 'WebView2 environment' rather than 'browser process tree' - Remove 'by accident' framing for cooperating apps - Remove reviewer-meta spec-scope/alternatives line from Background - Remove template conceptual-note line - Remove 'constant in a shared header' parenthetical Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The full P1/P2/P3 comparison lives in the internal design doc; the condensed duplicate is not public-doc material and most specs do not carry a rejected-designs section. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
ChannelSearchKind only defines search order and is incomplete without ReleaseChannels (its matched pair). Keep the first iteration minimal; channel-selection options can be appended later on a derived options interface without breaking compat. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The prior text cited remote-debugging port/logging as omitted, but those are set via AdditionalBrowserArguments, which is included. Reframe: every exposed option is process-wide and first-creator-supplied; runtime-locating options (BrowserExecutableFolder, TargetCompatibleBrowserVersion, ReleaseChannels/ChannelSearchKind) are deferred to a later derived interface. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Appendix: keep only the process-wide new-type paragraph; drop the omitted-options list and any future-addition promise - Standardize 'attach to an identical cluster' -> 'attach to a cluster with matching options' - Name the model 'symmetric create-or-join' to match CreateOrJoin... - Re-flow the awkward Background line wrap - Remove the duplicate mismatch comment in the .NET example Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Model create/join outcomes with a COREWEBVIEW2_CLUSTER_ENVIRONMENT_STATUS enum (Succeeded, OptionsMismatch) returned in the completion handler, alongside errorCode (Print-style), instead of a bespoke HRESULT. - Surface not-supported (sandboxed/low-integrity/UWP, cannot share/access the UDF) via the synchronous return HRESULT_FROM_WIN32(ERROR_NOT_SUPPORTED) for CreateOrJoin (handler not called) and as the HRESULT/COMException for the synchronous Get. - Add ERROR_NOT_SUPPORTED handling to both examples; fix .NET Get-inside-try and retry status check. - Address PR feedback: gets->creates; recommend a stable descriptive Id (need not be a GUID); define 'cooperating host apps'; state a cluster occupies its own UDF namespace and never joins a CreateCoreWebView2EnvironmentWithOptions environment. - Standardize terminology (cluster options, shared browser process, matching options); trim implementation detail from PerHostProfileIsolation; clarify profile-isolation wording. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Adopt the ICoreWebView2ClusterEnvironmentCreateResult interface (Status + Environment) in the completion handler instead of separate status/environment parameters, matching the ICoreWebView2ExperimentalUpdateRuntimeResult / ICoreWebView2ExecuteScriptResult idiom and making the COM handler a clean source for the existing WinRT CreateResult projection. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Reframe the excluded options as a deliberate scoping choice for this initial version, not a technical 'cannot be shared'. - Simplify the Id-to-folder sentence and drop the confusing on-disk-layout clause. - Clarify that a private fallback environment uses your own user data folder with its own separate data. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Address Priyank's editorial comments: determines vs fixes; drop informal phrasing ('paid to launch', 'we mean', 'likes it'); apps -> applications; runtime -> WebView2 Runtime; de-emphasize sync/async in prose; rename BuildMyOptions -> BuildClusterOptions; standardize 'exists' over 'configured'; use nullptr in COM doc-comments; name the shared browser process; fix a missing blank line.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Label the Win32 C++ API Details code block as cpp. - Reword the CreateOrJoin doc-comment to drop synchronous/asynchronous adjectives and name CreateOrJoinCoreWebView2ClusterEnvironment and the handler parameter directly. - Standardize on Id for the concept/property in prose and doc-comments; keep the literal signature parameters lowercase id. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…ainer, errors - Add ReleaseChannels and ChannelSearchKind to cluster options (COM + WinRT), matching the PoC implementation; defaults all-channels and MOST_STABLE. - Fix options lifetime: a cluster exists only while its browser process is running; Get returns ERROR_NOT_FOUND when not running (was incorrectly 'available whether or not running'). - Define what 'match' means: all options except Id equal, AdditionalBrowserArguments exact string, custom schemes same in same order. - Tighten NOT_SUPPORTED wording to sandboxed AppContainer (UWP), matching the AppContainer gate in the implementation. - Rename heading to Description; add Win32-only support note; add cross-host diagnostics trust note; validate existing options in samples; make failing-create handling concrete; state handler errorCode carries the same failures as CreateCoreWebView2EnvironmentWithOptions; align custom-scheme memory wording. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Add %LOCALAPPDATA%\Microsoft\WebView2Clusters\<Id> as the runtime-derived cluster user data folder, and state that CreateCoreWebView2EnvironmentWithOptions cannot join a cluster even when passed a cluster folder path explicitly. Matches DeriveClusterUserDataFolder and the IsClusterUserDataFolder guard in entry.cc. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…l match, concurrency, trust/identity/session model, loader-override contract, and consistency fixes Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: ef97d825-5b56-4882-8226-a3af1d04c96a
…and fix consistency Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: ef97d825-5b56-4882-8226-a3af1d04c96a
…ed-options rationale Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: ef97d825-5b56-4882-8226-a3af1d04c96a
…eview) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: ef97d825-5b56-4882-8226-a3af1d04c96a
…draft Add spec for SharedClusterEnvironment.md
| Options **match** when every option on `ICoreWebView2ClusterEnvironmentOptions` | ||
| except `ClusterName` is equal: each scalar and boolean option is equal, `AdditionalBrowserArguments` | ||
| is the same string (compared exactly, with no normalization of whitespace or switch | ||
| order), and the custom scheme registrations are the same in the same order. The |
There was a problem hiding this comment.
Are CustomSchemeRegistrations usually order-dependent? If not, this seems like an extra (arbitrary?) bar for coordination between apps seeking to form a cluster.
Or, if this is an existing restriction of CoreWebView2Environment ::CreateWithOptionsAsync , then appeal to consistency.
There was a problem hiding this comment.
Agreed that it does feel arbitrary, however practically it shouldn't be an issue: either the apps have coordinated well ahead of time and have the exact same list or they use the Get...Options method to get the exact correct list.
Please update API public docs to note that this is intended for well coordinated apps to share WebView2 and not intended for loosely coupled apps to share WebView2.
| mirroring how `CreateCoreWebView2EnvironmentWithOptions` maps to | ||
| `CoreWebView2Environment.CreateAsync`. `CreateOrJoinClusterEnvironmentAsync` returns a | ||
| `CoreWebView2ClusterEnvironmentCreateResult` that contains the `Status` and, on success, | ||
| the `Environment`. When the operation cannot be started or fails, the method throws an exception; in |
There was a problem hiding this comment.
Does wv2 use the WinRT API preference to use return values for execution failures? Windows' WinRT APIs throw for "catastrophic errors" and "usage errors".
This appears to be throwing for execution failures, where I would expect to see a CoreWebView2ClusterEnvironmentCreateResult with .Environment == null
There was a problem hiding this comment.
This also appears in the WinRT sample above. We appear to be requiring a try/catch to handle 0x80070032, which for OS WinRT APIs should not be required in a minimal correct usage of the API.
There was a problem hiding this comment.
Please see above discussion about non-exceptional status codes.
| can expose frame names and last-committed URLs for frames owned by other hosts in the | ||
| cluster. Cluster members must trust one another with this metadata. | ||
|
|
||
| Joining a cluster is controlled only by the `ClusterName`. In this initial version there is no |
There was a problem hiding this comment.
It seems easier for apps to accidently collide on a cluster name than would have been plausible to accidently try using an in-use userDataFolder. Do we think this is still low likelihood because clustering will be such a niche case compared to default environment creation?
There was a problem hiding this comment.
Please update docs to provide recommended pattern for clustername including examples:
To ensure a unique cluster name consider including your company name or a GUID, for example: Contoso.Something, or {GUID}.Something (please fix this)
| We want a first-class, *explicit* way for a set of cooperating host applications to | ||
| opt into one shared WebView2 environment—a "cluster"—and to agree on the shared | ||
| options up front. Cooperating host applications trust one | ||
| another and agree on a shared `ClusterName` in advance; sharing is by convention, and |
There was a problem hiding this comment.
may be out of scope of today's API addition. Is there any thinking about how someone in a Cluster can tell who else has joined?
To add something like that in v2, I think we'd add to CoreWebView2ClusterEnvironmentCreateResult so that apps joining a cluster could get some kind of queryable object or event source for info about it.
There was a problem hiding this comment.
Discussion: we don't know what this may look like in the future and we have room on the result or elsewhere if necessary.
|
|
||
| Joining a cluster is controlled only by the `ClusterName`. In this initial version there is no | ||
| authentication or admission control: any process running as the same user that supplies | ||
| a matching `ClusterName` (and resolves the same runtime) attaches to the cluster, and matching |
There was a problem hiding this comment.
If two processes both create a cluster named "X" against different runtimes, how do they both reside in the same %LOCALAPPDATA%\Microsoft\WebView2Clusters\X directory yet remain distinct?
There was a problem hiding this comment.
Please update docs to note that different runtime means you can't join the cluster versus a different non-matching cluster name gives you a different cluster.
| of the joining host. Treat the `ClusterName` as a shared capability agreed among cooperating | ||
| hosts, and do not use a cluster to share data across trust boundaries. If it matters | ||
| that untrusted code on the machine cannot join, choose a `ClusterName` that such code cannot | ||
| predict; the per-user cluster root still scopes clusters to a single user. |
There was a problem hiding this comment.
But the attacker can just enumerate the directories under %LOCALAPPDATA%\Microsoft\WebView2Clusters to discover all cluster names.
I think we shouldn't promise that there's a way to keep untrusted code out. We can say "To make it less likely" to say that you can make it harder, but you can't stop it entirely.
There was a problem hiding this comment.
Please update docs to note that there is no security boundary here. Choosing a better name doesn't provide meaningly security benefits. Probably just remove final sentence.
| Profile isolation in a cluster is **anti-misuse, not a security boundary**. When | ||
| `PerHostProfileIsolation` is TRUE (the default), profile names are namespaced per | ||
| host application, so two different host applications that use the same profile | ||
| name do not accidentally end up sharing one profile. Here a "host application" is |
There was a problem hiding this comment.
If two hosts both create a cluster named "X", how do they both reside in the same %LOCALAPPDATA%\Microsoft\WebView2Clusters\X directory yet remain distinct?
There was a problem hiding this comment.
This is specifically about Edge browser profiles not cluster names.
Please update docs to make this clearer by explicitly calling out that these are profiles within clusters.
| UsePrivateEnvironment(); | ||
| } | ||
| break; | ||
| } |
There was a problem hiding this comment.
This code assumes that no new status values will be added in the future. Is that a safe assumption? Or should it have a
default:
// unknown failure, don't use clusters.
UsePrivateEnvironment();
break;There was a problem hiding this comment.
Please fix - use recommended above
| } | ||
| else | ||
| { | ||
| // Still mismatched after retrying; fall back to a private environment. |
There was a problem hiding this comment.
Misleading comment. Can also get here if the Status is something other than Succeeded or OptionsMismatch.
There was a problem hiding this comment.
Please fix. Similar to the switch with missing default above.
Please consider making the C++ & C# sample code more closely match and use matching switch or if/else
| } | ||
| options = existing; | ||
| } | ||
| else if (hr == HRESULT_FROM_WIN32(ERROR_NOT_FOUND)) |
There was a problem hiding this comment.
C++ and C# are inconsistent here. If the cluster cannot be found, C++ fails the operation with ERROR_NOT_FOUND, but C# succeeds the operation and returns null.
Should align the behaviors.
There was a problem hiding this comment.
Please fix sample code and make sure it matches between SOK/null in C++ & C#. Slight pref for C#
Please examine exceptions from CreateCoreWebView2Environment and the new CreateOrJoin..ClusterEnv and see what errors makes sense to make into non-exceptional status results. And ensure its consistent between C++ & C#.
| } | ||
|
|
||
| void AppWindow::CreateSharedEnvironment() | ||
| { |
There was a problem hiding this comment.
Shouldn't step 0 be "use the version API to see if clusters are supported"?
| responsibility. Because several applications can use the same cluster, no single | ||
| application should delete the folder while another might still use it. When the last | ||
| cooperating application is being uninstalled, that application should delete the folder | ||
| so it is not left behind on disk. |
There was a problem hiding this comment.
Does this mean that apps that use this feature cannot be MSIX? Because you can't run custom code at MSIX uninstall.
There was a problem hiding this comment.
Please consider how to resolve this or block appcontainers from using this feature (not great option because MSIX is the preferred deployment mechanism)
| // 2. Read the cluster's user data folder path from the environment this | ||
| // application joined. | ||
| wil::unique_cotaskmem_string userDataFolder; | ||
| CHECK_FAILURE(m_clusterEnvironment->get_UserDataFolder(&userDataFolder)); |
There was a problem hiding this comment.
Who initializes this variable m_clusterEnvironment?
I think there is a missing step: 1.5: Get the environment that we want to clean up. To do that, we need to create the environment (or join it). This will start the shared browser process, but that's apparently unavoidable. There's no way to get an environment for a non-running cluster.
GetCoreWebView2ClusterEnvironmentOptions(L"contoso.cluster", &options);
CreateOrJoinCoreWebView2ClusterEnvironment(options, Callback([](HRESULT errorCode, auto result) {
// blah blah retry or whatever
if (SUCCEEDED(errorCode) && result->Status == Succeeded) {
// continue with step 2
}
});There was a problem hiding this comment.
Please fix:
- Make the sample code show how to obtain the cluster given that many cases uninstall is totally separate process from app getting uninstalled. Or consider addiotnal uninstall sample standalone v clean on exit.
- Do we want to make this easier? Perhaps ties into above issue resolution.
| return S_OK; | ||
| }).Get(), | ||
| nullptr)); | ||
| m_clusterEnvironment.reset(); |
There was a problem hiding this comment.
This sample hangs because our own process is keeping the cluster environment from exiting!
Mussing step 4: Exit our environment and hope the others will exit soon too.
The only way to exit our environment appears to be to create a webview2 and then close the webview2.
m_clusterEnvironment.as<ICoreWebView2Environment3>()->CreateCoreWebview2Controller(m_mainWindow, [](HRESULT errorCode, auto controller) {
if (SUCCEEDED(errorCode)) { controller->Close(); /* exit our webview and hope that the others will follow suit */
}
);There was a problem hiding this comment.
The sample is for clean on exit not standalone uninstall. See above.
But also please examine code and make sure its correct.
This pull request introduces the specification for a Shared WebView2 Cluster Environment, a first-class, explicit way for a set of cooperating host applications to opt into one shared WebView2 environment (a "cluster") and agree on the shared options up front. Today apps can share a browser process only implicitly by passing the same user data folder; this API makes the sharing intent explicit, keyed on a well-known
ClusterName, with a clear options-match contract and a way to read a cluster's options before joining.New APIs:
CreateOrJoinCoreWebView2ClusterEnvironment(options, handler)— Symmetric create-or-join. The first host to establish a cluster for a givenClusterNamefixes the cluster's options (first-creator-wins). A later host with matching options attaches to the running cluster; a host with different options receivesCOREWEBVIEW2_CLUSTER_ENVIRONMENT_STATUS_OPTIONS_MISMATCH.GetCoreWebView2ClusterEnvironmentOptions(clusterName, options)— Synchronously reads a running cluster's options without launching the shared browser process, so a host can decide before joining.New Interfaces and Enums:
ICoreWebView2ClusterEnvironmentOptions— the shared options (ClusterName,AdditionalBrowserArguments,Language,AllowSingleSignOnUsingOSPrimaryAccount,EnableTrackingPrevention,AreBrowserExtensionsEnabled,PerHostProfileIsolation,ReleaseChannels,ChannelSearchKind,CustomSchemeRegistrations).ICoreWebView2ClusterEnvironmentCreateResult—StatusplusEnvironment(non-null only when the status is Succeeded).ICoreWebView2CreateOrJoinClusterEnvironmentCompletedHandler— completion handler for the create-or-join operation.COREWEBVIEW2_CLUSTER_ENVIRONMENT_STATUS—Succeeded,OptionsMismatch.Details covered in the spec:
ClusterName, lifetime and liveness model, options-matching semantics, trust/identity/session model, host-dependent option resolution, the loader-override contract, and user-data-folder deletion guidance.