Skip to content

[webview_flutter_android] Adds opt out of displayCutout and systemBars Android inset changes#11192

Open
bparrishMines wants to merge 23 commits intoflutter:mainfrom
bparrishMines:webview_safe_area
Open

[webview_flutter_android] Adds opt out of displayCutout and systemBars Android inset changes#11192
bparrishMines wants to merge 23 commits intoflutter:mainfrom
bparrishMines:webview_safe_area

Conversation

@bparrishMines
Copy link
Contributor

@bparrishMines bparrishMines commented Mar 6, 2026

For WebView versions >=144, support has been added for displayCutout() insets and systemBars() insets. This is causing WebViews to incorrectly report that it is obscured by a system bar or display cutout as demonstrated in this issue.

This adds the opt out for inset changes as explained in this chromium doc. It seems Flutter handles safe areas for platform views, so the WebView can zero out inset changes to the View.

iOS does something similar. And it also sets UIScrollView.contentInsetAdjustmentBehavior to Never. My assumption was that this was never done for Android, because WebViews didn't receive these inset changes until this version.

Code sample

main.dart in webview_flutter_android:

import 'package:flutter/material.dart';
import 'package:webview_flutter_platform_interface/webview_flutter_platform_interface.dart';

const String htmlPage = '''
<!DOCTYPE html>
<html lang="de">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>WebView Test</title>
    <style>
        header {
            position: absolute;
            top: 0;
            left: 0;
            right: 0;
            padding-top: env(safe-area-inset-top);
            background-color: blue;
            color: #ffffff;
        }
        .content {
            padding-top: 72px;
        }
    </style>
</head>
<body>
<div class="container">
    <header><h1>Webview AppBar</h1></header>
    <div class="content">
        <p>This is some webview content</p>
    </div>
</div>
</body>
</html>
''';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
      ),
      home: const MyHomePage(title: 'Flutter Demo Home Page'),
    );
  }
}

class MyHomePage extends StatefulWidget {
  const MyHomePage({super.key, required this.title});

  final String title;

  @override
  State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  late final PlatformWebViewController _controller;

  @override
  void initState() {
    super.initState();

    _controller =
        PlatformWebViewController(
            const PlatformWebViewControllerCreationParams(),
          )
          ..setJavaScriptMode(JavaScriptMode.unrestricted)
          ..loadHtmlString(htmlPage);
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        backgroundColor: Theme.of(context).colorScheme.primary,
        toolbarHeight: 0,
      ),
      body: PlatformWebViewWidget(
        PlatformWebViewWidgetCreationParams(controller: _controller),
      ).build(context),
    );
  }
}
Screenshots
Before After
Screenshot_20260313_151847 Screenshot_20260313_151957

Fixes flutter/flutter#182208

Pre-Review Checklist

If you need help, consider asking for advice on the #hackers-new channel on Discord.

Note: The Flutter team is currently trialing the use of Gemini Code Assist for GitHub. Comments from the gemini-code-assist bot should not be taken as authoritative feedback from the Flutter team. If you find its comments useful you can update your code accordingly, but if you are unsure or disagree with the feedback, please feel free to wait for a Flutter team member's review for guidance on which automated comments should be addressed.

Footnotes

  1. Regular contributors who have demonstrated familiarity with the repository guidelines only need to comment if the PR is not auto-exempted by repo tooling. 2

@bparrishMines bparrishMines changed the title Webview safe area [webview_flutter_android] Adds opt out of displayCutout and systemBars Android inset changes Mar 6, 2026
@bparrishMines bparrishMines marked this pull request as ready for review March 9, 2026 21:24
@bparrishMines bparrishMines requested review from a team and stuartmorgan-g March 9, 2026 21:24
@bparrishMines
Copy link
Contributor Author

@flutter/android-reviewers This adds an opt out to newly supported insets on chromium. My assumption for this PR is that these insets are handled by Flutter (similar to iOS), but wanted to ensure this is fine with Android team members.

Copy link

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces an opt-out mechanism for displayCutout and systemBars Android inset changes in webview_flutter_android. The changes involve updating Pigeon files to add a new method for setting an inset listener, implementing this logic on the Android native side, and calling it from the Dart AndroidWebViewController. The tests have been updated accordingly.

My main feedback concerns the implementation of the inset listener on the Android side, where padding is being applied to the View. This seems to contradict the goal of letting Flutter handle safe areas, as stated in the comments and PR description. I've left a specific comment with a suggestion to address this.

Note: Security Review did not run due to the size of the PR.

Comment on lines +98 to +103
final Insets allInsets = windowInsets.getInsets(finalTypeMask);
pigeon_instance.setPadding(
allInsets.left, allInsets.top, allInsets.right, allInsets.bottom);
return new WindowInsetsCompat.Builder(windowInsets)
.setInsets(finalTypeMask, Insets.NONE)
.build();

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

The comment in android_webview_controller.dart states that "Flutter handles these internally", and the PR description mentions that the desired behavior is similar to iOS's contentInsetAdjustmentBehavior = .never. Both of these suggest that the web content should extend into the safe areas (i.e., underlap system bars and cutouts).

However, applying padding to the native View here will shrink the web content area, preventing it from drawing into the safe areas. This seems to contradict the intended goal of letting Flutter manage the safe area.

If the intention is to let Flutter handle safe areas, you should probably only consume the insets to prevent the WebView from applying them, but not apply any padding to the View itself. This would allow the web content to render edge-to-edge.

