Skip to content

JCEF doesn't expose CefExecuteJavaScriptWithUserGestureForTests, blocking real onBeforePopup test coverage #11

Description

@Thrameos

Summary

CefLifeSpanHandler.onBeforePopup (native/life_span_handler.cpp) has no test
coverage, and can't easily get any: a script-initiated window.open() call is
blocked by Chromium's own popup blocker (no user gesture) before it ever reaches
OnBeforePopup at all, so a straightforward JUnit test (create a page whose script
calls window.open(), assert the handler fires) fails with the handler simply never
invoked -- not a JCEF bug, just an untestable-as-written scenario.

CEF's own C++ API has a purpose-built helper for exactly this:

// include/test/cef_test_helpers.h
void CefExecuteJavaScriptWithUserGestureForTests(CefRefPtr<CefFrame> frame,
                                                  const CefString& javascript);

(also declared in include/capi/test/cef_test_helpers_capi.h for the C API). This
lets test code execute JS as if it had a real user gesture attached, which is
exactly what's needed to make a window.open() call actually reach
OnBeforePopup/onBeforePopup instead of being silently blocked upstream.

JCEF does not currently bind this anywhere (grep -rn "UserGestureForTests" java/ native -- no results). Exposing it would need: a new native _N.cpp/.h pair (or
a method added to an existing one, e.g. CefFrame_N), the matching Java method
(probably on CefFrame, gated to test/debug use given the name), and JNI header
regeneration per root CLAUDE.md's native-side instructions.

Impact

Low urgency -- this blocks test coverage for one handler method, not any
production functionality. Filed as part of the coverage-expansion push tracked in
#5. Once available, a real BeforePopupTest (attempted and reverted this session --
see plan/roadmap.md/findings.md) becomes straightforward: call the new binding to
trigger window.open() with a synthetic user gesture, assert onBeforePopup fires
and (when it returns true) that the popup is actually blocked.

Repro / how this was found

Wrote BeforePopupTest.java with a page whose script called window.open()
directly (no user gesture) and asserted onBeforePopup fires. It reliably failed:
onBeforePopup was never invoked ==> expected: <true> but was: <false>. Confirmed
via CEF's own header comments (cef_life_span_handler.h's OnBeforePopup doc:
"...whether the popup was opened via explicit user gesture...") and the existence
of the above test-helper API that this is expected/by-design Chromium behavior, not
a bug in the test or in JCEF's existing bridge code.

Activity

  1. added 3 commits that reference this issue on Aug 29, 2026
  2. Thrameos commented on Aug 30, 2026

    @Thrameos
    OwnerAuthor

    Root-caused and fixed. Not a JCEF/CEF bug, not fundamentally a "no real user gesture" limitation -- found by reading CEF's own internal test suite (`tests/ceftests/dialog_unittest.cc`) rather than guessing further.

    CEF's own maintainers don't simulate a page-side `` click at all in their automated tests. Their `DialogTest.FileOpen`/`FileOpenCancel`/etc. (which run on Linux too, not just Windows) call `CefBrowserHost::RunFileDialog()` directly -- the C++ equivalent of `CefBrowser.runFileDialog()`, which this fork's Java API already exposes and which is not actually Windows-only despite `plan/windows-todo.md`'s earlier assumption. It routes through `CefDialogHandler::OnFileDialog` the same way a real click would.

    Two prior JCEF attempts both used the wrong trigger:

    1. A JS-synthesized `.click()` on the file input -- never fired `onFileDialog` at all (Chromium deliberately blocks script-triggered file-picker opens).
    2. A real synthetic AWT `MouseEvent` (same technique that fixed onBeforeContextMenu never fires for a synthetic OSR right-click (root cause not yet isolated) #17's context menu) -- fired `onFileDialog`, but then needed a hard `SIGKILL` past 45s.

    Switching to `browser.runFileDialog(...)` (CEF's own test technique) fixes it cleanly: `onFileDialog` fires, `callback.Cancel()` suppresses the real dialog before it's ever shown (exactly CEF's own test's pattern), and `CefRunFileDialogCallback.onFileDialogDismissed()` fires too. Confirmed safe across 3 repeated isolated runs -- no hang, no SIGKILL. Full suite: 167/167 passing.

  3. added a commit that references this issue on Aug 30, 2026
  4. Thrameos commented on Aug 31, 2026

    @Thrameos
    OwnerAuthor

    Independently re-confirmed today (2026-08-30, separate session) via a fresh
    test (CefLifeSpanPopupTest, @Disabled, commit cd504b4) before finding this
    issue already existed. Went a bit further diagnostically: added a
    document.title change inside the onclick handler (observed via
    CefDisplayHandler.onTitleChange) to distinguish "click never landed" from
    "click landed but window.open() got blocked" -- confirmed the click does
    land and the onclick handler does run (title change observed in the log),
    so it's specifically the window.open() -> OnBeforePopup dispatch that
    never happens, exactly matching this issue's diagnosis. Also tried a real
    synthetic mouse click (same canvas.dispatchEvent(MOUSE_PRESSED/RELEASED/ CLICKED) technique CefContextMenuTest already uses successfully for
    right-click) rather than just a bare inline <script> call, in case a real
    user-gesture-shaped input event would satisfy the popup blocker on its own --
    it didn't, consistent with this issue's conclusion that
    CefExecuteJavaScriptWithUserGestureForTests (or an equivalent Java binding)
    is genuinely needed, not just a "trigger from a synthetic gesture" workaround.

  5. Thrameos commented on Sep 4, 2026

    @Thrameos
    OwnerAuthor

    Fixed -- and the root cause turned out to be different from what this issue was originally filed against. The CefExecuteJavaScriptWithUserGestureForTests binding itself (native/CefTestHelper.cpp, java/tests/junittests/CefTestHelper.java) was real, working infrastructure, but it was never actually the blocker: native/life_span_handler.cpp's OnBeforePopup unconditionally cancels+returns before ever reaching Java when IsWindowRenderingDisabled() (i.e. for every OSR browser), regardless of what triggers the window.open() call underneath it.

    With #3 (windowed close hang) now fixed, switched CefLifeSpanPopupTest to a windowed browser and it passes cleanly: onBeforePopup fires with the correct target URL and frame name and cancels the popup as asserted. The user-gesture binding is kept in place as real, CEF-verified infrastructure that may still be useful for other user-activation-gated coverage (e.g. onbeforeunload dialogs) even though it wasn't the fix here.

    Details: plan/tasks/20260903-06-issue11-user-gesture-test-helper.md (archived).

    🤖 Generated with Claude Code

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