Skip to content

CefClient.onGotFocus() recurses synchronously with CEF's own focus-change notification until the stack overflows #31

Description

@Thrameos

Summary

CefClient.onGotFocus() unconditionally called browser.setFocus(true). CEF's OnSetFocus() callback guards itself against reentrant SetFocus() calls (is_in_onsetfocus_ in CefBrowserContentsDelegate::OnSetFocus()), but OnGotFocus/OnWebContentsFocused has no equivalent guard -- so an already-focused browser recurses onGotFocus() <-> native setFocus(true) synchronously (no async task-post anywhere in the cycle) until the calling thread's stack overflows.

Exact chain:

onGotFocus(browser)
  -> browser.setFocus(true)
  -> native SetFocus() -> OnSetFocus(FOCUS_SOURCE_SYSTEM)
  -> JCEF's onSetFocus handler returns false (no user handler registered)
  -> platform_delegate_->SetFocus(true)
  -> synchronously re-fires OnWebContentsFocused -> FocusHandler::OnGotFocus (native) -> Java onGotFocus(browser) again
  -> recurse

Real-world symptom

Manifests as a bare, repeated Exception in thread "AWT-EventQueue-0" with no visible exception class or stack trace (the default handler's own printStackTrace() re-triggers the same overflow while the stack is still exhausted) -- seen across multiple unrelated investigations this session as an apparently-inexplicable exception storm, and confirmed (via hs_err_pid*.log PID matching) to be the actual cause of #27's "several Exception in thread AWT-EventQueue-0 with no visible stack trace" preceding that issue's mojo DCHECK crash.

Reproduction

Confirmed via live jstack polling of an isolated CefMessageRouterTest run: a clean, 769-frame-deep, two-function cycle (N_SetFocus <-> CefClient.onGotFocus), entirely on the EDT/UI thread.

Not a recent regression -- git log --follow shows onGotFocus()'s body was never functionally touched since it was originally written (only whole-file reformats). Verified via a real build at cf73c06 (the commit that introduced CefNativeAdapter's lock mechanism, mined from a sibling fork) that this reproduces there too, and at that commit crashes the process outright (SIGSEGV, libc.so.6+0x91651) 3/3 runs, worse than current HEAD (survives with the storm, no crash, in isolation).

Fix

java/org/cef/CefClient.java's onGotFocus() now guards with the class's own pre-existing focusedBrowser_ tracking (already set in onGotFocus(), cleared in onTakeFocus(), just never used to prevent the redundant call): only calls browser.setFocus(true) on an actual focus transition (focusedBrowser_ != browser). The one real setFocus(true) call per genuine focus gain still happens; focusHandler_.onGotFocus() (the user-visible notification) still fires on every call, unaffected.

Fixed in commit c1e570a (branch coverage/phase1-value-objects-phase2-handlers). Verified: isolated repro clean (was crashing/storming every run, now 7/8 clean, the one remaining timeout is an unrelated bare-startup hang with zero focus-related output); full-suite Exception in thread count 0/3 (was present in every prior run).

Impact

Was silently corrupting/crashing test runs and (per the CEF focus-notification pattern being entirely stock/default) any real embedding application that lets a JCEF browser receive OS-level focus at all, whenever CEF's OnWebContentsFocused fires for an already-focused browser.

Activity

  1. Thrameos commented on Sep 1, 2026

    @Thrameos
    OwnerAuthor

    Already fixed in commit c1e570a (see issue body). Closing.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions