ionic-team / ionic-team/capacitor

[Bug]: SystemBars shrinks the WebView twice for the keyboard on Android 10 (API 29) when WebView >= 140 and viewport-fit=cover

Open Beginner friendly
#8,601 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/android 8.5.1 (the affected code is unchanged in 8.5.2 and on `main`:
android/capacitor/src/main/java/com/getcapacitor/plugin/SystemBars.java, `initWindowInsetsListener`)
@capacitor/core 8.5.1
@capacitor/keyboard 8.0.5
```

### Other API Details

```
Node 22.23.2, npm 10
Android: compileSdk/targetSdk 36, minSdk 26, AGP 8.13.0
Affected device: Zebra TC21, Android 10 (API 29), Android System WebView >= 140 (Play-updated)
Not affected: DT50 Android 9 (WebView provider is Chrome 138, the last for Android 9), DT50 Android 13 (WebView 151), Pixel Android 16/17
Page uses
capacitor.config.json: "SystemBars": { "style": "DARK" } (insetsHandling default "css"), no Keyboard config
```

### Platforms Affected

- [ ] iOS
- [x] Android
- [ ] Web

### Current Behavior

On **Android 10 (API 29)** with a current WebView (>= 140) and a page that declares `viewport-fit=cover`, opening the soft keyboard on any text field shrinks the WebView by the keyboard height **twice**. On a phone-sized screen the entire WebView disappears and only the window background is visible for as long as the keyboard is up (see the customer video description below: the login page turns into a solid colour when the username field is tapped, and comes back when the keyboard closes). Typing still goes into the invisible field.

Root cause, as far as I can tell from `SystemBars.initWindowInsetsListener`:

```java
boolean shouldPassthroughInsets = getWebViewMajorVersion() >= WEBVIEW_VERSION_WITH_SAFE_AREA_FIX && hasViewportCover;
...
if (shouldPassthroughInsets) {
// We need to correct for a possible shown IME
v.setPadding(0, 0, 0, keyboardVisible ? imeInsets.bottom : 0);
```

* On API < 30 the activity uses `adjustResize`, so the framework has **already** resized the window for the IME by the time this listener runs.
* On API < 30 `WindowInsetsCompat.Type.ime()` is the AndroidX compat approximation (system window inset minus stable inset), which still reports the full keyboard height even though the window no longer overlaps the IME.
* The passthrough branch then pads the WebView's parent by that height again. Net effect: WebView height = screen - 2 x keyboard.

On API 30+ `ime()` is the real overlap, which is 0 once the window has been resized, so the same line is harmless there. On WebView < 140 the passthrough branch is never taken, so the bug only appears once a device's WebView updates past 140, i.e. it is showing up in the field now on Android 10 devices with Play services, while Android 9 devices (capped at Chrome/WebView 138) never hit it.

### Expected Behavior

The WebView keeps the framework-resized height when the keyboard opens on Android 10, exactly as it does on Android 9 (non-passthrough branch) and Android 11+.

Fix that works for us (shipped as a patch-package patch):

```diff
if (shouldPassthroughInsets) {
- // We need to correct for a possible shown IME
- v.setPadding(0, 0, 0, keyboardVisible ? imeInsets.bottom : 0);
+ // On API < 30 the window is already resized for the IME (adjustResize) and the
+ // compat ime() inset still reports the keyboard height, so padding again shrinks
+ // the WebView twice. Only pad on API 30+, where ime() is the real overlap.
+ boolean padForIme = keyboardVisible && Build.VERSION.SDK_INT >= Build.VERSION_CODES.R;
+ v.setPadding(0, 0, 0, padForIme ? imeInsets.bottom : 0);
```

Happy to open a PR with this if you agree with the approach.

### Project Reproduction

No public repo yet (I can put one together if needed). Steps, ~10 minutes with an emulator:

1. `npm init @capacitor/app@latest`, add Android, keep the default `viewport-fit=cover` meta. Add a text `` to the page; make `` a colour different from the window background so the effect is obvious.
2. Emulator: `system-images;android-29;default;arm64-v8a` (Android 10, ships WebView 74). Because that WebView is < 140, temporarily set `WEBVIEW_VERSION_WITH_SAFE_AREA_FIX = 0` in `node_modules/@capacitor/android/.../SystemBars.java` to take the passthrough branch (that is what a real Android 10 device with WebView >= 140 does).
3. Run, tap the input. The WebView loses **twice** the keyboard height: on a 1080x2280 emulator ~26% of the page is left above a blank band the size of the keyboard; on a 720x1280 device (Zebra TC21) nothing is left.
4. Apply the diff above: the WebView keeps the framework-resized height and the input stays visible.
5. Same build on an Android 16 emulator and on Android 9/13 devices: unchanged.

### Additional Information

* Also transpiling for old WebViews and the `WEBVIEW_VERSION_WITH_SAFE_AREA_KEYBOARD_FIX` path are unrelated; the issue is purely the API < 30 double shrink.
* Field impact: rugged Android 10 handhelds (Zebra TC21/TC26 etc.) are common in logistics; as their WebView updates past 140, every Capacitor 8.5 app with `viewport-fit=cover` will start losing its page whenever the keyboard opens.

Contributor guide

Open the contributing guide

Research direction

Start in android/capacitor/src/main/java/com/getcapacitor/plugin/SystemBars.java, at initWindowInsetsListener and the shouldPassthroughInsets branch. Reproduce with the Android 10 emulator setup described, using viewport-fit=cover and a text input. Done means the WebView keeps the framework-resized height while the keyboard is open on API 29, with Android 30+ and Android 9/13 behavior unchanged.

Written by the indexing model from the issue text.

Assessment

Tech stack
android, java
Domain
mobile
Issue type
Bug
Difficulty
2/5
Estimated time
1-3 hours
Activity status
Active
Clarity
Clearly specified
Newbie friendliness
78/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.