Android 15+ edge-to-edge: WebView never receives real WindowInsets

Title: Android 15+ edge-to-edge: WebView never receives real WindowInsets (CoordinatorLayout swallows them), and overlaysWebView has no working replacement

Bug Report

Capacitor Version

$ npx cap doctor
@capacitor/android: 8.5.0
@capacitor/core: 8.5.0
@capacitor/cli: 8.5.0

Platform(s) Android (targetSdk 35+ / 36)

Current Behavior

On Android 15+, edge-to-edge is enforced by the OS with no opt-out (android:windowOptOutEdgeToEdgeEnforcement is ignored once targetSdk reaches 35+, and removed as an escape hatch entirely on 36). BridgeActivity’s default content view (capacitor_bridge_layout_main.xml, a plain CoordinatorLayout wrapping the WebView) does no WindowInsets handling at all — I searched the full Capacitor Android source (Bridge.java, BridgeActivity.java, CapacitorWebView.java) and found zero references to WindowInsets, fitsSystemWindows, or edge-to-edge.

The practical effect: the WebView draws content under the status bar and any display cutout, with no supported way to reserve space for it — including the documented one. @capacitor/status-bar’s own type definitions say:

/**
 * Whether the statusbar is overlaid or not.
 * Not available on Android 15+.
 */
overlaysWebView?: boolean;

So the one API that used to solve this (overlaysWebView: false, reserving native space for the status bar outside the WebView) is explicitly disabled on exactly the OS version where it’s needed most, with nothing offered in its place.

Attempting the natural workaround also fails silently, and this is the part I think is a real bug rather than just a missing feature: calling WindowCompat.setDecorFitsSystemWindows(getWindow(), false) and registering ViewCompat.setOnApplyWindowInsetsListener directly on the WebView (getBridge().getWebView()) always receives all-zero insets, even though the window genuinely has a non-zero status bar. Confirmed via logging:

D ProjectInsets: DECOR VIEW listener fired: top=66 bottom=66     <- correct, matches dumpsys
D ProjectInsets: WEBVIEW listener fired: top=0 bottom=0          <- always zero

(adb shell dumpsys window displays independently confirms the real status bar inset is 66px on this device — the decor view sees it correctly, the WebView never does.)

So something between the decor view and the WebView — almost certainly the default CoordinatorLayout root, since it’s the only view in between and Capacitor itself does nothing insets-related — is consuming/zeroing the insets before they reach the WebView. This makes it impossible to react to real inset values from the one place (the WebView itself) that would be the obvious place to do it.

Expected Behavior

Either:

  1. BridgeActivity/the default layout should not silently swallow WindowInsets before they reach the WebView, so a listener registered on the WebView gets real values, or
  2. Capacitor should handle edge-to-edge inset correctly out of the box on Android 15+ (e.g. auto-apply WindowInsets as native padding on the WebView, and/or push the values into CSS custom properties so env(safe-area-inset-*)-dependent frameworks like Ionic work correctly), since overlaysWebView — the documented mechanism — is explicitly unavailable on exactly this OS version.

Reproduction

  1. Create a fresh Capacitor Android app, compileSdk/targetSdk = 36.

  2. Run on an Android 15+ emulator (any device/AVD, cutout not required — reproduces on a plain Pixel 4 profile with no cutout, so this isn’t cutout-specific).

  3. Add any UI element anchored to the top of the page (e.g. a fixed header).

  4. Observe it renders under the status bar.

  5. Add:

    WindowCompat.setDecorFitsSystemWindows(getWindow(), false);
    ViewCompat.setOnApplyWindowInsetsListener(getBridge().getWebView(), (view, insets) -> {
        Log.d("Insets", "top=" + insets.getInsets(WindowInsetsCompat.Type.systemBars()).top);
        return insets;
    });
    
    
  6. Observe the logged value is always 0, while the same listener registered on getWindow().getDecorView() logs the correct non-zero value at the same moment.

Workaround we’re using

Listen on the decor view instead of the WebView (where real values are actually available), then manually apply them as native View.setPadding() on the WebView:

WindowCompat.setDecorFitsSystemWindows(getWindow(), false);
WebView webView = getBridge().getWebView();
ViewCompat.setOnApplyWindowInsetsListener(getWindow().getDecorView(), (view, windowInsets) -> {
    Insets insets = windowInsets.getInsets(
        WindowInsetsCompat.Type.systemBars() | WindowInsetsCompat.Type.displayCutout()
    );
    webView.setPadding(insets.left, insets.top, insets.right, insets.bottom);
    return windowInsets;
});

This works, but it’s a workaround every Capacitor Android app targeting SDK 35+ will need to reinvent, since it isn’t handled by the framework and the documented API (overlaysWebView) doesn’t work anymore.

Happy to share a minimal repro project if useful.