[Breaking Change] Enabling Windows ShellHandwriting Support for WebView2 in WindowToVisual Mode
Summary
WebView2 is introducing support for Windows shellhandwriting (pen handwriting-to-text) in WindowToVisual hosting mode, gated behind the feature flag msAbydosForWindowlessWV2.
Details
Scope:
- In scope: WebView2 in WindowToVisual hosting mode on Windows.
- Out of scope: VisualToVisual hosting mode — not supported in this change.
- Out of scope: WindowToWindow hosting mode already supports Windows shellhandwriting.
Purpose: Enable Windows shell handwriting on edit fields inside WebView2 instances hosted in WindowToVisual mode.
To enable correct handwriting routing, WebView2 now registers a per-instance ITfHandwritingSink (as a TSF secondary client) for every WebView2 instance. This is a behavioral change in how WebView2 interacts with the Text Services Framework on the host app's UI thread. When the flag is enabled, the OS no longer falls back to its UIA-based default for handwriting target determination on the host's TSF thread.
Behavior
When msAbydosForWindowlessWV2 is enabled, WebView2 in WindowToVisual mode registers a per-instance ITfHandwritingSink on the host app's TSF thread. This enables shellhandwriting on edit fields inside WebView2, and changes how TSF handwriting events are dispatched on the shared TSF thread.
For HWNDs that WebView2 does not own, the sink returns E_NOTIMPL, allowing TSF to chain through to other registered sinks on the thread.
See Impact for what this means for your app and any required action.
Impact
This change affects WebView2 host apps running in WindowToVisual hosting mode on Windows. Apps in WindowToWindow or VisualToVisual mode are not affected (see Details).
⚠️ Regression risk for apps that do not register a TSF handwriting sink
If your app is in WindowToVisual mode and does not register its own ITfHandwritingSink, pen handwriting on your app's native edit fields may stop working once this change is live.
Cause: WebView2 now registers a per-instance ITfHandwritingSink on the host app's TSF thread. For HWNDs WebView2 does not own, the sink returns E_NOTIMPL, expecting TSF to chain through to other registered sinks. If your app has no sink of its own, there is nothing for TSF to chain to, and the OS does not fall back to its UIA-based default in this case.
Mitigation: Register your own ITfHandwritingSink (see App Action). This also enables pen handwriting inside WebView2 edit fields.
Audience summary
| If your app… |
Behavior change (with flag enabled) |
Action |
Already registers an ITfHandwritingSink on its TSF thread |
Pen handwriting now works inside WebView2 edit fields. Your native edit fields continue to work via your sink — TSF chains to it when WebView2 returns E_NOTIMPL. |
Recommended: smoke-test pen handwriting on your native edit fields with a WebView2 instance loaded, to confirm chaining works as expected in your scenario. |
Does not register an ITfHandwritingSink |
Pen handwriting now works inside WebView2 edit fields, but pen handwriting on your app's native edit fields may stop working (see Regression risk above). |
Required: register an ITfHandwritingSink on your TSF thread. See App Action. |
App Action
If your app does not already register its own ITfHandwritingSink, perform Steps 1 and 2 to preserve pen handwriting on your native edit fields. Pen handwriting inside WebView2 edit fields is enabled automatically by this change — no host-side action required.
Perform only Step 2 if you want to enable handwriting in WebView2 only.
Step 1 — Implement ITfHandwritingSink
Create a COM class (e.g., HostHandwritingSink) that implements ITfHandwritingSink. TSF calls its two methods in sequence for every pen interaction.
Class skeleton (illustrative):
class HostHandwritingSink
: public Microsoft::WRL::RuntimeClass<
Microsoft::WRL::RuntimeClassFlags<Microsoft::WRL::ClassicCom>,
ITfHandwritingSink>
{
public:
HostHandwritingSink() = default;
HRESULT STDMETHODCALLTYPE DetermineProximateHandwritingTarget(
ITfDetermineProximateHandwritingTargetArgs* args) override;
HRESULT STDMETHODCALLTYPE FocusHandwritingTarget(
ITfFocusHandwritingTargetArgs* args) override;
};
1a. DetermineProximateHandwritingTarget
Purpose: TSF gives you a screen point near where the pen is hovering and asks, "Is there a text-input target around here that you own?"
What your implementation should do:
- If HWND matches your host app's HWND, Set response as
TF_HANDWRITING_TARGET_PROXIMATE and return S_OK — you have claimed the target.
- Otherwise, return
E_NOTIMPL so TSF can ask other registered sinks.
Result of returning TF_HANDWRITING_TARGET_PROXIMATE: TSF immediately follows up with FocusHandwritingTarget for the same HWND.
Example (illustrative — not production-ready):
HRESULT STDMETHODCALLTYPE DetermineProximateHandwritingTarget(
ITfDetermineProximateHandwritingTargetArgs* args) override
{
if (!args)
{
return E_NOTIMPL;
}
HWND hwnd = nullptr;
POINT screenPt{};
SIZE distThreshold{};
args->GetPointerTargetInfo(&hwnd, &screenPt, &distThreshold);
// IsTextInputHwnd is your own helper that decides whether the
// candidate HWND is a text-input target your app wants to claim.
// A common approach is to query UI Automation (TextPattern /
// ValuePattern) and filter out read-only / disabled elements;
// restrict the check to your own process to avoid cross-process
// UIA stalls.
if (IsTextInputHwnd(hwnd))
{
args->SetResponse(TF_HANDWRITING_TARGET_PROXIMATE);
return S_OK;
}
return E_NOTIMPL;
}
1b. FocusHandwritingTarget
Purpose: TSF asks you to give keyboard focus to the target you just claimed, so recognized text has a control to land in.
What your implementation should do:
- Retrieve the target HWND and verify that the HWND belongs to your native app.
- Set focus on the HWND.
- SetResponse as
TF_HANDWRITING_TARGET_FOCUSED and return S_OK.
Result of returning TF_HANDWRITING_TARGET_FOCUSED: TSF opens the handwriting panel, captures ink, performs recognition, and injects the recognized text into the now-focused control.
Example (illustrative — not production-ready):
HRESULT STDMETHODCALLTYPE FocusHandwritingTarget(
ITfFocusHandwritingTargetArgs* args) override
{
if (!args)
{
return E_NOTIMPL;
}
HWND hwnd = nullptr;
RECT screenArea{};
SIZE distThreshold{};
HRESULT hr = args->GetPointerTargetInfo(&hwnd, &screenArea, &distThreshold);
if (FAILED(hr))
{
return E_NOTIMPL;
}
// IsTextInputHwnd is your own helper that decides whether the
// candidate HWND is a text-input target your app wants to claim.
// Decline if the HWND is not one of your text-input controls.
if (!IsTextInputHwnd(hwnd))
{
return E_NOTIMPL;
}
SetFocus(hwnd);
// SetFocus can no-op (e.g., HWND on a different thread, or its
// top-level parent isn't active). Consider verifying with
// GetFocus() and returning E_FAIL if focus didn't actually land,
// to avoid lying to TSF about the focused state.
args->SetResponse(TF_HANDWRITING_TARGET_FOCUSED);
return S_OK;
}
Step 2 — Activate TSF and register the sink
Store two variables that hold the sink and the registration cookie:
- A
ComPtr<HostHandwritingSink> (e.g., hostSink).
- A
DWORD cookie initialized to TF_INVALID_COOKIE (e.g., hostSinkCookie).
Then, once during UI initialization (a good moment is when your top-level window or controller is created — COM is already initialized at that point), perform the steps below. Wrap the body in a one-shot helper and early-out if hostSinkCookie != TF_INVALID_COOKIE.
-
Create the TSF thread manager:
Microsoft::WRL::ComPtr<ITfThreadMgr> threadMgr;
HRESULT hr = CoCreateInstance(
CLSID_TF_ThreadMgr, // rclsid
nullptr, // pUnkOuter
CLSCTX_INPROC_SERVER, // dwClsContext
IID_PPV_ARGS(&threadMgr)); // riid, ppv
-
Activate it. Call threadMgr->Activate(&clientId) so TSF recognizes the thread as a TSF client.
-
Enable handwriting. Query ITfHandwriting from the thread manager and call handwriting->SetHandwritingState(TF_HANDWRITING_ENABLED).
-
Instantiate and register the sink. Create a HostHandwritingSink, query ITfSource from the handwriting interface, then call tfSource->AdviseSink(__uuidof(ITfHandwritingSink), sink, &hostSinkCookie).
That is it — TSF will now route pen-handwriting interactions on your app's native text controls through your sink.
Example (illustrative — not production-ready):
// Run once per UI thread during initialization.
Microsoft::WRL::ComPtr<ITfThreadMgr> threadMgr;
HRESULT hr = CoCreateInstance(
CLSID_TF_ThreadMgr, nullptr, CLSCTX_INPROC_SERVER,
IID_PPV_ARGS(&threadMgr));
TfClientId clientId = TF_CLIENTID_NULL;
if (SUCCEEDED(hr))
{
hr = threadMgr->Activate(&clientId);
}
if (SUCCEEDED(hr))
{
Microsoft::WRL::ComPtr<ITfHandwriting> handwriting;
hr = threadMgr.As(&handwriting);
if (SUCCEEDED(hr))
{
// Use TF_HANDWRITING_ENABLED (not TF_HANDWRITING_POINTERDELIVERY)
// so TSF calls DetermineProximateHandwritingTarget per
// interaction. This lets WebView2 opt into pointer delivery for
// its own windows while host text fields keep the buffered flow.
handwriting->SetHandwritingState(TF_HANDWRITING_ENABLED);
auto hostSink = Microsoft::WRL::Make<HostHandwritingSink>();
Microsoft::WRL::ComPtr<ITfSource> tfSource;
DWORD hostSinkCookie = TF_INVALID_COOKIE;
if (SUCCEEDED(handwriting.As(&tfSource)))
{
tfSource->AdviseSink(
__uuidof(ITfHandwritingSink), hostSink.Get(),
&hostSinkCookie);
}
}
}
// On thread teardown, symmetrically call
// tfSource->UnadviseSink(hostSinkCookie) and threadMgr->Deactivate().
Additional Changes
-
Feature flag opt-in: Pass the following when creating the WebView2 environment:
--enable-features=msAbydosForWindowlessWV2
Pass this via the WEBVIEW2_ADDITIONAL_BROWSER_ARGUMENTS environment variable or via CoreWebView2EnvironmentOptions.AdditionalBrowserArguments.
-
Lifetime. Keep the TSF thread manager alive for the lifetime of your UI thread. Do not deactivate it while WebView2 instances are alive.
-
Required headers. The interfaces and constants used above are declared in:
msctf.h — ITfThreadMgr, ITfSource, CLSID_TF_ThreadMgr, TF_CLIENTID_NULL, TF_INVALID_COOKIE.
ShellHandwriting.h — ITfHandwriting, ITfHandwritingSink, ITfDetermineProximateHandwritingTargetArgs, ITfFocusHandwritingTargetArgs, TF_HANDWRITING_ENABLED, TF_HANDWRITING_TARGET_PROXIMATE, TF_HANDWRITING_TARGET_FOCUSED.
Testing
To test proactively, set the feature flag before launching your app:
set WEBVIEW2_ADDITIONAL_BROWSER_ARGUMENTS=--enable-features=msAbydosForWindowlessWV2
Timeline
| Release |
Date |
State |
Key Change |
| 149 |
1st week of June, 2026 |
Off (flag gated) |
msAbydosForWindowlessWV2 disabled by default. Apps can opt in to test. |
| 150 |
1st week of July, 2026 |
Off (flag gated) |
msAbydosForWindowlessWV2 disabled by default. Apps can opt in to test. |
| 151 |
1st week of Aug, 2026 |
On by default |
Apps in WindowToVisual mode will see shell handwriting work in WebView2 edit fields. Apps that have registered an ITfHandwritingSink continue to receive handwriting events on their own text controls via the sink chain. |
Contact
For questions or concerns, reach out to WebView2Feedback repo.
[Breaking Change] Enabling Windows ShellHandwriting Support for WebView2 in WindowToVisual Mode
Summary
WebView2 is introducing support for Windows shellhandwriting (pen handwriting-to-text) in WindowToVisual hosting mode, gated behind the feature flag
msAbydosForWindowlessWV2.Details
Scope:
Purpose: Enable Windows shell handwriting on edit fields inside WebView2 instances hosted in WindowToVisual mode.
To enable correct handwriting routing, WebView2 now registers a per-instance
ITfHandwritingSink(as a TSF secondary client) for every WebView2 instance. This is a behavioral change in how WebView2 interacts with the Text Services Framework on the host app's UI thread. When the flag is enabled, the OS no longer falls back to its UIA-based default for handwriting target determination on the host's TSF thread.Behavior
When
msAbydosForWindowlessWV2is enabled, WebView2 in WindowToVisual mode registers a per-instanceITfHandwritingSinkon the host app's TSF thread. This enables shellhandwriting on edit fields inside WebView2, and changes how TSF handwriting events are dispatched on the shared TSF thread.For HWNDs that WebView2 does not own, the sink returns
E_NOTIMPL, allowing TSF to chain through to other registered sinks on the thread.See Impact for what this means for your app and any required action.
Impact
This change affects WebView2 host apps running in WindowToVisual hosting mode on Windows. Apps in WindowToWindow or VisualToVisual mode are not affected (see Details).
If your app is in WindowToVisual mode and does not register its own
ITfHandwritingSink, pen handwriting on your app's native edit fields may stop working once this change is live.Cause: WebView2 now registers a per-instance
ITfHandwritingSinkon the host app's TSF thread. For HWNDs WebView2 does not own, the sink returnsE_NOTIMPL, expecting TSF to chain through to other registered sinks. If your app has no sink of its own, there is nothing for TSF to chain to, and the OS does not fall back to its UIA-based default in this case.Mitigation: Register your own
ITfHandwritingSink(see App Action). This also enables pen handwriting inside WebView2 edit fields.Audience summary
ITfHandwritingSinkon its TSF threadE_NOTIMPL.ITfHandwritingSinkITfHandwritingSinkon your TSF thread. See App Action.App Action
If your app does not already register its own
ITfHandwritingSink, perform Steps 1 and 2 to preserve pen handwriting on your native edit fields. Pen handwriting inside WebView2 edit fields is enabled automatically by this change — no host-side action required.Perform only Step 2 if you want to enable handwriting in WebView2 only.
Step 1 — Implement
ITfHandwritingSinkCreate a COM class (e.g.,
HostHandwritingSink) that implementsITfHandwritingSink. TSF calls its two methods in sequence for every pen interaction.Class skeleton (illustrative):
1a.
DetermineProximateHandwritingTargetPurpose: TSF gives you a screen point near where the pen is hovering and asks, "Is there a text-input target around here that you own?"
What your implementation should do:
TF_HANDWRITING_TARGET_PROXIMATEand returnS_OK— you have claimed the target.E_NOTIMPLso TSF can ask other registered sinks.Result of returning
TF_HANDWRITING_TARGET_PROXIMATE: TSF immediately follows up withFocusHandwritingTargetfor the same HWND.Example (illustrative — not production-ready):
1b.
FocusHandwritingTargetPurpose: TSF asks you to give keyboard focus to the target you just claimed, so recognized text has a control to land in.
What your implementation should do:
TF_HANDWRITING_TARGET_FOCUSEDand returnS_OK.Result of returning
TF_HANDWRITING_TARGET_FOCUSED: TSF opens the handwriting panel, captures ink, performs recognition, and injects the recognized text into the now-focused control.Example (illustrative — not production-ready):
Step 2 — Activate TSF and register the sink
Store two variables that hold the sink and the registration cookie:
ComPtr<HostHandwritingSink>(e.g.,hostSink).DWORDcookie initialized toTF_INVALID_COOKIE(e.g.,hostSinkCookie).Then, once during UI initialization (a good moment is when your top-level window or controller is created — COM is already initialized at that point), perform the steps below. Wrap the body in a one-shot helper and early-out if
hostSinkCookie != TF_INVALID_COOKIE.Create the TSF thread manager:
Activate it. Call
threadMgr->Activate(&clientId)so TSF recognizes the thread as a TSF client.Enable handwriting. Query
ITfHandwritingfrom the thread manager and callhandwriting->SetHandwritingState(TF_HANDWRITING_ENABLED).Instantiate and register the sink. Create a
HostHandwritingSink, queryITfSourcefrom the handwriting interface, then calltfSource->AdviseSink(__uuidof(ITfHandwritingSink), sink, &hostSinkCookie).That is it — TSF will now route pen-handwriting interactions on your app's native text controls through your sink.
Example (illustrative — not production-ready):
Additional Changes
Feature flag opt-in: Pass the following when creating the WebView2 environment:
Pass this via the
WEBVIEW2_ADDITIONAL_BROWSER_ARGUMENTSenvironment variable or viaCoreWebView2EnvironmentOptions.AdditionalBrowserArguments.Lifetime. Keep the TSF thread manager alive for the lifetime of your UI thread. Do not deactivate it while WebView2 instances are alive.
Required headers. The interfaces and constants used above are declared in:
msctf.h—ITfThreadMgr,ITfSource,CLSID_TF_ThreadMgr,TF_CLIENTID_NULL,TF_INVALID_COOKIE.ShellHandwriting.h—ITfHandwriting,ITfHandwritingSink,ITfDetermineProximateHandwritingTargetArgs,ITfFocusHandwritingTargetArgs,TF_HANDWRITING_ENABLED,TF_HANDWRITING_TARGET_PROXIMATE,TF_HANDWRITING_TARGET_FOCUSED.Testing
To test proactively, set the feature flag before launching your app:
Timeline
msAbydosForWindowlessWV2disabled by default. Apps can opt in to test.msAbydosForWindowlessWV2disabled by default. Apps can opt in to test.ITfHandwritingSinkcontinue to receive handwriting events on their own text controls via the sink chain.Contact
For questions or concerns, reach out to WebView2Feedback repo.