From 00a9e00c58492084ed718b8785c577dafaf338ac Mon Sep 17 00:00:00 2001 From: Piotr Andreassen Blasiak <2700143+piotrblasiak@users.noreply.github.com> Date: Mon, 21 Sep 2026 21:06:26 +0200 Subject: [PATCH] fix(SystemBars): respect windowSoftInputMode="adjustNothing" Detect `adjustNothing` from the window's soft input mode and treat the keyboard as not there: no IME padding, and the IME insets handed to the WebView are set to 0. The latter is needed because WebView >= 139 shrinks its visual viewport for the IME insets it receives, which makes the page pannable above the keyboard. Unlike Chrome, WebView ignores `VirtualKeyboard.overlaysContent` and `interactive-widget=overlays-content`, so there is no web-side opt-out. --- .../com/getcapacitor/plugin/SystemBars.java | 39 +++++++++++++++---- 1 file changed, 31 insertions(+), 8 deletions(-) diff --git a/android/capacitor/src/main/java/com/getcapacitor/plugin/SystemBars.java b/android/capacitor/src/main/java/com/getcapacitor/plugin/SystemBars.java index 8b9de8849..9d2400362 100644 --- a/android/capacitor/src/main/java/com/getcapacitor/plugin/SystemBars.java +++ b/android/capacitor/src/main/java/com/getcapacitor/plugin/SystemBars.java @@ -7,6 +7,7 @@ import android.util.TypedValue; import android.view.View; import android.view.Window; +import android.view.WindowManager; import android.webkit.WebView; import androidx.core.graphics.Insets; import androidx.core.view.ViewCompat; @@ -195,14 +196,18 @@ private void initWindowInsetsListener() { Insets systemBarsInsets = insets.getInsets(WindowInsetsCompat.Type.systemBars() | WindowInsetsCompat.Type.displayCutout()); Insets imeInsets = insets.getInsets(WindowInsetsCompat.Type.ime()); - boolean keyboardVisible = insets.isVisible(WindowInsetsCompat.Type.ime()); + // With `adjustNothing` the keyboard overlays the WebView, so it is handled as if it is not there + boolean keyboardOverlaysWebView = + (getActivity().getWindow().getAttributes().softInputMode & WindowManager.LayoutParams.SOFT_INPUT_MASK_ADJUST) == + WindowManager.LayoutParams.SOFT_INPUT_ADJUST_NOTHING; + boolean keyboardVisible = !keyboardOverlaysWebView && insets.isVisible(WindowInsetsCompat.Type.ime()); if (shouldPassthroughInsets) { // We need to correct for a possible shown IME v.setPadding(0, 0, 0, keyboardVisible ? imeInsets.bottom : 0); - WindowInsetsCompat newInsets = new WindowInsetsCompat.Builder(insets) - .setInsets( + WindowInsetsCompat newInsets = withoutOverlayingKeyboard( + new WindowInsetsCompat.Builder(insets).setInsets( WindowInsetsCompat.Type.systemBars() | WindowInsetsCompat.Type.displayCutout(), Insets.of( systemBarsInsets.left, @@ -210,8 +215,9 @@ private void initWindowInsetsListener() { systemBarsInsets.right, getBottomInset(systemBarsInsets, keyboardVisible) ) - ) - .build(); + ), + keyboardOverlaysWebView + ).build(); injectSafeAreaCSS(newInsets); @@ -229,9 +235,13 @@ private void initWindowInsetsListener() { // Returning `WindowInsetsCompat.CONSUMED` breaks recalculation of safe area insets // So we have to explicitly set insets to `0` // See: https://issues.chromium.org/issues/461332423 - WindowInsetsCompat newInsets = new WindowInsetsCompat.Builder(insets) - .setInsets(WindowInsetsCompat.Type.systemBars() | WindowInsetsCompat.Type.displayCutout(), Insets.of(0, 0, 0, 0)) - .build(); + WindowInsetsCompat newInsets = withoutOverlayingKeyboard( + new WindowInsetsCompat.Builder(insets).setInsets( + WindowInsetsCompat.Type.systemBars() | WindowInsetsCompat.Type.displayCutout(), + Insets.of(0, 0, 0, 0) + ), + keyboardOverlaysWebView + ).build(); injectSafeAreaCSS(newInsets); @@ -239,6 +249,19 @@ private void initWindowInsetsListener() { }); } + private WindowInsetsCompat.Builder withoutOverlayingKeyboard(WindowInsetsCompat.Builder builder, boolean keyboardOverlaysWebView) { + if (!keyboardOverlaysWebView) { + return builder; + } + + // WebView >= 139 shrinks its visual viewport for the IME insets it receives, which makes the page pannable above the keyboard. + // Unlike Chrome it ignores `VirtualKeyboard.overlaysContent` and `interactive-widget=overlays-content`, + // so not handing it the IME insets is the only way to opt out. + // Setting them to `0` rather than consuming them keeps the WebView's inset state up to date. + // See: https://developer.android.com/develop/ui/views/layout/webapps/understand-window-insets + return builder.setInsets(WindowInsetsCompat.Type.ime(), Insets.NONE).setVisible(WindowInsetsCompat.Type.ime(), false); + } + private void injectSafeAreaCSS(WindowInsetsCompat insets) { if (!INSETS_HANDLING_CSS.equals(insetsHandling)) { return;