Skip to content

[web] Report env(safe-area-inset-*) as FlutterView.viewPadding - #191647

Merged
auto-submit[bot] merged 6 commits into
flutter:masterfrom
diegolopezrm:web-safe-area-insets
Sep 23, 2026
Merged

auto-submit[bot] merged 6 commits into
flutter:masterfrom
diegolopezrm:web-safe-area-insets

Conversation

@diegolopezrm

@diegolopezrm diegolopezrm commented Aug 25, 2026 •

Copy link
Copy Markdown
Contributor

[web] Report env(safe-area-inset-*) as FlutterView.viewPadding

The problem

SafeArea does nothing on the web. On an iPhone running the app as a
standalone PWA, content sits under the notch, the Dynamic Island and the home
indicator, and MediaQuery.paddingOf(context) is EdgeInsets.zero on every
browser. The issue has been open since 2021, and the workaround that everybody
in the thread reaches for — adding <meta name="viewport" content="..., viewport-fit=cover"> to index.html — has no effect.

Issue: #84833

Root cause

There are two independent causes, and fixing either one alone leaves the bug in
place.

1. The engine deletes the app's opt-in.
FullPageEmbeddingStrategy._applyViewportMeta()
(engine/src/flutter/lib/web_ui/lib/src/engine/view_embedder/embedding_strategy/full_page_embedding_strategy.dart:70)
runs querySelectorAll('meta[name="viewport"]'), calls remove() on every
match (line 86), and appends its own tag with a hard-coded
content = 'width=device-width, initial-scale=1.0, maximum-scale=5.0'
(line 96). There is no viewport-fit in it. So an app that wrote
viewport-fit=cover in its index.html has that descriptor deleted during
bootstrap, WebKit reverts to keeping the viewport inside the safe area, and
env(safe-area-inset-*) correctly reports that there is nothing to inset. This
is why every workaround in the thread fails.

2. Nothing ever reads env(safe-area-inset-*).
Grepping safe-area|safeAreaInset|viewport-fit|viewportFit across
engine/src/flutter/lib/web_ui/lib/ returns no matches before this change.
EngineFlutterView.viewPadding (window.dart:264) and
EngineFlutterView.padding (window.dart:270) both read a
final ViewConfiguration _viewConfiguration = const ViewConfiguration();
(window.dart:97) that is never reassigned, so they are permanently
ui.ViewPadding.zero. Only viewInsets works, because it is backed by a
mutable field that _computeOnScreenKeyboardInsets (window.dart:363) updates
through DimensionsProvider.computeKeyboardInsets.

Reproduction

