ionic-team / ionic-team/capacitor

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

Open
#8,562 0 comments 0 reactions 0 assignees View on GitHub
triage
Dominant language
TypeScript
Stars
16.7k
Forks
1.3k
Avg merge
3d 15h
Merged PRs (30d)
10

Description

### 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:

```java
// android/capacitor/src/main/java/com/getcapacitor/cordova/MockCordovaWebViewImpl.java#L187-L190
public void eval(final String js, final ValueCallback 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.

Contributor guide

Open the contributing guide

Research direction

Start with android/capacitor/src/main/java/com/getcapacitor/cordova/MockCordovaWebViewImpl.java, especially eval(), and trace teardown through BridgeActivity. Reproduce the issue with the provided font-scale change and filtered adb logcat, then verify that a configuration recreate no longer produces an attempt to evaluate JavaScript on a destroyed WebView.

Written by the indexing model from the issue text.

Assessment

Tech stack
android, java
Domain
mobile-dev
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Quiet
Clarity
Mostly clear
Newbie friendliness
58/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.