[BUG] Chrome extension (v1.0.81) missing two CDP setup steps: Emulation.setFocusEmulationEnabled never called + Page.handleJavaScriptDialog only handles beforeunload
Title
[BUG] Chrome extension (v1.0.81) missing two CDP setup steps: Emulation.setFocusEmulationEnabled never called + Page.handleJavaScriptDialog only handles beforeunload
Body
Summary
Static analysis of the shipped Chrome extension bundle (fcoeoabgfenejglbffodgkkbkcdhcgfn v1.0.81, "Claude") reveals two missing CDP setup steps that cause two large classes of observable failures across mcp__claude-in-chrome__* tool calls:
Emulation.setFocusEmulationEnabledis never called → click/type/focus events behave incorrectly when the target tab is not the foreground tab, causingbrowser_batchsequences (click → type → submit) to fail silently.Page.handleJavaScriptDialogis only wired forbeforeunload→ nativealert(),confirm(), andprompt()dialogs are never dismissed, blocking the renderer main thread and causing subsequentRuntime.evaluate(and every other tool call on the tab) to hang indefinitely. Result: the tab becomes an unhealthy zombie.
Both are fixable in the extension in ~5 lines of code and would eliminate the majority of repeat failures I see in day-to-day Claude Code sessions.
Evidence (from the shipped bundle)
Extension path: ~/Library/Application Support/Google/Chrome/Default/Extensions/fcoeoabgfenejglbffodgkkbkcdhcgfn/1.0.81_0/
Finding 1: setFocusEmulationEnabled is never called
$ rg -c "setFocusEmulationEnabled" ~/Library/Application\ Support/Google/Chrome/Default/Extensions/fcoeoabgfenejglbffodgkkbkcdhcgfn/1.0.81_0/
(no output — zero hits across all 237 files)
Meanwhile the extension does call Input.dispatchMouseEvent directly (in assets/mcpPermissions-DsrFI4Sf.js):
async dispatchMouseEvent(e,t){
...
await this.sendCommand(e,"Input.dispatchMouseEvent",n)
}
Without Emulation.setFocusEmulationEnabled({enabled: true}) on attach, document.hasFocus() returns false whenever the target tab isn't the frontmost one, and standard focusin/click handler chains break. Playwright sets this by default on every browser context, which is why raw Playwright doesn't hit this failure mode.
Finding 2: Page.handleJavaScriptDialog is only wired for beforeunload
Same bundle (assets/mcpPermissions-DsrFI4Sf.js):
if("Page.javascriptDialogOpening"===r){
const t=n?.type;
if("beforeunload"===t){
const t="accept"===(e.beforeunloadPolicyByTab.get(i)??"dismiss");
...
chrome.debugger.sendCommand({tabId:i},"Page.handleJavaScriptDialog",{accept:t},...)
}
// No branch for "alert", "confirm", or "prompt" — event is dropped
}
Page.enable is called on attach (verified: Page.enable appears in the bundle), so the javascriptDialogOpening event does fire — it's just discarded when type !== "beforeunload". That leaves the renderer's synchronous confirm() blocking forever, and every follow-up CDP command that touches the renderer (Runtime.evaluate, DOM.*, etc.) pends until the tab is manually closed.
Reproduction
Repro 1 (silent click failure, related: browser_batch reliability):
- Open two tabs; call any
mcp__claude-in-chrome__*action on the non-frontmost one that depends ondocument.hasFocus()(a login form, a rich-text editor, an OAuth consent screen). - Click/type events fire on the DOM element but the app's focus-guarded handlers don't run → the flow silently no-ops.
- Bring the tab to the front and repeat → succeeds.
Repro 2 (confirm freeze):
- Navigate to any page with a button that calls
window.confirm("…")(or trigger a native delete-confirm on an admin panel). - Call
mcp__claude-in-chrome__left_clickon that button. - The dialog opens; the extension does not dismiss it.
- Every subsequent tool call on that tab (
read_page,computer, etc.) hangs / returns "tab not responding". The tab is effectively lost until closed.
Suggested fix (sketch)
In the CDP command layer of the extension (same file as above):
// (1) After attach + Page.enable, also enable focus emulation:
await this.sendCommandOnce(t, "Page.enable")
await this.sendCommandOnce(t, "Emulation.setFocusEmulationEnabled", { enabled: true })
// (2) Handle non-beforeunload dialogs (policy configurable per tab, default accept):
if ("Page.javascriptDialogOpening" === r) {
const type = n?.type
if (type === "beforeunload") { /* existing branch */ }
else if (type === "alert" || type === "confirm" || type === "prompt") {
chrome.debugger.sendCommand(
{ tabId: i },
"Page.handleJavaScriptDialog",
{ accept: this.dialogPolicyByTab.get(i) ?? true }
)
}
}
A small extension of the existing beforeunloadPolicyByTab API — e.g. setDialogPolicy(tabId, {beforeunload, alert, confirm, prompt}) — would let callers keep the safer "dismiss by default" behavior on high-stakes pages while defaulting to auto-accept in normal automation.
Impact
Across ~90 days of my own sessions (grepped from local Claude Code transcripts):
- ~155 sessions with a
TimeoutError/ navigation-timeout that traces back to a hung tab (Finding 2 as a plausible cause). - ~30 sessions with
browser_batchcalls where the click landed but the follow-up type/submit didn't (Finding 1). - Local memory entries
chrome-mcp-silent-click-no-focus-use-form-input,chrome-native-confirm-freezes,chrome-mcp-cookie-blocked-use-dom-hrefalready documented as workarounds — all three would become unnecessary if these two CDP setup steps landed upstream.
Environment
- macOS 15.6 (Darwin 24.6.0)
- Chrome (stable channel)
- Claude Code CLI (current)
- Claude Chrome extension v1.0.81 (extension ID
fcoeoabgfenejglbffodgkkbkcdhcgfn)
Notes
Happy to open a PR if the extension source is public somewhere I don't know about — the bundled JS is unminified enough to patch, but obviously that's not the source of truth. Also happy to run a live repro against a test page if that helps triage.
Labels: bug, area:browser-extension, area:chrome, platform:macos