Dispatch MRAID JS bridge calls to the main thread - #1012
Open
mdanylov-sigma wants to merge 2 commits into
Open
mdanylov-sigma wants to merge 2 commits into
mdanylov-sigma wants to merge 2 commits into
Conversation
WebView calls JavaScript interfaces on its JavaBridge background thread, but BaseJSInterface works with views and state owned by the main thread (the crash in #735 was one symptom). Register MainThreadJSInterface as "jsBridge" instead: it posts MRAID commands to the main thread and waits up to 250 ms for getters, returning the existing error value when the main thread does not answer in time. This replaces the unbounded main-thread wait that #736 added to getCurrentPosition. Commands that throw are logged and still release the mraid.js command queue, and calls that reach the main thread after destroy() are ignored. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
BaseJSInterface reused one MraidEvent for every command, but MraidController reads it later in posted runnables and completion callbacks, and two-part expand stores it for InterstitialManager. A later command could overwrite the action or URL before that code read it. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
mdanylov-sigma
force-pushed
the
claude/prebid-mobile-android-739-4a76d5
branch
from
September 15, 2026 19:13
a313487 to
747807e
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #739
Problem
WebView calls every
@JavascriptInterfacemethod on its backgroundJavaBridgethread, butBaseJSInterfacewas registered directly and works with views and state owned by the main thread. #736 fixed the crash from #735 by makinggetCurrentPositionwait for the main thread, with no timeout. The other bridge methods still ran off the main thread, for example:resize()added a screen-metrics request toScreenMetricsWaiter's unsynchronizedLinkedListwhile the main thread was removing from it. If that request is lost,nativeCallComplete()never reaches mraid.js, and every later command,closeincluded, stays queued.javaScriptCallbackran itsHandleron the bridge thread. That is only safe today because every caller passesFetchPropertiesHandler, which re-posts to the main thread.MraidEventthat main-thread code read later.Changes
1.
MainThreadJSInterfaceis registered asjsBridgefor banners and interstitialsopen,close,expand,resize, …) are posted to the main thread. mraid.js already waits fornativeCallComplete()between commands, so it doesn't rely on these calls returning synchronously.{}, or-1forgetLocation) if it doesn't answer in time.getPlacementTypeandsupportsdon't read shared state, so they answer on the calling thread.nativeCallComplete()so the ad's command queue keeps moving.BaseJSInterface.destroy()are ignored.getCurrentPositionis removed, since the method now always runs on the main thread.jsBridgename are unchanged.2. A new
MraidEventfor each command (separate commit)BaseJSInterfacereused oneMraidEventfor every command.MraidControllerreads it later, in posted runnables and completion callbacks, and two-part expand stores it forInterstitialManager. A later command could overwrite the action or URL before that code read it. Each command now creates its own event.Tests
MainThreadJSInterfaceTest: getters from the main and a background thread, the timeout fallback, command order, commands that throw, calls after destroy, and a reflection check that every@JavascriptInterfacemethod ofBaseJSInterfaceis exposed on the bridge.WebViewBannerTestandWebViewInterstitialTest: the registeredjsBridgeis aMainThreadJSInterface.BaseJSInterfaceTest: event checks capture the event passed toMraidControllerinstead of reading the removed field; new checks that consecutive commands get separate events and thatdestroy()sets the flag../gradlew :PrebidMobile-core:testReleaseUnitTestpasses locally: 1,313 tests, no failures (JDK 21).Questions for reviewers
{}?🤖 Generated with Claude Code