Skip to content

fix: measure a window resize on web once for every provider - #788

Open
YevheniiKotyrlo wants to merge 3 commits into
appandflow:mainfrom
YevheniiKotyrlo:fix/web-measure-after-layout
Open

YevheniiKotyrlo wants to merge 3 commits into
appandflow:mainfrom
YevheniiKotyrlo:fix/web-measure-after-layout

Conversation

@YevheniiKotyrlo

@YevheniiKotyrlo YevheniiKotyrlo commented Oct 15, 2026 •

Copy link
Copy Markdown

Summary

On web, every SafeAreaProvider measures a window resize from a resize listener of its own, and React commits the updates made in each listener separately, so nested providers commit once each per resize step. Each provider's ResizeObserver then measures again after layout and reports the values its listener already reported, which React renders once more. Two nested providers, as an Expo Router app mounts them beside an app shell's own provider, commit three times per step.

One resize listener now measures every provider, so React commits their updates together, and a provider reports only metrics that changed, as the Android and iOS providers do, so the observer callback that follows a resize commits nothing. The measurement stays in the resize event, so a provider that only moves is still measured and the new metrics still paint in the resize's own frame.

Test Plan

  • NativeSafeAreaProvider.web-test.tsx: "measures nested providers from one window resize listener" and "does not report metrics that did not change" fail on main and pass here. "measures a window resize that moves the provider without resizing it" and "reports a window resize that changes the insets and leaves the frame as it was" hold the resize-event measurement for a provider that only moves and for an insets-only change.
  • With two nested providers, a 190-step window resize drops from 570 React commits to 191 in Chromium, Firefox and WebKit, and both builds paint every step's new metrics in its own frame (measurements).
  • The JS job passes on Ubuntu (Node 22.13.0, npm 10.9.2, JDK 17 for format:spotless:check).

…ze event

The resize listener ran before the frame's layout, so reading the provider
element's rect there forced a synchronous layout, and when the provider
resized with the window its ResizeObserver reported the same values again
once layout had run. The context element now spans the viewport and the
same observer watches it, so a window resize is measured once, after
layout. Without a ResizeObserver the provider keeps the window resize
listener.
@vercel

vercel Bot commented Oct 15, 2026

Copy link
Copy Markdown

@YevheniiKotyrlo is attempting to deploy a commit to the appandflow Team on Vercel.

A member of the Team first needs to authorize it.

@YevheniiKotyrlo
YevheniiKotyrlo marked this pull request as ready for review October 15, 2026 16:18
@YevheniiKotyrlo
YevheniiKotyrlo marked this pull request as draft October 15, 2026 16:47
…rve a border box

Chrome before 84 and Safari before 15.4 ignore the box option and observe
the content box, which the context element's env() padding transition
resizes, so those engines would report the insets at every step of the
transition. They keep the window resize listener, as an engine without
ResizeObserver does.
@YevheniiKotyrlo

YevheniiKotyrlo commented Oct 15, 2026 •

Copy link
Copy Markdown
Author

Measurements — before / after

UNFIXED — Over a 190-step window resize, two nested providers commit React 570 times, three per step: once in each provider's resize listener, and once more in the next frame, when their ResizeObservers report the values the listeners already reported.

FIXED — 191 commits, one per step and one more, from a single window resize listener.

React commits window resize listeners steps painting their new metrics in the resize's own frame
before — stock 5.10.1 570 2 190 of 190
after — with this PR 191 1 190 of 190

Chromium 153, Firefox 155 and WebKit 26.6, two runs each, read the same. Two nested SafeAreaProviders on react-native-web, as an Expo Router app mounts them beside an app shell's own provider, narrowed from 1280 to 520 px in 4 px steps; the harness builds both from the library's source and checks each page's resize listeners against the build it claims. Chromium counts 570 layouts over the sweep for both builds.

@YevheniiKotyrlo
YevheniiKotyrlo marked this pull request as ready for review October 15, 2026 18:27
@YevheniiKotyrlo
YevheniiKotyrlo marked this pull request as draft October 16, 2026 07:54
…changes

Measuring a window resize from a ResizeObserver alone painted the new
metrics a frame after the resize, because React commits an update made in
an observer callback after paint, and it missed a resize that only moves
the provider once a transform on body narrowed the viewport-spanning
context element.

The resize event stays where a window resize is measured. One listener
measures every provider, so React commits their updates together, and a
provider reports only metrics that changed, as the native providers do,
so the ResizeObserver callback that follows a resize commits nothing.
@YevheniiKotyrlo YevheniiKotyrlo changed the title fix: measure a window resize on web after layout, not inside the resize event fix: measure a window resize on web once for every provider Oct 16, 2026
@YevheniiKotyrlo
YevheniiKotyrlo marked this pull request as ready for review October 16, 2026 08:29

This branch has not been deployed

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant