Skip to content

SystemBars: passthrough IME resize (viewport-fit=cover) ignores View.setPadding()/rewritten insets on some devices #8622

Description

@GuilhermeAbreu

Description

On at least one real device (details below), SystemBars's "passthrough" keyboard-resize path (shouldPassthroughInsets = webViewMajorVersion >= 140 && hasViewportCover, in SystemBars.java) resizes the WebView by a value that's consistently ~130px short of the keyboard's real on-screen top edge, leaving a flat unpainted strip between the page content and the keyboard.

More importantly: this isn't fixable from app code today. I traced it all the way through and confirmed that in passthrough mode, View.setPadding() on the DecorView (what SystemBars.applyInsets() calls) has zero effect on the WebView's actual rendered size — window.innerHeight never changes no matter what padding value is applied. I also tried rewriting the ime() portion of the WindowInsetsCompat returned from the OnApplyWindowInsetsListener/WindowInsetsAnimationCompat.Callback — also zero effect. Chromium appears to resize the WebView through a separate internal channel that isn't influenced by anything SystemBars (or any app code reachable via public Capacitor/AndroidX APIs) returns or sets.

Environment

  • @capacitor/core / @capacitor/android: 8.5.2 (also reproduced on 8.5.0, before upgrading in an attempt to pick up fix: resolve issues with safe area / systembars plugin #8535)
  • Device: Xiaomi (model M2101K9AG), Android 13, MIUI
  • Android System WebView: 152.0.7977.87 (well past the WEBVIEW_VERSION_WITH_SAFE_AREA_KEYBOARD_FIX = 144 gate in SystemBars.java, so the code assumes this version reports IME insets correctly — on this device it doesn't, for the resize, even though the reported inset value itself is actually correct — see below)
  • Keyboard.resizeOnFullScreen: not set (omitted per the plugin's own warning)
  • insetsHandling: default (css)

Steps to reproduce

  1. Any Capacitor Android app with the default viewport meta tag (viewport-fit=cover) and an <input>/focusable field.
  2. Focus the field to open the soft keyboard.
  3. Compare where the page content visually ends vs. where the keyboard visually starts.

Expected

The WebView content area should end exactly where the keyboard begins (or vice versa when it closes).

Actual

A ~130px gap (physical pixels, this device) appears between the content and the keyboard, filled with a flat, unpainted color — not the page background, not the keyboard's own background, a third "nothing was drawn here yet" color.

Root-cause investigation (with numbers)

I have real-device measurements, not guesses:

  • adb shell dumpsys window windows → the IME window's real touchable region: SkRegion((0,1569,1080,2270)) — keyboard top edge is physically at y=1569.
  • The WebView's own reported frame (via chrome://inspect / CDP json/list, "screenY":104,"height":1335) → WebView ends at y=1439 (104+1335).
  • Gap: 130px, matching this device's navigation-bar height almost exactly (navBarHeight: 130 also shows up in unrelated Gboard logcat lines on this device, for what that's worth — possibly coincidental, possibly not).
  • I patched SystemBars.java (via pnpm patch) to log both the raw insets.getInsets(WindowInsetsCompat.Type.ime()).bottom from the callback parameter and a fresh ViewCompat.getRootWindowInsets(v).getInsets(...) re-query. Once the keyboard is fully settled, both report the correct value (831px, which is exactly 2400-1569). So the inset math this plugin does is not wrong — Android is handing it the right number.
  • Despite that correct number being computed and passed to v.setPadding(0,0,0,831), and despite rewriting the ime() type of the returned WindowInsetsCompat to carry that same corrected Insets, window.innerHeight (checked live via CDP Runtime.evaluate) stayed at the exact same wrong value throughout — the same one it had before any of these patches, with resizeOnFullScreen/safe-area conflicts/etc. all ruled out first.
  • The one thing that did change the WebView's actual resize amount: stripping viewport-fit=cover from the meta viewport tag, which flips shouldPassthroughInsets to false and moves SystemBars into the other branch (v.setPadding(systemBarsInsets.left, top, right, keyboardVisible ? imeInsets.bottom : systemBarsInsets.bottom), explicit Insets.of(0,0,0,0) for systemBars/cutout, env(safe-area-inset-*) no longer populated, custom-property injection via injectSafeAreaCSS becomes the only source of truth). In that branch, the manual setPadding() call does control the WebView's visible size correctly.

So: in passthrough mode, the resize is entirely internal to Chromium/WebView and not influenceable through WindowInsetsCompat/View.setPadding() from application/plugin code — at least not through any API surface I could find. Whatever Chromium's own internal handling is doing for viewport-fit=cover + IME on this WebView build, it's arriving at a different (and wrong, on this device) number than what WindowInsetsCompat.Type.ime() reports to the app.

What I'd ask for

  • If this is a known Chromium bug with an upstream tracking issue, a pointer would be very helpful — I couldn't find one specific to this "reported inset ≠ actual resize" mismatch (as opposed to the reported-inset-value-itself-being-wrong bugs like 40699457/457682720 already referenced in this file).
  • A config escape hatch to force the non-passthrough (manual setPadding) branch on Android, without losing the env(safe-area-inset-*)/native-CSS-env behavior for status bar and cutout (i.e., decoupling the IME-resize strategy from the systemBars/cutout strategy, since today shouldPassthroughInsets controls both together) would let apps work around this per-device without having to fully drop viewport-fit=cover (and re-plumb every env(safe-area-inset-*) usage to var(--safe-area-inset-*) instead, which is what I ended up doing).
  • At minimum, documenting that View.setPadding()/returned-inset-rewriting has no effect in passthrough mode would save the next person a few hours of patching and measuring to (re)discover this.

Happy to share the pnpm patch diffs I used for the logging/measurement if useful, and the exact CDP script I used to watch window.innerHeight live if that helps someone else's investigation.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

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