Could you clarify if this padding is intended? If not, these lines should be removed, and the corresponding test in ViewTest.java should be updated.

Suggested change
final Insets allInsets = windowInsets.getInsets(finalTypeMask);
pigeon_instance.setPadding(
allInsets.left, allInsets.top, allInsets.right, allInsets.bottom);
return new WindowInsetsCompat.Builder(windowInsets)
.setInsets(finalTypeMask, Insets.NONE)
.build();
return new WindowInsetsCompat.Builder(windowInsets)
.setInsets(finalTypeMask, Insets.NONE)
.build();

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Confirmed that an edge-edge WebView still works.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you confirm the behavior of a full screen flutter web view on a device with a simulated or actual tall cutout.
You can test on any physical device (or maybe emulator) by running adb shell cmd overlay enable com.android.internal.display.cutout.emulation.tall

Copy link
Contributor

@reidbaker reidbaker left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

At a high level I like that we are opting out of chromes auto system bars instead of creating a path to ignore our own insets logic when showing in chrome. I need to get this a more through review but directionally I am for this change.

Copy link
Contributor

@reidbaker reidbaker left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you have screenshots of the before and after behavior could you include them in this pr? If you dont and it is cumbersome to get them then feel free to ignore this request.

public void setInsetListenerToSetInsetsToZero(
@NonNull View pigeon_instance, @NonNull List<? extends WindowInsets> insets) {
int insetsTypeMask = 0;
for (WindowInsets inset : insets) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you add comment here that indicates why SYTEM_BARS and DISPLAY_CUTOUT where chosen and how to know when a new item should be added to this list.

Additionally why use the all caps ints instead of WindowInsets.Type.systemBars()?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I only added those two because they were the only ones that we are interested in using for this opt out. I went ahead and added the remaining values for the enum. The caps for SYSTEM_BARS, DISPLAY_CUTOUT, etc. are the generated names for the WindowInsets enum created by pigeon.

I just changed them to WindowInsetsType to make it more accurate though.

ViewCompat.setOnApplyWindowInsetsListener(
pigeon_instance,
(view, windowInsets) -> {
if (finalTypeMask == 0) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you add a comment explaining that this is checking to see if we set any bitwise values to make the code easier to skim without having someone need to understand why we are comparing to zero.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I changed the logic to not need to make this check since it is bug prone. Now the code checks if the list is empty and handles it at the top.

}

final Insets allInsets = windowInsets.getInsets(finalTypeMask);
pigeon_instance.setPadding(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What does setting the padding here on the pigeon_instance do?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So I reread the opt out doc and my interpretation is that the padding used in that doc were just an example of how to manually handle the insets. So I removed the padding added in this PR since these seem to be handled by Flutter. The fix still works without it.

Comment on lines +101 to +102
return new WindowInsetsCompat.Builder(windowInsets)
.setInsets(finalTypeMask, Insets.NONE)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this line is saying to copy the existing windowInsets then to set all the ones we care about "consuming" to Insets.NONE.

If that is correct i think this line could be made more readable with the following changes.

Suggested change
return new WindowInsetsCompat.Builder(windowInsets)
.setInsets(finalTypeMask, Insets.NONE)
// Consume insects that are already handled by flutter.
return new WindowInsetsCompat.Builder(windowInsets)
.setInsets(activeInsetsHandledByFlutter, Insets.NONE)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Additionally if we cant define a list to import in both locations I think the code that is "already" handling the insets should be cross linked. As a result the case statements probably become another loop.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This code isn't intended to indicate what is handled by Flutter. I added comments about the selected inset types in android_webview_controller.dart.

My original idea was to expose this setting to the user, so they can have control over this. But I changed it to only handle this internally in the plugin instead for now since this may be a general change that we need to make for every WebView to work properly within Flutter.

Comment on lines +98 to +103
final Insets allInsets = windowInsets.getInsets(finalTypeMask);
pigeon_instance.setPadding(
allInsets.left, allInsets.top, allInsets.right, allInsets.bottom);
return new WindowInsetsCompat.Builder(windowInsets)
.setInsets(finalTypeMask, Insets.NONE)
.build();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you confirm the behavior of a full screen flutter web view on a device with a simulated or actual tall cutout.
You can test on any physical device (or maybe emulator) by running adb shell cmd overlay enable com.android.internal.display.cutout.emulation.tall

@bparrishMines bparrishMines added the CICD Run CI/CD label Mar 16, 2026
@bparrishMines
Copy link
Contributor Author

@reidbaker I added a code sample and images to the description to show the difference.

@bparrishMines bparrishMines added CICD Run CI/CD and removed CICD Run CI/CD labels Mar 18, 2026
@bparrishMines
Copy link
Contributor Author

Can you confirm the behavior of a full screen flutter web view on a device with a simulated or actual tall cutout.
You can test on any physical device (or maybe emulator) by running adb shell cmd overlay enable com.android.internal.display.cutout.emulation.tall

Did some testing. It looks like Scaffold may be the problem. Scaffold seems to handle making space for system bars and display cutouts, but the WebView still thinks it is full screen. When I removed the Scaffold and just showed the WebView directly this is what I got.

Without PR With PR
Screenshot_20260318_115850 Screenshot_20260318_115911

The WebView handles the spacing correctly when there is no scaffold.

This potentially needs to be a feature that the user can use rather the defacto handling. Or we may need to handle why the WebView believes it is full screen when it is in a Scaffold.

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

android webview M144 reporting insets despite not overlapping with system bars

2 participants