Measured on an iPhone simulator, devicePixelRatio 3, UA
Mozilla/5.0 (iPhone; CPU iPhone OS 18_7 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/26.1 Mobile/15E148 Safari/604.1. Two pages were
served side by side from the same origin: a plain HTML control that declares
viewport-fit=cover and reads env(safe-area-inset-*) out of
getComputedStyle, and a Flutter app whose index.html declares the same tag
and which additionally reports MediaQuery.viewPaddingOf / paddingOf. Both
declare apple-mobile-web-app-status-bar-style=black-translucent. Each was read
in a Safari tab and as a standalone add-to-home-screen PWA, in both
orientations. 11 configurations were recorded in all, from 997 samples; the four
below are the ones the argument rests on.

The Flutter app was built with Flutter 3.44.6 stable, not with this branch,
so the engine tag quoted in row 3 is the one that release writes. Current main
writes maximum-scale=5.0 and no user-scalable descriptor. The difference is
immaterial here: what matters is that neither carries a viewport-fit, and both
remove the tag the app authored.

page mode orientation innerWidth viewport meta actually in the DOM browser env() T/R/B/L MediaQuery.viewPaddingOf
control tab landscape 874 …, viewport-fit=cover (author's) 0 / 62 / 20 / 62 n/a
control PWA portrait 402 …, viewport-fit=cover (author's) 0 / 0 / 34 / 0 n/a
Flutter tab landscape 750 width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no (flt-viewport) 0 / 0 / 20 / 0 zero
Flutter PWA portrait 402 (flt-viewport, no viewport-fit) 0 / 0 / 0 / 0 zero

Two things to read off this:

  • The viewport-fit=cover the app wrote is not in the DOM on the Flutter
    page. The tag that is there is the engine's, and it has no viewport-fit.
    That is cause 1, observed directly.
  • The viewport is correspondingly narrower — 750 against the control's 874 in
    the same orientation on the same device. The difference, 124, is exactly
    62 + 62, the left and right insets the control reports. The app is being
    laid out inside the safe area.

MediaQuery.viewPaddingOf was EdgeInsets.zero in all 997 readings taken
from the Flutter page
, including the runs where the viewport meta tag was
restored by hand so that the page did go full-bleed and env() did report
0 / 62 / 20 / 62. That isolates cause 2: even with the browser reporting a
safe area correctly, nothing in the engine reads it.

What has not been measured on device. The numbers above are all pre-patch.
The patched engine has unit coverage (below) but has not been run on a device or
simulator, so this PR claims no measured post-patch MediaQuery.padding.

env(safe-area-inset-top) was zero in all 11 configurations, including the
two standalone-PWA ones, where there is no Safari chrome to explain it away. I
don't have an account of why, so I'm not offering one: the top axis is simply
uncovered on device and rests on unit tests. If a reviewer knows the
configuration that produces a non-zero top inset in mobile WebKit, I'd like to
measure it. An end-to-end confirmation that a SafeArea child moves is also
still owed. Both are repeated under "Not covered".

The same run also shows why the env() values cannot be passed through
verbatim: with no viewport-fit anywhere, iOS Safari still reports
env(safe-area-inset-bottom) = 20px (row 3 above). See "Backwards
compatibility" below.

What the fix does

Layer 1 — preserve the app's viewport-fit.
_applyViewportMeta() now reads the viewport-fit descriptor off the viewport
meta tags it is about to remove and copies it into the tag it writes. Parsing
lives in one new file, view_embedder/viewport_fit.dart, so that the side that
reads a descriptor and the side that writes it can never drift apart. The
resolution matches the browser's:

  • the last viewport meta tag of the page wins outright, including when it
    declares no viewport-fit (WebKit builds a fresh set of viewport arguments
    per tag, so descriptors that the last tag omits revert to their defaults);
  • ,, ; and whitespace are all accepted as descriptor separators, because
    browsers accept all three — content="width=400;initial-scale=1" is honoured
    by both Blink and WebKit;
  • the descriptor and its value are matched case-insensitively;
  • only auto, contain and cover are copied over, so nothing unknown can
    reach the tag the engine writes.

Because the tag the engine writes carries the descriptor, the value also
survives hot restart: the tag read at the start of _applyViewportMeta() on the
next run is the flt-viewport tag left behind by the previous one.

The "this tag will be replaced" warning now fires only when the engine's tag
really does say less than the app's — it lists the descriptors being dropped —
so following the template's own advice below no longer provokes a warning on
every debug run.

Layer 2 — feed env(safe-area-inset-*) into the framework.
DimensionsProvider gains computeSafeAreaInsets().
FullPageDimensionsProvider implements it with a display: none probe element
whose four paddings are env(safe-area-inset-*, 0px); the browser substitutes
env() at computed-value time, so getComputedStyle returns absolute lengths
without the element ever being laid out or painted. Every declaration on the
probe is written with important priority, so an ordinary
* { padding: 0 !important } reset in the host page cannot silently zero the
measurement. The probe is created up front, in the constructor, so reading the
safe area is a pure measurement and never a DOM mutation in the middle of a
frame; it is re-attached if the host page ever empties <body>, and removed in
close().

The insets are gated on the app's own opt-in. computeSafeAreaInsets()
returns ViewPadding.zero unless the viewport meta tag the engine wrote carries
viewport-fit=cover, i.e. unless the app declared it in its index.html. The
gate reads that tag back with readViewportFit, the same function that wrote
it, so the two halves are not coupled through an exact spelling: changing how
_applyViewportMeta joins its descriptors cannot silently switch the feature
off.

EngineFlutterView.viewPadding is now backed by those insets, computed lazily
and recomputed by _handleBrowserResize on the same events that recompute the
physical size. padding is viewPadding minus viewInsets, clamped at zero
(ViewPadding.minus), which is the relationship the other platforms use: when
the on-screen keyboard covers the bottom safe area, the bottom padding goes to
zero. No framework-side change is needed — MediaQuery.fromView already reads
view.padding and view.viewPadding.

systemGestureInsets is deliberately left at zero. env() describes the parts
of the viewport that the device obstructs, not the parts the system reserves for
its own gestures, and browsers do not expose the latter.

Tooling. packages/flutter_tools/templates/app/web/index.html.tmpl gains
the tag, commented out, with an explanation, next to the guidance it already
carries for <base href>. New projects are not opted in by default; the comment
tells developers how to opt in.

Cleanup. ViewConfiguration.viewInsets, .viewPadding and .padding
(platform_dispatcher.dart) are removed. viewInsets was already dead, and the
other two become dead with this change; ViewConfiguration is only ever
instantiated as const ViewConfiguration().

Backwards compatibility

The gate is what keeps this from being a breaking change, and it is there
because the naive version is one.

  • Apps that never declared viewport-fit — including everything
    flutter create produces, since the web template ships no active viewport meta tag —
    are unchanged. The engine writes a byte-identical viewport meta tag, and
    viewPadding, padding and MediaQuery.paddingOf stay zero.

    This is not automatic. iOS Safari reports env(safe-area-inset-bottom) = 20px
    in landscape with no viewport-fit at all, so an implementation that simply
    forwarded env() would have given every existing Flutter web app on iOS an
    uninvited bottom: 20 logical pixels, shrinking the child of every SafeArea
    in an app that asked for nothing. The 20px is a measured reading (row 3 of
    the table above); the resulting layout shift is the arithmetic consequence,
    not a second measurement.
    FullPageDimensionsProvider.computeSafeAreaInsets() therefore returns zero
    unless the page opted in, and the doc comment on
    DimensionsProvider.computeSafeAreaInsets says so explicitly.

  • Apps that declared viewport-fit=cover in their index.html do change,
    and this is the fix working rather than a side effect. Today the engine
    deletes the descriptor, so those apps are laid out inside the safe area; after
    this change they are laid out full-bleed. On the device above, that is the
    difference between the Flutter page's 750 logical pixels and the control's
    874 in the same orientation, and content that is not
    wrapped in a SafeArea moves under the cutout and the rounded corners.

    This population matters: viewport-fit=cover in index.html is the most
    repeated workaround in issue [web][PWA] SafeArea Widget does not work on iOS Safari PWA #84833 itself and in five years of blog posts
    about it. Many authors added the tag, saw nothing happen, and never wrapped
    anything in SafeArea. For them this is a visible layout change. It is the
    behaviour the tag has always requested, and the tag is the app's own explicit
    opt-in, but it should be called out in the release notes and it is the reason
    the PR does not carry Fixes #84833 on its own.

  • Embedded (hostElement) and multi-view apps are untouched.
    _applyViewportMeta only exists in FullPageEmbeddingStrategy, and
    CustomElementDimensionsProvider.computeSafeAreaInsets() returns zero, so
    padding == zero.minus(viewInsets) == zero exactly as before.

  • Browsers without env() support degrade to zero rather than to NaN: an
    unsupported env() leaves the padding at its initial value, which reads back
    as 0px.

If the team would rather not move the viewport-fit=cover population in a
single release, the cover pass-through in _applyViewportMeta is the one place
to put a FlutterConfiguration flag; the rest of the change is inert without
it.

Tests

42 new unit tests, run with felt test --browser chrome --compiler dart2js over
the whole test/engine directory. The engine suite goes from 1190 to 1232
passing, 14 skipped, no failures and no regressions.

A new viewport_fit_test.dart covers the parser directly: every specification
value, unknown values, a descriptor that merely ends in viewport-fit, ,
; and whitespace separators, = spacing, case-insensitivity,
last-declaration-wins, and a round trip between what ViewportFit writes and
what readViewportFit reads. parseViewportContent is held to the same
tolerance, so the two functions cannot disagree about what a content attribute
says. The rest extend the four existing suites: the
descriptor surviving _applyViewportMeta in each of those spellings; the gate
staying shut for auto and contain, which the engine preserves but which must
not unlock the insets; the probe's structure, its important priority beating a
* { padding: 5px !important } rule in the host page, its re-attachment and its
removal on close(); viewPadding refreshed by a real resize event rather
than only by debugForceResize, so _handleBrowserResize is covered; zero for
an embedded view; and ViewPadding.minus at no, partial and full clamping.

Two of them run a real FullPageEmbeddingStrategy over a page declaring
viewport-fit=cover, hand the page it leaves behind to a real
FullPageDimensionsProvider, and assert a non-zero result (and a zero one for
the negative twin). That seam between the two halves of the fix is where a
hand-written content string would have hidden a break.

Two mutations were run to check the tests bite. Making the gate accept any
viewport-fit rather than only cover fails the auto/contain test. Changing
how _applyViewportMeta joins its descriptors from ', ' to ';' leaves the
provider suite green — which is the point of sharing the parser, and would not
have been true of an earlier revision of this patch that matched the joined
string exactly.

Not covered, left for follow-up

  • Embedded and multi-view apps still get zero. The engine could derive their
    insets from the intersection of the host element's bounding rect with the
    page's env() values; there is a TODO on
    CustomElementDimensionsProvider.computeSafeAreaInsets for it. Issue [web][PWA] SafeArea Widget does not work on iOS Safari PWA #84833
    should stay open until that lands, which is why this PR references the issue
    rather than closing it.
  • Safe-area changes that do not resize the viewport are not observed.
    _viewPadding is refreshed only from _handleBrowserResize. On iOS the
    status bar grows during a call, screen recording or personal hotspot without
    changing the viewport size, so env(safe-area-inset-top) changes with no
    resize event. A ResizeObserver on a probe whose top/left/width/
    height are the env() values would close this; the limitation is documented
    on both DimensionsProvider.computeSafeAreaInsets and
    EngineFlutterView._viewPadding.
  • viewport-fit injected from JavaScript after bootstrap is not re-read.
    _applyViewportMeta runs once, in the strategy's constructor.
  • systemGestureInsets stays zero; browsers do not expose the information.
  • env(safe-area-inset-top) has unit coverage but no on-device coverage.
    It was zero in all 11 measured configurations, standalone PWAs included, and I
    have no explanation for that. It should be measured on a configuration that
    does report a non-zero top inset before this ships.
  • Docs. A companion flutter/website change should document the
    viewport-fit=cover opt-in on the web display-modes/initialization page and
    add a web caveat to the SafeArea API docs.

Pre-launch Checklist

Notes on the checklist:

  • On the breaking change policy: the change is gated so that no app that did not
    declare viewport-fit=cover sees any difference in MediaQuery.padding,
    MediaQuery.viewPadding or its viewport meta tag. Apps that did declare it
    move from a safe-area-contained layout to a full-bleed one, with the measured
    numbers above. customer_testing / flutter/tests should be run against this
    branch before landing, and if anything moves, a migration guide from the
    flutter/website template should land with it.
  • No Data Driven Fixes apply: there is no API rename or removal on the framework
    side.

@github-actions github-actions Bot added tool Affects the "flutter" command-line tool. See also t: labels. engine flutter/engine related. See also e: labels. f: scrolling Viewports, list views, slivers, etc. platform-web Web applications specifically team-web Owned by Web platform team labels Aug 25, 2026

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

Copy link
Copy Markdown
Contributor

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 support for safe area insets on Flutter Web by reading CSS env(safe-area-inset-*) values via a DOM probe when the viewport is configured with viewport-fit=cover. The changes include updating EngineFlutterView to lazily compute and cache viewPadding, preserving viewport-fit in FullPageEmbeddingStrategy, and adding associated tests and templates. Review feedback highlights a potential issue where parseFloat returning NaN for empty strings could propagate into ViewPadding, a parsing bug in parseViewportContent when spaces surround the = character, and opportunities to replace null-assertions on domDocument.body with null-safe optional chaining to prevent runtime exceptions.

Comment on lines +155 to +161
double inset(String property) {
// A browser that doesn't understand `env()` leaves the padding at its
// initial value, which reads back as `0px`, so there is no separate code
// path for it. `parseFloat` only returns null for an empty string, which
// can't happen for an element that is in the document.
return (parseFloat(probeStyle.getPropertyValue(property)) ?? 0.0) * devicePixelRatio;
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

high

The assumption that parseFloat returns null for an empty string is incorrect. In JavaScript, parseFloat("") returns NaN. When compiled to Dart, NaN is a valid double and is not null, so the null-coalescing operator ?? 0.0 will not be triggered, resulting in NaN propagating into ViewPadding. This can cause layout assertions and crashes in the Flutter framework.

Instead, explicitly check if the parsed value is null or isNaN before multiplying by devicePixelRatio.

    double inset(String property) {
      final double? value = parseFloat(probeStyle.getPropertyValue(property));
      return (value == null || value.isNaN) ? 0.0 : value * devicePixelRatio;
    }

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

This isn't JavaScript's parseFloat — it's web_ui's wrapper in safe_browser_api.dart, which turns NaN into null before returning:

final double result = parseFloatImpl(source);
if (result.isNaN) {
  return null;
}

So ?? 0.0 does fire for the empty string, and NaN can't reach ViewPadding.

The comment was wrong, though: it said null only comes back for an empty string, when it comes back for anything unparseable. Reworded.

// The host page owns <body>, and is free to empty it, e.g. to take a
// custom loading indicator down. `getComputedStyle` of a detached element
// resolves to nothing, so put the probe back before measuring it.
domDocument.body!.append(safeAreaProbe);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

Avoid using the null-assertion operator ! on domDocument.body here as well. If the body element is temporarily unavailable or removed, this will throw a runtime exception. Use the null-safe optional chaining operator ?. instead.

Suggested change
domDocument.body!.append(safeAreaProbe);
domDocument.body?.append(safeAreaProbe);

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Same as the one above. This is also the path that puts the probe back when the host page has emptied <body> — skipping silently here would leave it detached, and getComputedStyle on a detached element resolves to nothing, so every inset would read zero.

`SafeArea` has always been a no-op on the web because nothing ever fed
the device safe area into the framework. Two things were missing.

1. `FullPageEmbeddingStrategy` removed every `<meta name="viewport">` on
   the page and wrote its own without `viewport-fit`. An app that opted
   into a full-bleed layout with `viewport-fit=cover` had that opt-in
   deleted at startup, and WebKit then kept laying the app out inside the
   safe area, so `env(safe-area-inset-*)` had nothing to report. This is
   why the workarounds in the issue never worked. The `viewport-fit` of
   the last viewport meta tag on the page, which is the one the browser
   itself honours, is now carried over to the tag that the engine writes,
   and, as a result, across hot restarts too. The "this tag will be
   replaced" warning now names the descriptors that are actually being
   dropped, and stays quiet when none are, so an app following the web
   template's advice no longer provokes a warning on every debug run.

2. Nothing in `web_ui` read `env(safe-area-inset-*)`. `DimensionsProvider`
   gains `computeSafeAreaInsets()`. `FullPageDimensionsProvider`
   implements it by reading the values back from a `display: none` probe
   element whose paddings are set to `env(...)`; the browser resolves
   `env()` at computed-value time, so no layout or paint is involved.
   `CustomElementDimensionsProvider` reports zero, because the host
   application is the one that positions an embedded view on the page.

`EngineFlutterView.viewPadding` is now backed by those insets, computed
lazily and recomputed on the same events that recompute the physical
size, and `padding` is `viewPadding` minus `viewInsets`, clamped at zero,
matching the other platforms: when the on-screen keyboard covers the
bottom safe area, the bottom padding goes to zero.

Parsing a `viewport-fit` descriptor lives in one new file,
`view_embedder/viewport_fit.dart`, used both by the strategy that writes
the tag and by the provider that reads it back, so that the two sides
cannot drift apart over the exact spelling of a `content` attribute.

The insets are only reported for a page that declared `viewport-fit=cover`
itself. That is a stricter rule than the one the browser applies to
`env()`: on an iPhone 17 Pro (iOS 18.7, WebKit 605.1.15, Safari 26.1)
`env(safe-area-inset-bottom)` is `20px` in landscape whether or not the
page is full-bleed, and passing that on would have moved the content of
every existing Flutter web app on iOS, including the ones that ask for
nothing. A page that never declared `viewport-fit` keeps a byte-identical
viewport meta tag and a `viewPadding` of `ViewPadding.zero`, exactly as
before this change.

Apps that do declare `viewport-fit=cover` in their `index.html` change,
and that is the point of the fix: the browser stops keeping them within
the safe area. On the device above, a Flutter page in landscape reports
`window.innerWidth` of 750 against 874 for a plain HTML control that
declares the same tag, a difference of exactly the `62 + 62` of left and
right inset the control reports. Content that is not wrapped in a
`SafeArea` moves under the display cutout and the rounded corners, which
is what `viewport-fit=cover` asks the browser for.

The post-change behaviour is covered by unit tests; it has not been
measured on a device, and `env(safe-area-inset-top)` was zero in every
device reading taken, so the top axis in particular rests on unit
coverage alone.

The `flutter create` web template gains the tag, commented out, next to
the guidance it already carries for `<base href>`.

`systemGestureInsets` is deliberately left at zero. `env()` describes the
parts of the viewport obstructed by the device, not the parts the system
reserves for its own gestures, and browsers do not expose the latter.

This covers full-page embeddings only. A view hosted in a custom element
still reports zero, so the issue stays open for embedded and multi-view
apps.

See flutter#84833
@diegolopezrm

Copy link
Copy Markdown
Contributor Author

Thanks — one of these was a real bug and is now fixed. Taking them in turn.

parseViewportContent dropping descriptors spaced around = — correct, and
fixed in the latest push. This mattered more than the report suggests: the same
file's readViewportFit deliberately tolerates viewport-fit = cover, so the
two functions disagreed about what a content attribute says, which is exactly
the kind of asymmetry this PR exists to remove. The content is now closed up with
RegExp(r'\s*=\s*') before it is split, and there is a test asserting
parseViewportContent('width = device-width, user-scalable = no') parses both
descriptors. I confirmed it failed before the change (Actual: {}).

parseFloat returning NaN — this one doesn't apply. The call is not
JavaScript's global parseFloat; it is web_ui's Dart wrapper in
safe_browser_api.dart, which exists specifically to normalise that:

double? parseFloat(String source) {
  final double result = parseFloatImpl(source);
  if (result.isNaN) {
    return null;
  }
  return result;
}

So NaN arrives as null and ?? 0.0 does fire. No NaN can reach
ViewPadding through this path.

domDocument.body! → domDocument.body?. — I'd rather keep the assertion,
in both places. In full-page mode the <body> is the host element:
FullPageEmbeddingStrategy opens with final DomElement hostElement = domDocument.body! for that reason, and there are eight such uses across the
engine. Optional chaining wouldn't prevent a failure here, it would relocate it:
the probe would silently not be appended, and getComputedStyle on a detached
element resolves to nothing, so computeSafeAreaInsets() would quietly return
zero insets on every call instead of throwing. A safe area that is silently
always zero is the bug this PR is fixing, so failing loudly is the behaviour I
want. That goes double for the second site, which is the re-attachment path — its
whole purpose is to put the probe back when the host page has removed it.

Full engine suite is green: 1232 passing, 14 skipped, no regressions.

@flutter-zl flutter-zl added the CICD Run CI/CD label Sep 9, 2026
@flutter-zl
flutter-zl requested a review from mdebbar September 9, 2026 18:28
@flutter-dashboard flutter-dashboard Bot removed the CICD Run CI/CD label Sep 10, 2026
The comment claimed it only returns null for an empty string. The wrapper
in safe_browser_api.dart returns null wherever JavaScript's parseFloat
returns NaN, which is a wider set, and the `?? 0.0` covers all of it.
@diegolopezrm

Copy link
Copy Markdown
Contributor Author

Heads up: the CICD label came off when I pushed a comment-only fix yesterday — the dashboard bot drops it on every push — so Awaiting CICD label is still holding the presubmit tests back and Dashboard Checks hasn't started.

The ci.yaml validation failure that was here before is gone. It was the branch sitting 76 commits behind master, which had renamed a target the stale .ci.yaml still listed; merging master cleared it.

Re-adding the label whenever convenient is all it needs.

@mdebbar mdebbar added the CICD Run CI/CD label Sep 22, 2026
@mdebbar

mdebbar commented Sep 22, 2026

Copy link
Copy Markdown
Contributor

Thank you for contributing this fix!

@flutter-zl
flutter-zl self-requested a review September 22, 2026 17:07
@diegolopezrm

Copy link
Copy Markdown
Contributor Author

Thanks for the review, @mdebbar. Everything is green now — Google testing, Dashboard Checks and the merge queue guard all passed. Anything left from my side, or does it just need the autosubmit label?

@flutter-zl flutter-zl left a comment •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Image Image Image Image

LGTM. I verified this on a real device, IOS 27, with before(https://flutter-demo-63-before.web.app/)/after(https://flutter-demo-63-after.web.app/) builds of the same app. The app's index.html declares viewport-fit=cover and apple-mobile-web-app-status-bar-style=black-translucent. Both builds also draw a browser-rendered outline inset by env(safe-area-inset-*), so it's easy to see whether SafeArea lines up with what the browser reports.

@flutter-zl flutter-zl added the autosubmit Merge PR when tree becomes green via auto submit App label Sep 22, 2026
@auto-submit
auto-submit Bot added this pull request to the merge queue Sep 22, 2026
Merged via the queue into flutter:master with commit 0748e48 Sep 23, 2026
29 checks passed
@flutter-dashboard flutter-dashboard Bot removed the autosubmit Merge PR when tree becomes green via auto submit App label Sep 23, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

CICD Run CI/CD engine flutter/engine related. See also e: labels. f: scrolling Viewports, list views, slivers, etc. platform-web Web applications specifically team-web Owned by Web platform team tool Affects the "flutter" command-line tool. See also t: labels.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants