From 6c3ef65763713a9cced78dc03b85d5aa91a07617 Mon Sep 17 00:00:00 2001 From: Joseph Pender Date: Thu, 5 Mar 2026 09:22:56 -0600 Subject: [PATCH 1/8] Testing margins fix from capacitor-community/safe-area --- .../com/getcapacitor/plugin/SystemBars.java | 108 ++++++++++++++---- 1 file changed, 83 insertions(+), 25 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 3f04adf976..657e588813 100644 --- a/android/capacitor/src/main/java/com/getcapacitor/plugin/SystemBars.java +++ b/android/capacitor/src/main/java/com/getcapacitor/plugin/SystemBars.java @@ -36,6 +36,9 @@ public class SystemBars extends Plugin { static final String INSETS_HANDLING_CSS = "css"; static final String INSETS_HANDLING_DISABLE = "disable"; + private static final int WEBVIEW_VERSION_WITH_SAFE_AREA_FIX = 140; + private static final int WEBVIEW_VERSION_WITH_SAFE_AREA_KEYBOARD_FIX = 144; + static final String viewportMetaJSFunction = """ function capacitorSystemBarsCheckMetaViewport() { const meta = document.querySelectorAll("meta[name=viewport]"); @@ -70,13 +73,19 @@ protected void handleOnStart() { new WebViewListener() { @Override public void onPageCommitVisible(WebView view, String url) { - super.onPageCommitVisible(view, url); - getBridge().getWebView().requestApplyInsets(); + super.onPageCommitVisible(view, url); + getBridge().getWebView().requestApplyInsets(); } } ); } + @Override + protected void handleOnResume() { + super.handleOnResume(); + getBridge().executeOnMainThread(() -> getBridge().getWebView().requestApplyInsets()); + } + @Override protected void handleOnConfigurationChanged(Configuration newConfig) { super.handleOnConfigurationChanged(newConfig); @@ -169,32 +178,67 @@ private void initSafeAreaInsets() { } private void initWindowInsetsListener() { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.VANILLA_ICE_CREAM && insetHandlingEnabled) { - ViewCompat.setOnApplyWindowInsetsListener((View) getBridge().getWebView().getParent(), (v, insets) -> { - boolean hasBrokenWebViewVersion = getWebViewMajorVersion() <= 139; + ViewCompat.setOnApplyWindowInsetsListener((View) getBridge().getWebView().getParent(), (v, insets) -> { + boolean shouldPassthroughInsets = getWebViewMajorVersion() >= WEBVIEW_VERSION_WITH_SAFE_AREA_FIX && hasViewportCover; - if (hasViewportCover) { - Insets safeAreaInsets = calcSafeAreaInsets(insets); - injectSafeAreaCSS(safeAreaInsets.top, safeAreaInsets.right, safeAreaInsets.bottom, safeAreaInsets.left); - } + Insets systemBarsInsets = insets.getInsets(WindowInsetsCompat.Type.systemBars() | WindowInsetsCompat.Type.displayCutout()); + Insets imeInsets = insets.getInsets(WindowInsetsCompat.Type.ime()); + boolean keyboardVisible = insets.isVisible(WindowInsetsCompat.Type.ime()); - if (hasBrokenWebViewVersion) { - if (hasViewportCover && v.hasWindowFocus() && v.isShown()) { - boolean keyboardVisible = insets.isVisible(WindowInsetsCompat.Type.ime()); - if (keyboardVisible) { - Insets imeInsets = insets.getInsets(WindowInsetsCompat.Type.ime()); - setViewMargins(v, Insets.of(0, 0, 0, imeInsets.bottom)); - } else { - setViewMargins(v, Insets.NONE); - } - - return WindowInsetsCompat.CONSUMED; - } - } + if (hasViewportCover) { + Insets safeAreaInsets = calcSafeAreaInsets(insets); + injectSafeAreaCSS(safeAreaInsets.top, safeAreaInsets.right, safeAreaInsets.bottom, safeAreaInsets.left); + } - return insets; - }); - } + if (shouldPassthroughInsets) { + // We need to correct for a possible shown IME + v.setPadding(0, 0, 0, keyboardVisible ? imeInsets.bottom : 0); + + return new WindowInsetsCompat.Builder(insets).setInsets( + WindowInsetsCompat.Type.systemBars() | WindowInsetsCompat.Type.displayCutout(), + Insets.of( + systemBarsInsets.left, + systemBarsInsets.top, + systemBarsInsets.right, + getBottomInset(systemBarsInsets, keyboardVisible) + ) + ).build(); + } + + // We need to correct for a possible shown IME + v.setPadding(systemBarsInsets.left, systemBarsInsets.top, systemBarsInsets.right, keyboardVisible ? imeInsets.bottom : systemBarsInsets.bottom); + + return new WindowInsetsCompat.Builder(insets).setInsets( + WindowInsetsCompat.Type.systemBars() | WindowInsetsCompat.Type.displayCutout(), + Insets.of(0, 0, 0, 0) + ).build(); + }); +// if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.VANILLA_ICE_CREAM && insetHandlingEnabled) { +// ViewCompat.setOnApplyWindowInsetsListener((View) getBridge().getWebView().getParent(), (v, insets) -> { +// boolean hasBrokenWebViewVersion = getWebViewMajorVersion() <= 139; +// +// if (hasViewportCover) { +// Insets safeAreaInsets = calcSafeAreaInsets(insets); +// injectSafeAreaCSS(safeAreaInsets.top, safeAreaInsets.right, safeAreaInsets.bottom, safeAreaInsets.left); +// } +// +// if (hasBrokenWebViewVersion) { +// if (hasViewportCover && v.hasWindowFocus() && v.isShown()) { +// boolean keyboardVisible = insets.isVisible(WindowInsetsCompat.Type.ime()); +// if (keyboardVisible) { +// Insets imeInsets = insets.getInsets(WindowInsetsCompat.Type.ime()); +// setViewMargins(v, Insets.of(0, 0, 0, imeInsets.bottom)); +// } else { +// setViewMargins(v, Insets.NONE); +// } +// +// return WindowInsetsCompat.CONSUMED; +// } +// } +// +// return insets; +// }); +// } } private void setViewMargins(View v, Insets insets) { @@ -305,4 +349,18 @@ private Integer getWebViewMajorVersion() { return 0; } + + private int getBottomInset(Insets systemBarsInsets, boolean keyboardVisible) { + if (getWebViewMajorVersion() < WEBVIEW_VERSION_WITH_SAFE_AREA_KEYBOARD_FIX) { + // This is a workaround for webview versions that have a bug + // that causes the bottom inset to be incorrect if the IME is visible + // See: https://issues.chromium.org/issues/457682720 + + if (keyboardVisible) { + return 0; + } + } + + return systemBarsInsets.bottom; + } } From 850fe2b404c48065ebb68e85d5ef03e243712d91 Mon Sep 17 00:00:00 2001 From: Joseph Pender Date: Mon, 9 Mar 2026 11:52:47 -0500 Subject: [PATCH 2/8] cleanup --- .../com/getcapacitor/plugin/SystemBars.java | 35 ------------------- 1 file changed, 35 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 657e588813..437941fd83 100644 --- a/android/capacitor/src/main/java/com/getcapacitor/plugin/SystemBars.java +++ b/android/capacitor/src/main/java/com/getcapacitor/plugin/SystemBars.java @@ -213,41 +213,6 @@ private void initWindowInsetsListener() { Insets.of(0, 0, 0, 0) ).build(); }); -// if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.VANILLA_ICE_CREAM && insetHandlingEnabled) { -// ViewCompat.setOnApplyWindowInsetsListener((View) getBridge().getWebView().getParent(), (v, insets) -> { -// boolean hasBrokenWebViewVersion = getWebViewMajorVersion() <= 139; -// -// if (hasViewportCover) { -// Insets safeAreaInsets = calcSafeAreaInsets(insets); -// injectSafeAreaCSS(safeAreaInsets.top, safeAreaInsets.right, safeAreaInsets.bottom, safeAreaInsets.left); -// } -// -// if (hasBrokenWebViewVersion) { -// if (hasViewportCover && v.hasWindowFocus() && v.isShown()) { -// boolean keyboardVisible = insets.isVisible(WindowInsetsCompat.Type.ime()); -// if (keyboardVisible) { -// Insets imeInsets = insets.getInsets(WindowInsetsCompat.Type.ime()); -// setViewMargins(v, Insets.of(0, 0, 0, imeInsets.bottom)); -// } else { -// setViewMargins(v, Insets.NONE); -// } -// -// return WindowInsetsCompat.CONSUMED; -// } -// } -// -// return insets; -// }); -// } - } - - private void setViewMargins(View v, Insets insets) { - ViewGroup.MarginLayoutParams mlp = (ViewGroup.MarginLayoutParams) v.getLayoutParams(); - mlp.leftMargin = insets.left; - mlp.bottomMargin = insets.bottom; - mlp.rightMargin = insets.right; - mlp.topMargin = insets.top; - v.setLayoutParams(mlp); } private void injectSafeAreaCSS(int top, int right, int bottom, int left) { From 9ebb431b7ea255ad72e4f1feea64eac14b7d2d68 Mon Sep 17 00:00:00 2001 From: Joseph Pender Date: Mon, 9 Mar 2026 11:59:08 -0500 Subject: [PATCH 3/8] fmt --- .../com/getcapacitor/plugin/SystemBars.java | 38 +++++++++++-------- 1 file changed, 22 insertions(+), 16 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 437941fd83..b3eaadc67a 100644 --- a/android/capacitor/src/main/java/com/getcapacitor/plugin/SystemBars.java +++ b/android/capacitor/src/main/java/com/getcapacitor/plugin/SystemBars.java @@ -73,8 +73,8 @@ protected void handleOnStart() { new WebViewListener() { @Override public void onPageCommitVisible(WebView view, String url) { - super.onPageCommitVisible(view, url); - getBridge().getWebView().requestApplyInsets(); + super.onPageCommitVisible(view, url); + getBridge().getWebView().requestApplyInsets(); } } ); @@ -194,24 +194,30 @@ private void initWindowInsetsListener() { // We need to correct for a possible shown IME v.setPadding(0, 0, 0, keyboardVisible ? imeInsets.bottom : 0); - return new WindowInsetsCompat.Builder(insets).setInsets( - WindowInsetsCompat.Type.systemBars() | WindowInsetsCompat.Type.displayCutout(), - Insets.of( - systemBarsInsets.left, - systemBarsInsets.top, - systemBarsInsets.right, - getBottomInset(systemBarsInsets, keyboardVisible) + return new WindowInsetsCompat.Builder(insets) + .setInsets( + WindowInsetsCompat.Type.systemBars() | WindowInsetsCompat.Type.displayCutout(), + Insets.of( + systemBarsInsets.left, + systemBarsInsets.top, + systemBarsInsets.right, + getBottomInset(systemBarsInsets, keyboardVisible) + ) ) - ).build(); + .build(); } // We need to correct for a possible shown IME - v.setPadding(systemBarsInsets.left, systemBarsInsets.top, systemBarsInsets.right, keyboardVisible ? imeInsets.bottom : systemBarsInsets.bottom); - - return new WindowInsetsCompat.Builder(insets).setInsets( - WindowInsetsCompat.Type.systemBars() | WindowInsetsCompat.Type.displayCutout(), - Insets.of(0, 0, 0, 0) - ).build(); + v.setPadding( + systemBarsInsets.left, + systemBarsInsets.top, + systemBarsInsets.right, + keyboardVisible ? imeInsets.bottom : systemBarsInsets.bottom + ); + + return new WindowInsetsCompat.Builder(insets) + .setInsets(WindowInsetsCompat.Type.systemBars() | WindowInsetsCompat.Type.displayCutout(), Insets.of(0, 0, 0, 0)) + .build(); }); } From d5742a53f06fb8f617fdb251b4dabc4d9af8faa6 Mon Sep 17 00:00:00 2001 From: Joseph Pender Date: Tue, 10 Mar 2026 14:06:33 -0500 Subject: [PATCH 4/8] Adding additional check for injecting css vars --- .../src/main/java/com/getcapacitor/plugin/SystemBars.java | 6 +++--- 1 file changed, 3 insertions(+), 3 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 b3eaadc67a..52cfb4c3be 100644 --- a/android/capacitor/src/main/java/com/getcapacitor/plugin/SystemBars.java +++ b/android/capacitor/src/main/java/com/getcapacitor/plugin/SystemBars.java @@ -103,7 +103,7 @@ private void initSystemBars() { } initWindowInsetsListener(); - initSafeAreaInsets(); + initSafeAreaCSSVariables(); getBridge().executeOnMainThread(() -> { setStyle(style, ""); @@ -166,7 +166,7 @@ private Insets calcSafeAreaInsets(WindowInsetsCompat insets) { return Insets.of(safeArea.left, safeArea.top, safeArea.right, safeArea.bottom); } - private void initSafeAreaInsets() { + private void initSafeAreaCSSVariables() { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.VANILLA_ICE_CREAM && insetHandlingEnabled) { View v = (View) this.getBridge().getWebView().getParent(); WindowInsetsCompat insets = ViewCompat.getRootWindowInsets(v); @@ -185,7 +185,7 @@ private void initWindowInsetsListener() { Insets imeInsets = insets.getInsets(WindowInsetsCompat.Type.ime()); boolean keyboardVisible = insets.isVisible(WindowInsetsCompat.Type.ime()); - if (hasViewportCover) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.VANILLA_ICE_CREAM && hasViewportCover && insetHandlingEnabled) { Insets safeAreaInsets = calcSafeAreaInsets(insets); injectSafeAreaCSS(safeAreaInsets.top, safeAreaInsets.right, safeAreaInsets.bottom, safeAreaInsets.left); } From d071cdee98e4ab72f5c89ba68dfd3e90bd8248fb Mon Sep 17 00:00:00 2001 From: Joseph Pender Date: Thu, 12 Mar 2026 09:31:34 -0500 Subject: [PATCH 5/8] fixing potential double margin issue --- .../com/getcapacitor/plugin/SystemBars.java | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 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 52cfb4c3be..b0e1463a24 100644 --- a/android/capacitor/src/main/java/com/getcapacitor/plugin/SystemBars.java +++ b/android/capacitor/src/main/java/com/getcapacitor/plugin/SystemBars.java @@ -185,15 +185,15 @@ private void initWindowInsetsListener() { Insets imeInsets = insets.getInsets(WindowInsetsCompat.Type.ime()); boolean keyboardVisible = insets.isVisible(WindowInsetsCompat.Type.ime()); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.VANILLA_ICE_CREAM && hasViewportCover && insetHandlingEnabled) { - Insets safeAreaInsets = calcSafeAreaInsets(insets); - injectSafeAreaCSS(safeAreaInsets.top, safeAreaInsets.right, safeAreaInsets.bottom, safeAreaInsets.left); - } - if (shouldPassthroughInsets) { // We need to correct for a possible shown IME v.setPadding(0, 0, 0, keyboardVisible ? imeInsets.bottom : 0); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.VANILLA_ICE_CREAM && hasViewportCover && insetHandlingEnabled) { + Insets safeAreaInsets = calcSafeAreaInsets(insets); + injectSafeAreaCSS(safeAreaInsets.top, safeAreaInsets.right, safeAreaInsets.bottom, safeAreaInsets.left); + } + return new WindowInsetsCompat.Builder(insets) .setInsets( WindowInsetsCompat.Type.systemBars() | WindowInsetsCompat.Type.displayCutout(), @@ -215,9 +215,16 @@ private void initWindowInsetsListener() { keyboardVisible ? imeInsets.bottom : systemBarsInsets.bottom ); - return new WindowInsetsCompat.Builder(insets) + WindowInsetsCompat newInsets = new WindowInsetsCompat.Builder(insets) .setInsets(WindowInsetsCompat.Type.systemBars() | WindowInsetsCompat.Type.displayCutout(), Insets.of(0, 0, 0, 0)) .build(); + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.VANILLA_ICE_CREAM && hasViewportCover && insetHandlingEnabled) { + Insets safeAreaInsets = calcSafeAreaInsets(newInsets); + injectSafeAreaCSS(safeAreaInsets.top, safeAreaInsets.right, safeAreaInsets.bottom, safeAreaInsets.left); + } + + return newInsets; }); } From 2fc968d9c4114e19a8be1e8da19336c60877fbf3 Mon Sep 17 00:00:00 2001 From: Joseph Pender Date: Fri, 13 Mar 2026 10:09:33 -0500 Subject: [PATCH 6/8] Add documentation / historical comments --- .../src/main/java/com/getcapacitor/plugin/SystemBars.java | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) 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 b0e1463a24..fbdbe07023 100644 --- a/android/capacitor/src/main/java/com/getcapacitor/plugin/SystemBars.java +++ b/android/capacitor/src/main/java/com/getcapacitor/plugin/SystemBars.java @@ -7,7 +7,6 @@ import android.os.Build; import android.util.TypedValue; import android.view.View; -import android.view.ViewGroup; import android.view.Window; import android.webkit.JavascriptInterface; import android.webkit.WebView; @@ -36,7 +35,9 @@ public class SystemBars extends Plugin { static final String INSETS_HANDLING_CSS = "css"; static final String INSETS_HANDLING_DISABLE = "disable"; + // https://issues.chromium.org/issues/40699457 private static final int WEBVIEW_VERSION_WITH_SAFE_AREA_FIX = 140; + // https://issues.chromium.org/issues/457682720 private static final int WEBVIEW_VERSION_WITH_SAFE_AREA_KEYBOARD_FIX = 144; static final String viewportMetaJSFunction = """ @@ -215,6 +216,9 @@ private void initWindowInsetsListener() { keyboardVisible ? imeInsets.bottom : systemBarsInsets.bottom ); + // 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(); From 9614368f79e57042c938df2b9ec06178f6b7924d Mon Sep 17 00:00:00 2001 From: Joseph Pender Date: Fri, 13 Mar 2026 10:14:22 -0500 Subject: [PATCH 7/8] removing unneeded handleOnResume --- .../src/main/java/com/getcapacitor/plugin/SystemBars.java | 6 ------ 1 file changed, 6 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 fbdbe07023..c84923ca8b 100644 --- a/android/capacitor/src/main/java/com/getcapacitor/plugin/SystemBars.java +++ b/android/capacitor/src/main/java/com/getcapacitor/plugin/SystemBars.java @@ -81,12 +81,6 @@ public void onPageCommitVisible(WebView view, String url) { ); } - @Override - protected void handleOnResume() { - super.handleOnResume(); - getBridge().executeOnMainThread(() -> getBridge().getWebView().requestApplyInsets()); - } - @Override protected void handleOnConfigurationChanged(Configuration newConfig) { super.handleOnConfigurationChanged(newConfig); From 73689cc8754a367e24623c84ef090fb03187587d Mon Sep 17 00:00:00 2001 From: Joseph Pender Date: Fri, 13 Mar 2026 10:33:33 -0500 Subject: [PATCH 8/8] Updataing documentation --- cli/src/declarations.ts | 2 +- core/system-bars.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/cli/src/declarations.ts b/cli/src/declarations.ts index 6612eec777..5eacf5e654 100644 --- a/cli/src/declarations.ts +++ b/cli/src/declarations.ts @@ -713,7 +713,7 @@ export interface PluginsConfig { * * `css` = Injects CSS variables (`--safe-area-inset-*`) containing correct safe area inset values into the webview. * - * `disable` = Disable all inset handling. + * `disable` = Disable CSS variables injection. * * @default "css" */ diff --git a/core/system-bars.md b/core/system-bars.md index c7654063bc..67275077af 100644 --- a/core/system-bars.md +++ b/core/system-bars.md @@ -73,7 +73,7 @@ const setStatusBarAnimation = async () => { ## Configuration | Prop | Type | Description | Default | | ------------- | -------------------- | ------------------------------------------------------------------------- | ------------------ | -| **`insetsHandling`** | string | Specifies how to handle problematic insets on Android. This option is only supported on Android.
`css` = Injects CSS variables (`--safe-area-inset-*`) containing correct safe area inset values into the webview.
`disable` = Disable all inset handling. | css | +| **`insetsHandling`** | string | Specifies how to handle problematic insets on Android. This option is only supported on Android.
`css` = Injects CSS variables (`--safe-area-inset-*`) containing correct safe area inset values into the webview.
`disable` = Disable CSS variables injection. | css | | **`style`** | string | The style of the text and icons of the system bars. | DEFAULT | | **`hidden`** | boolean | Hide the system bars on start. | false | | **`animation`** | string | The type of status bar animation used when showing or hiding. This option is only supported on iOS. | FADE |