Surface the context menu Chromium already builds (OSR) - #34
Merged
Conversation
Right-click did nothing in an OSR tile. Not because the menu was missing — Chromium builds a complete, correctly-stateful one for every right-click (back/forward/reload, cut/copy/paste with the right items greyed out, view-source, copy-link-address, the whole spellcheck block with live dictionary suggestions) — but because no CefContextMenuHandler was implemented, so CEF constructed it and threw it away. We cannot let CEF display it: the default path is a native menu parented to a window, and a windowless browser has none. So RunContextMenu takes over display instead. Serialise the model Chromium built (labels, command ids, enabled / checked, separators, one level of submenu) plus the hit context (coords, link url, media source, selection, misspelled word) and hand it to the host to draw; send the chosen command id back so CHROMIUM executes it. Every command's behaviour and enabled state stays authoritative rather than reimplemented. The invariant is answer-exactly-once: CEF requires the menu callback be continued or cancelled, and a dropped answer wedges the page's menu handling so later right-clicks are silently ignored. Every path therefore answers — chosen, dismissed, no handler, throwing handler, malformed json — with 0 meaning dismiss. OnContextMenuDismissed drops pending entries because CEF has already invalidated the callback by then. 5 tests cover those paths. Protocol 6 -> 7 (kOpContextMenu 0x20 up, kOpContextMenuCommand 0x3e down); both sides bumped together, since a mismatch is a hard processGone(protocolMismatch). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Demonstrates the seam end to end: Chromium's model arrives, a plain Material menu draws it at the click point, the chosen id goes back. Deliberately not styled — it shows the wiring, not a design. Chromium's enabled state is honored so Paste greys out with an empty clipboard without the host deriving anything. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Three follow-ups from driving the context menu in a real app. DevTools inspect point. showDevTools now takes an optional page-DIP point and passes it to ShowDevTools(inspect_element_at), so a host can open DevTools already inspecting the right-clicked element — what "Inspect" means in a browser. Empty payload still means "just open", so the existing call is unchanged. DevTools is a REAL window even though the page is windowless, which is why this works at all where view-source does not. view-source is judged by what it wraps. OnBeforeBrowse refused `view-source:` outright (not in the allowlist, not `about:`), so Chromium's own View Page Source silently did nothing — no error, no log. Viewing the source of a page you were already allowed to LOAD grants no new reach: it renders bytes as text and executes nothing. So `view-source:X` is allowed iff X's own scheme is allowed. Verified: `view-source:https://…` renders, `view-source:file:///etc/hosts` is still refused. Chromium rejects nested `view-source:view-source:` upstream, so one unwrap is the whole story. (The context-menu ROW for view-source is still dropped host-side: CEF's default handler for it wants a new tab, and a windowless browser has no tab strip. The scheme working is what lets an agent — or a future "open source in a new tile" — reach it.) Opcode collision. kOpContextMenu was 0x20, which kOpNavigate already uses. The two travel in opposite directions so dispatch happened to work, but it was a trap for the next reader; moved to 0x40. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Right-click did nothing in an OSR tile. Not because the menu was missing — Chromium builds a complete, correctly-stateful one for every right-click (back/forward/reload, cut/copy/paste with the right items greyed out, view-source, copy-link-address, the whole spellcheck block with live dictionary suggestions) — but because no
CefContextMenuHandlerwas implemented, so CEF constructed it and threw it away.Approach
We can't let CEF display it: the default path is a native menu parented to a window, and a windowless browser has none. So
RunContextMenutakes over display only:enabled/checked, separators, one level of submenu — plus hit context (coords, link url, media source, selection, misspelled word).That split matters: every command's behaviour and its enabled/checked state stays authoritative instead of being reimplemented. Paste greys out with an empty clipboard because Chromium said so, not because we guessed.
The invariant
Answer exactly once. CEF requires the menu callback be continued or cancelled; a dropped answer wedges the page's menu handling so later right-clicks are silently ignored. Every path answers, with
0= dismiss:FlutterError, then 0OnContextMenuDismisseddrops pending entries, because CEF has already invalidated the callback by then and answering later would be a use-after-free.Protocol
6 → 7.
kOpContextMenu(0x20) up,kOpContextMenuCommand(0x3e) down. Both sides bumped in the same commit — a mismatch is a hardprocessGone(protocolMismatch), so they can never land separately.Verification
flutter analyze— clean (1 pre-existing unrelated info).flutter test— 178 pass (173 before + 5 new covering each answer-exactly-once path).Not yet verified: an actual interactive right-click producing the menu. That needs a real click in a frontmost window — an agent shell can't do it reliably (OSR reports false freezes when not frontmost). The example app is built and running for exactly that check.
Note for consumers
main.mmchanged, so the content hash moved:make publish-cef-hostis required for the new hash before any consumer canmake pin-cefto this commit.🤖 Generated with Claude Code