[BUG] Chrome extension (v1.0.81) missing two CDP setup steps: Emulation.setFocusEmulationEnabled never called + Page.handleJavaScriptDialog only handles beforeunload

Status Open
Maintainer reply None cached
Activity 0 comments · opened Jul 25, 2026

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:

  1. Emulation.setFocusEmulationEnabled is never called → click/type/focus events behave incorrectly when the target tab is not the foreground tab, causing browser_batch sequences (click → type → submit) to fail silently.
  2. Page.handleJavaScriptDialog is only wired for beforeunload → native alert(), confirm(), and prompt() dialogs are never dismissed, blocking the renderer main thread and causing subsequent Runtime.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):

  1. Open two tabs; call any mcp__claude-in-chrome__* action on the non-frontmost one that depends on document.hasFocus() (a login form, a rich-text editor, an OAuth consent screen).
  2. Click/type events fire on the DOM element but the app's focus-guarded handlers don't run → the flow silently no-ops.
  3. Bring the tab to the front and repeat → succeeds.

Repro 2 (confirm freeze):

  1. Navigate to any page with a button that calls window.confirm("…") (or trigger a native delete-confirm on an admin panel).
  2. Call mcp__claude-in-chrome__left_click on that button.
  3. The dialog opens; the extension does not dismiss it.
  4. 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_batch calls 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-href already 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

View original on GitHub ↗