Repository navigation
fix(android): handle each back action once - #2029
GitToTheHub wants to merge 2 commits into
Conversation
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## master #2029 +/- ##
=======================================
Coverage 63.20% 63.20%
=======================================
Files 24 24
Lines 4922 4922
=======================================
Hits 3111 3111
Misses 1811 1811 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
| Intent intent = new Intent(); | ||
| intent.putExtra("startUrl", "https://localhost/backbuttondispatch/index.html"); | ||
| activity = activityRule.launchActivity(intent); | ||
| webView = (WebView) activity.getWebInterface().getView(); |
There was a problem hiding this comment.
This test retrieves the existing Cordova WebView; it does not enable content access or change any WebView security settings. The finding exposes the existing WebView configuration through the new test. Any production change to content:// access should be considered in a separate PR with compatibility coverage, so this back-button fix remains focused.
| activity.getWebInterface().showCustomView(new View(activity), | ||
| () -> customViewHidden.set(true)); |
There was a problem hiding this comment.
The regression test deliberately exercises the existing showCustomView API to verify that a Back action dismisses the custom view once without also firing a JavaScript backbutton event. The API is deprecated, but the behavior is still supported and needs coverage. Custom-view API modernization can be handled in a separate PR.
| private String evaluate(String script) throws Throwable { | ||
| ArrayBlockingQueue<String> result = new ArrayBlockingQueue<>(1); | ||
| activityRule.runOnUiThread(() -> webView.evaluateJavascript(script, result::add)); | ||
| String value = result.poll(10, TimeUnit.SECONDS); |
There was a problem hiding this comment.
This appears to be a false positive. value is read immediately after this assignment by assertNotNull("JavaScript evaluation timed out", value) and then returned with return value;. Keeping the local variable allows the test to report a clear timeout failure before returning the JavaScript result.
|
Tested this branch (cordova-android 15.1.1-dev.0) with the repro app https://github.com/2zovd/cordova-backbutton-repro on an Android 16 (API 36) emulator, Pixel 9, WebView 153.
With 15.1.0 the same app gets 2 events per Back key press, 0.4 ms apart. I did not see such pairs with this branch. Thanks for the quick fix! |
|
Thanks for the reply and tests :) |
|
If no one has any objections, I would merge this. |
Document the two ways a back press reaches Cordova (back-press callback and back key events) in CordovaWebView, CordovaWebViewImpl and CoreAndroid, and add comments to BackButtonDispatchTest. Make the history navigation test wait for the page load to finish after going back instead of evaluating JavaScript during the navigation, which could drop the result callback. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
|
I documented the back button behaviour in cordova-android. If no one has any objections I would merge this the next days. |
| private boolean performBackButtonAction() { | ||
| // 1. A custom view (for example a full-screen video) is showing: close it. | ||
| if (mCustomView != null) { | ||
| hideCustomView(); |
There was a problem hiding this comment.
This call already existed in the Back key handler and has been moved into the shared back-action helper. Preserving custom-view dismissal is necessary so closing a full-screen view does not also fire backbutton and navigate the app. Modernizing the deprecated custom-view API should be handled separately; this PR preserves the existing behavior.

Platforms affected
Android
Motivation and Context
On Android 16, one Back key press can invoke the AndroidX back callback and then deliver the original key to the WebView. Cordova's synthetic key redispatch therefore fires
backbuttontwice, causing applications to navigate back two screens. Edge swipes fire once.Fixes #2028.
Description
A back press can reach Cordova in two ways:
CoreAndroidregisters anOnBackPressedCallbackwhile the back button is overridden, that is while a JavaScriptbackbuttonlistener exists.onDispatchKeyEvent). This also works without abackbuttonlistener.Depending on the Android version, one press arrives through one or both ways. On Android 16 it can arrive through both, which caused the duplicate.
Replace synthetic DOWN/UP keys with a shared back-action handler. Callbacks and back key releases use the same action order: dismiss a custom view, notify a JavaScript
backbuttonlistener, or navigate WebView history. Unhandled callback actions delegate to Android with the callback temporarily disabled to avoid recursion.Keep a single callback timestamp in the WebView to consume an original key already handled by the callback. Clear it when the JavaScript override is removed. This avoids a debounce interval and preserves rapid consecutive inputs.
The new
CordovaWebView.handleBackButton()interface method is called byCoreAndroidfrom the back-press callback.Testing
backbuttonlistener produces one event per real Back key press on Android 12 to 17 (checked withinput keyevent), and per edge swipe on Android 16 and 17. Consecutive key presses about 21–23 ms apart were checked on Android 15 and 16. How a press arrives differs by version: Android 12 to 15 deliver key events only, Android 16 delivers the callback just before the key-up (which is ignored), Android 17 delivers the callback only.backbuttonlistener, Back behaves the same on Android 16 and 17 as before this PR. Checked by comparing with the code before the PR, using real Back key presses.npm run lint, all 335npm run unit-testsspecs, andgit diff --checkpass.The existing instrumentation project does not build unmodified: Kotlin stdlib versions conflict,
androidx.test:rulesis missing, andBackButtonMultipageTesthas an unhandledInterruptedException. For the targeted run, a temporary Gradle init script aligned Kotlin jdk7/jdk8 to 1.8.22, addedandroidx.test:rules:1.1.1, and limited instrumentation compilation toBackButtonDispatchTest. The APK build excluded the unrelated:CordovaLib:extractDebugAnnotationstask. Those infrastructure workarounds are not part of this PR. The full instrumentation suite was not run. The six tests were not run on Android 12 to 14.On Android 11 and older, the test app crashes at startup (
InflateExceptionin the splash screen layout), because its theme lackssplashScreenIconSize. This is unrelated to this PR. With that attribute temporarily added to the test theme (not part of this PR):exec()calls, such asoverrideBackbutton, are dropped as "from previous page load". A separate test that only waits for the back override to arrive failed in 2 to 23 of 60 page loads with the code from before this PR as well, so the PR does not cause it.Checklist
(platform)if this change only applies to one platform