-
Notifications
You must be signed in to change notification settings - Fork 63
API Review: Custom context menu Spellcheck #5553
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from 6 commits
417e474
aab5c7a
db3dacb
c7d73db
d2397e0
4615e97
a170b51
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,172 @@ | ||||||||||||||||||||||||||
| Custom Context Menu SpellCheck | ||||||||||||||||||||||||||
| === | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| # Background | ||||||||||||||||||||||||||
| When a host application renders a custom context menu via the `ContextMenuRequested` event, spellcheck | ||||||||||||||||||||||||||
| suggestions for misspelled words are not available. The browser's built-in spellcheck pipeline resolves | ||||||||||||||||||||||||||
| suggestions asynchronously, but there is no mechanism for custom context menu hosts to retrieve or apply | ||||||||||||||||||||||||||
| these suggestions. | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| # Description | ||||||||||||||||||||||||||
| We propose extending the existing `ContextMenuRequested` API surface with spellcheck support for custom | ||||||||||||||||||||||||||
| context menus. This adds the ability to: | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| 1. Asynchronously retrieve spellcheck suggestions via a one-shot completion handler. | ||||||||||||||||||||||||||
| 2. Apply a selected spellcheck suggestion to replace the misspelled word in the DOM. | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| The design uses a purely asynchronous approach: the host always calls `GetSpellCheckSuggestionsAsync` | ||||||||||||||||||||||||||
| which fires the handler exactly once either immediately (if suggestions are already resolved) or | ||||||||||||||||||||||||||
| when they become available. There is no synchronous readiness query. | ||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| Hosts opt in by calling `QueryInterface` for the new `ICoreWebView2ContextMenuRequestedEventArgs2` | ||||||||||||||||||||||||||
| interface. Existing `ContextMenuRequested` consumers are unaffected. | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| # Examples | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| ## Win32 C++ | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| ```cpp | ||||||||||||||||||||||||||
| // Inside the ContextMenuRequested handler for an editable target: | ||||||||||||||||||||||||||
| args->put_Handled(TRUE); | ||||||||||||||||||||||||||
| wil::com_ptr<ICoreWebView2Deferral> deferral; | ||||||||||||||||||||||||||
| args->GetDeferral(&deferral); | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| HMENU hMenu = CreatePopupMenu(); | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| auto args2 = | ||||||||||||||||||||||||||
| wil::try_com_query<ICoreWebView2ContextMenuRequestedEventArgs2>(args); | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| if (args2) | ||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||
| // Show placeholder while suggestions load. | ||||||||||||||||||||||||||
| AppendMenu(hMenu, MF_GRAYED | MF_STRING, IDM_SUGGESTION_BASE, | ||||||||||||||||||||||||||
| L"Loading suggestions..."); | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| // Handler fires immediately if resolved, or when ready. | ||||||||||||||||||||||||||
| args2->GetSpellCheckSuggestionsAsync( | ||||||||||||||||||||||||||
| Callback<ICoreWebView2GetSpellCheckSuggestionsCompletedHandler>( | ||||||||||||||||||||||||||
| [hMenu, args2](HRESULT errorCode, | ||||||||||||||||||||||||||
| ICoreWebView2StringCollection* suggestions) | ||||||||||||||||||||||||||
| -> HRESULT | ||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||
| if (FAILED(errorCode) || !suggestions) | ||||||||||||||||||||||||||
| return S_OK; | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| UINT32 count = 0; | ||||||||||||||||||||||||||
| suggestions->get_Count(&count); | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| // Replace placeholder and add suggestion items. | ||||||||||||||||||||||||||
| for (UINT32 i = 0; i < count && i < 5; i++) | ||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||
| LPWSTR word = nullptr; | ||||||||||||||||||||||||||
| suggestions->GetValueAtIndex(i, &word); | ||||||||||||||||||||||||||
| // ... update menu items with word ... | ||||||||||||||||||||||||||
| CoTaskMemFree(word); | ||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||
| return S_OK; | ||||||||||||||||||||||||||
| }) | ||||||||||||||||||||||||||
| .Get()); | ||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| // ... add other menu items, show popup with TrackPopupMenu ... | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| // When the user picks a suggestion: | ||||||||||||||||||||||||||
| LPWSTR chosenSuggestion = /* label from selected menu item */; | ||||||||||||||||||||||||||
| args2->ApplySpellCheckSuggestion(chosenSuggestion); | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| deferral->Complete(); | ||||||||||||||||||||||||||
| ``` | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| ## .NET/WinRT | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| ```csharp | ||||||||||||||||||||||||||
| // Inside the ContextMenuRequested handler for an editable target: | ||||||||||||||||||||||||||
| args.Handled = true; | ||||||||||||||||||||||||||
| var deferral = args.GetDeferral(); | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| var contextMenu = new ContextMenuStrip(); | ||||||||||||||||||||||||||
| var placeholder = new ToolStripMenuItem("Loading...") { Enabled = false }; | ||||||||||||||||||||||||||
| contextMenu.Items.Add(placeholder); | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| // Handler fires immediately if resolved, or when ready. | ||||||||||||||||||||||||||
| args.GetSpellCheckSuggestionsAsync().Completed = (op, status) => | ||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||
| if (status != AsyncStatus.Completed) return; | ||||||||||||||||||||||||||
| var suggestions = op.GetResults(); | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| contextMenu.Invoke(() => | ||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||
| contextMenu.Items.Remove(placeholder); | ||||||||||||||||||||||||||
| foreach (string s in suggestions.Take(5)) | ||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||
| var item = new ToolStripMenuItem(s); | ||||||||||||||||||||||||||
| item.Click += (_, _) => args.ApplySpellCheckSuggestion(s); | ||||||||||||||||||||||||||
| contextMenu.Items.Insert(0, item); | ||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| // ... add other menu items, show contextMenu ... | ||||||||||||||||||||||||||
| deferral.Complete(); | ||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
| deferral.Complete(); | |
| contextMenu.Closed += (_, _) => deferral.Complete(); |
Outdated
Copilot
AI
Apr 2, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The COM API states that only one handler can be registered at a time and subsequent calls replace the prior handler. This differs from other WebView2 async-with-handler patterns (which typically allow multiple concurrent calls and invoke all handlers) and also doesn’t map cleanly to the WinRT projection where each call returns a distinct IAsyncOperation. Consider aligning with existing WebView2 semantics (e.g., multiple calls before completion all complete together; after completion, complete immediately) or clearly defining cancellation/overwriting behavior in both COM and WinRT surfaces.
| /// the handler is invoked immediately and synchronously. | |
| /// The handler receives `S_OK` and the suggestions collection on success, | |
| /// or an error HRESULT and `nullptr` on failure. | |
| /// Only one handler can be registered at a time; calling this method again | |
| /// replaces any previously registered handler. | |
| /// the handler is invoked immediately and synchronously with the cached | |
| /// suggestions. | |
| /// The handler receives `S_OK` and the suggestions collection on success, | |
| /// or an error HRESULT and `nullptr` on failure. | |
| /// Multiple calls to this method are allowed. All handlers registered before | |
| /// suggestions are resolved are invoked when results become available, and | |
| /// calls made after resolution complete immediately using the cached results. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In the description, there’s an extra space in “exactly once either” which reads like a typo. Please change to a single space (and consider adding a comma after “once” for readability).