Skip to content

[Bug]: eval() runs on a destroyed WebView after activity recreate #8562

Description

@rekinet

Capacitor Version

@capacitor/cli: 8.5.0
@capacitor/core: 8.5.0
@capacitor/android: 8.5.0
@capacitor/app: 8.1.1

Other API Details

Android 14 (API 34), ASUS AI2201, System WebView chromium-SystemWebViewGoogle6432.aab-stable-787118103.

Platforms Affected

Android

Current Behavior

When the activity is recreated (any configuration change not listed in android:configChanges), AppPlugin fires pause and appStateChange immediately before the WebView is destroyed. Those events go through MockCordovaWebViewImpl.eval(), which posts to the main looper without checking whether the WebView is still alive:

// android/capacitor/src/main/java/com/getcapacitor/cordova/MockCordovaWebViewImpl.java#L187-L190
public void eval(final String js, final ValueCallback<String> callback) {
    Handler mainHandler = new Handler(context.getMainLooper());
    mainHandler.post(() -> webView.evaluateJavascript(js, callback));
}

The posted runnable is executed after the WebView has been destroyed, so evaluateJavascript runs on a dead instance. Timestamps from a single recreate, showing the runnable outliving its WebView by 190 ms:

14:56:49.324  V Capacitor/AppPlugin: Notifying listeners for event pause
14:56:49.339  V Capacitor/AppPlugin: Notifying listeners for event appStateChange
14:56:49.398  D Capacitor: App destroyed
14:56:49.497  D Capacitor: Starting BridgeActivity
14:56:49.529  D Capacitor: App started
14:56:49.588  W cr_AwContents: Application attempted to call on a destroyed WebView

Stack trace:

W cr_AwContents: Application attempted to call on a destroyed WebView
W cr_AwContents: java.lang.Throwable
W cr_AwContents: 	at org.chromium.android_webview.AwContents.p(...)
W cr_AwContents: 	at com.android.webview.chromium.WebViewChromium.evaluateJavaScript(...)
W cr_AwContents: 	at android.webkit.WebView.evaluateJavascript(WebView.java:893)
W cr_AwContents: 	at com.getcapacitor.cordova.MockCordovaWebViewImpl.lambda$eval$0(MockCordovaWebViewImpl.java:189)
W cr_AwContents: 	at com.getcapacitor.cordova.MockCordovaWebViewImpl$$ExternalSyntheticLambda1.run(...)
W cr_AwContents: 	at android.os.Handler.handleCallback(Handler.java:958)
W cr_AwContents: 	at android.os.Looper.loop(Looper.java:364)
W cr_AwContents: 	at android.app.ActivityThread.main(ActivityThread.java:8439)

In this particular case the consequence is benign — the dropped events target the JS context that is being torn down anyway, and the fresh context receives its own appStateChange right after App resumed. The reason for reporting it is the pattern rather than this instance: any plugin that calls notifyListeners around teardown hits the same window, and there the lost event may matter. The warning also adds noise that makes real WebView lifecycle problems harder to spot in logcat.

Expected Behavior

eval() should not call into a WebView that has been destroyed. Either skip the call when the bridge/activity is no longer alive, or have BridgeActivity clear pending evals during teardown, so that events queued before destruction do not execute afterwards.

Project Reproduction

I do not have a standalone reproduction repository, but the issue reproduces on a stock npx cap add android project with no plugins beyond @capacitor/app, because the events involved are fired by Capacitor itself.

Steps:

  1. Create a default Capacitor Android project and run it on a device with Android 14.
  2. Keep the default android:configChanges generated by Capacitor — note that it does not include fontScale, which makes a font size change a convenient trigger for a genuine activity recreate.
  3. Start adb logcat filtered on Capacitor and cr_AwContents.
  4. With the app in the foreground, change the system font size (Settings → Display → Font size), or run adb shell settings put system font_scale 1.15.
  5. Observe App destroyed followed by Starting BridgeActivity, and then the Application attempted to call on a destroyed WebView warning.

The same happens on any other recreate path (for example a configuration change outside configChanges, or the activity being destroyed in the background under memory pressure and then restored).

Additional Information

The code path is unchanged on main at the time of writing, so this is not specific to the 8.5.0 release.

The trigger used above is not artificial for our use case: the app is an accessibility-first podcast player for blind and partially sighted users, and changing the system font size is one of the first things our users do. Playback runs in a foreground media service that survives the recreate, so the WebView restarts while audio keeps playing — which is how we noticed the warning in the first place.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions