Repository navigation
Offer: GitHub Actions CI (build/test/coverage) + 9 bugs found and fixed along the way -- no obligation, cherry-pick as you like #542
Description
Activity
Follow-up: more bugs found and fixed, plus 3 missing handler bindings
Continuing to work through test coverage on the same fork. No action needed here either -- everything below is available to merge as-is, hand off to a volunteer, or cherry-pick individually. Each item is its own small, independently-reviewable PR on
Thrameos/java-cef, linked below -- check the PR itself for current status (open, merged, or closed after merge).Bugs found and fixed
- Modified-UTF-8 (CESU-8) JNI string corruption.
GetJNIString()/NewJNIString()used JNI's modified-UTF-8 string functions but treated the result as standard UTF-8, silently corrupting supplementary-plane characters (emoji, etc.) and embedded NULs crossing the JNI boundary in either direction. Thrameos/java-cef#1 ColorType(int,int,int,int)sign-extends on high alpha. The packing constructor computed ARGB in 32-bitintarithmetic then relied on implicit widening tolong; an alpha >= 0x80 set the sign bit, corrupting the upper 32 bits ofcolor_value. Thrameos/java-cef#36- OSR mouse wheel scroll direction is inverted.
N_SendMouseWheelEventpassed AWT's wheel-rotation sign straight through; AWT's convention is the opposite ofCefMouseEvent's. Thrameos/java-cef#37 CefQueryCallback.success()ignorespersistent=true. A persistent query is documented to allow repeatedsuccess()calls, but the native ref was unconditionally cleared after the first call regardless ofpersistent. Thrameos/java-cef#38DCHECK(handle)crash inCefMessageRouter.cancelPending(null, null). A null router-handler argument (documented as valid) still reached a constructor thatDCHECKs non-null. Thrameos/java-cef#39GetCefValueFromJNIMapdrops all data. Built a populatedCefDictionaryValuebut returned a brand-new emptyCefValueinstead of attaching it -- everyMappassed toCefRequestContext.setPreference()silently lost its contents. Thrameos/java-cef#40setWindowVisibility()is a no-op for OSR/windowless browsers. Gated onOS_MACOSXand windowed mode only; should callCefBrowserHost::WasHidden()for windowless browsers instead. Found by comparing against a sibling fork (DanielTM999/java-cef, commit6bd0cca1). Thrameos/java-cef#41- Two more double-release causes in
CefClient.cleanupBrowser()(ConcurrentModificationException-prone iteration + re-entrant handler removal) andRenderHandler::GetJNIScreenInfo()(doubleDeleteLocalRef) -- follow-up to issue Mac: Right-click context menu does not display in OSR mode #22. Thrameos/java-cef#42 - Malformed JNI input reaches CEF's internal
CHECKs.N_SetToBytesdidn't validate a negative/oversizedsizebefore an unsigned cast;N_SetHeaderByName/N_SetURLguarded null but not empty strings, and empty still tripsCHECK(!x.empty())(aborts Debug builds, silently wrong in Release). Thrameos/java-cef#43, plus the remaining call sites of the same empty-string gap in Thrameos/java-cef#49 CefDownloadHandler.onBeforeDownloadreturningfalsecan crash the process. Under Chrome-style runtime,falsefalls through to Chrome's own download-shelf default handling, which this embedding never implemented; if the browser is torn down before that deferred handling runs, CEF'sChooseDownloadPathcrashes on an already-gone callback. Fixed by normalizingfalseto a safe cancel (drop the callback un-run) at the JNI boundary instead. Thrameos/java-cef#48
Missing API surface added
CefDevToolsMessageObserverwas missingonDevToolsAgentAttached/onDevToolsAgentDetached, present on the real C++ interface. Thrameos/java-cef#50- No binding for
CefFindHandler(in-page find result callbacks) at all. Thrameos/java-cef#51 - No binding for
CefFrameHandler(frame lifecycle: created/destroyed/attached/detached, main-frame-changed) at all. Thrameos/java-cef#52 - No binding for
CefPermissionHandler(media access / permission-prompt requests) at all -- previously no way for a JCEF app to grant camera/mic/geolocation access; Alloy style silently denied everything. Thrameos/java-cef#53
As before: substantial AI assistance (Claude, Anthropic) under my direction and review, disclosed up front.
- Modified-UTF-8 (CESU-8) JNI string corruption.
If you submit your changes as separate PRs (one PR per bug fix or enhancement, following the rules), they can be reviewed and potentially merged.
Do you have a preference on order?
The coverage one is unfortunately massive because to add unittesting I had to fix the 10 bugs at once (too many segfaults to split them out safely and clear the CI). My original intent was a CI only PR which had no API changes at all and any problematic test was disabled. This issue being that the race conditions means that CI result is like a box of chocolates so many fixes had to be backported or it would never clear. The good news being almost all of it is in junit directory except the fixes. If you get that one in then all the others get much easier to deal with as you will get a green light from the CI not just on my PR but on anyone's linux PR that runs cleanly under xvfb and icevm. If it isn't covered with unittests, you can easily reject it and a working unittest makes is much easier to see if bug was an ordering problem with API usage.
Alternatively, I can try to submit the 10 as single issue PR without the CI first, then the CI which with be no core changes (still huge but no API changes at once). But there is no guarantee the intermediate product works properly as testing each combination is rather costly.
The other 14 are all small two to three file alterations that can be easily reviewed. But same issue applies that they are built on the 10 segfault fixes. Each has an individual unittest that clears the CI.
I am not sure flooding you with 25 PRs at once would be very helpful. If you can specify your preferred method (and order) I can start the PR next weekend.
Individual bug fixes and (non-test-support) enhancements should be proposed as separate PRs, with associated test/regression coverage. If that test/regression coverage depends on new test infrastructure, that infrastructure can be proposed first as a separate PR. The primary point being: don't mix unrelated concerns in a single PR. It's fine if that ends up being 25 (properly scoped) PRs.
The offer, up front
I didn't see a
.github/workflows/setup here, so while building coverage toolingon a personal fork I put together a GitHub Actions CI setup (Linux/macOS/Windows
builds, JUnit tests, JaCoCo+LLVM coverage, CodeQL) in case it's useful. Along the
way it surfaced 9 real bugs, all fixed, verified against a live CI run with all
checks green and a processed Codecov report.
No action is required from anyone here. Everything below is in one squashed,
CI-validated commit at Thrameos#35, with the full, unsquashed commit-by-
commit history preserved at the
backport/phase1-ci-infra-full-historytag in thesame fork. Whatever's useful is available: merge the PR as-is if you want the whole
thing, hand it to a volunteer to review at whatever pace works, or cherry-pick
whichever individual fixes are wanted straight out of the full-history tag and
ignore the rest. Just flagging what's there.
Enhancements
coverage job, and CodeQL analysis (C++ and Java).
ENABLE_LLVM_COVERAGE: a Clang source-based native coverage CMake option.gcov-style instrumentation was tried and rejected during development --
its
.gcdawriter isn't fork-safe against CEF's zygote-style processmodel, and silently produces wrong (usually near-zero) numbers for
whichever files a test happens to hit a fork on -- so it was never carried
into the branch's shipped configuration.
support -- 0.8.12 can't parse it).
tools/run_tests_ci.sh/tools/run_coverage_ci.sh) that tellsa real, reportable JUnit test failure apart from a known, already-diagnosed
native crash: a real failure is never silently retried, while a known crash
signature is retried within a bounded budget, or accepted as passing once
it's been verified that the crash doesn't lose any test or coverage data.
The point is to avoid two opposite failure modes -- a flaky job that's
ignored because it's "always red," and a job that looks green by quietly
swallowing real failures.
LeakChecker,LeakTargets,IsolatedRunner) for RSS-growth-based leak sweeps, plus roughly 25 tests portedfrom CEF's own
ceftestssuite and other coverage work (value objects, browser/context/request/response/download-item coverage, OSR/windowed smoke tests, drag-
data, cookies, message router, print/PDF settings, etc.).
Bugs found and fixed
CefMessageRouterleak on router removal.RemoveMessageRouter()nevercalled
CancelPending()on outstanding persistent-query callbacks, which holda
CefRefPtrback to the router. If a browser is removed mid-query rather thanactually closing, nothing else ever releases that reference -- the router, and
transitively the browser context it keeps alive, leaks for the process's life.
This looks like the same issue Preserve persistent query callbacks #541 and Support persistent & binary queries/callbacks #528 are addressing.
CefRequestContextdouble-caching leak.getGlobalContextNative()wasmissing a final
else, so every call after the first cached (and leaked) asecond native reference.
CefRequestContext/CefCookieManagernever disposed at shutdown.The cached global instance outlived
CefShutdown(), trippingDCHECK(all_.empty())inbrowser_context.cc's exit-time leak detector(Debug builds only -- invisible in Release, since that check is compiled out
there).
CefClient.onGotFocus()synchronous infinite recursion.setFocus(true)synchronously re-fires
OnWebContentsFocused->onGotFocus()again for thesame browser; CEF's own
OnSetFocus()guards itself against this kind ofreentrancy but
OnGotFocus/OnWebContentsFocusedhas no equivalent guard. Analready-focused browser recurses until the thread's stack overflows.
ScopedJNIObject<T>mixed locked/unlocked accessors. A real SIGSEGV duringordinary handler-removal teardown, plus a related check-then-create race in
GetOrCreateCefObject().OnBeforeClosefor a browserwhose renderer already died (e.g. after a deliberate
chrome://crash);windowed mode had no fallback for the equivalent case either. Both now get a
bounded fallback instead of hanging indefinitely.
doMessageLoopWork's self-perpetuating Timer outliving native shutdown.An independently-scheduled Timer tick (up to ~33ms out) can still fire after
N_Shutdown()has already destroyed the nativeContextsingleton --harmless in Release, a real SIGSEGV in Debug builds where the resulting null-
thisDCHECK actually runs.CefClientHandlerhandler-removal methods used the abstract interface typeinstead of the concrete wrapper type. Follow-up to the
ScopedJNIObjectcrash above.
several
_N.cppsetters (CefRequest/CefResponse/CefPostDataElementandothers), and a CodeQL medium-severity finding (a world-readable temp file on
POSIX).
Public API notes
Two new
public staticmethods were added, both purely additive -- no existingpublic signature was changed, removed, or had its contract broken:
CefRequestContext.disposeGlobalContext()CefCookieManager.disposeGlobalManager()Both are documented as internal-use-only (called from
CefApp's shutdown sequenceto release the cached global instance before native CEF shutdown runs), but are
callable by any embedding app since they're public. Nothing else in the public
org.cefsurface changed.Credits
This work was done with substantial AI assistance (Claude, Anthropic) under my
direction and review -- disclosing that up front since it materially shaped how
this was produced.
Bug #5 above (
ScopedJNIObject<T>mixed locked/unlocked accessors) was found bydiffing this project's teardown code against JetBrains/jcef (branch 261,
CEF 137), which independently added a locking mechanism around the same native-
pointer accessor call sites with a comment describing the identical crash
signature we hit. The fix here is adapted from that fork's approach.
Known limitation
The underlying
browser_context.cc:44DCHECK(all_.empty())shutdown-time leakdetector is not fully fixed by #2/#3 above -- it still fires via at least one
further mechanism, confirmed independent of reference counting (ADDREF/RELEASE and
JNI ref counts both balance perfectly when it fires). The coverage CI job
recognizes this specific, well-understood signature and tolerates it rather than
treating it as a fresh failure, since it only ever fires after every test in a run
has already completed. Root-causing it further is still open.