Repository navigation
fix(🤖): move a SurfaceView's media overlay in place only where Android can - #578
YevheniiKotyrlo wants to merge 4 commits into
Conversation
… React Native Testing Library The Jest mock's Canvas built its ref handle on every render (useImperativeHandle without dependencies), so a ref got a new handle after each re-render, and a state setter used as the ref re-rendered its owner until React stopped with "Maximum update depth exceeded". It builds the handle once, as Canvas does. Every spec under src/__tests__ goes through the e2e harness, which waits for the example app, so nothing rendered a component under Jest. jest.unit.config.js runs src/__tests__/unit with React Native's Jest preset and React Native Testing Library 14 on test-renderer, React 19's replacement for react-test-renderer; CI runs it beside the plugin's tests. The Canvas spec drives a stand-in RNWebGPU module and View's measureLayout, and the mock's spec holds its handle and its loud failures. On a device, Canvas.spec.ts checks a canvas drawn at half its layout size under a scale transform: its context is sized from the layout, and its ref handle is the same after re-renders. setup.ts's isNativeApp() replaces the specs' inline platform checks, and WebPolyfill.spec.ts covers the web polyfill's native surface.
…d can - zOrderMediaOverlay applies to a live surface only from Android 14: up to Android 13, SurfaceView.setZOrderMediaOverlay() sets the sublayer the next surface is created with, and updateSurface() never re-applies it. The in-place gate is per prop (BackingViewKind), the change is ignored with a warning before 14, and the example remounts on it there. - On Android 13 a media overlay reads zOrderOnTop at mount as well: its SurfaceView moves a surface leaving the top to the default sublayer at once, before setZOrderMediaOverlay() asks for its own, so the canvas would come back among the other SurfaceViews rather than above them. Android 11 and 12 apply the sublayer with the next frame, and 14 re-applies it. - A z-order change is applied once per prop transaction, with the opacity: WebGPUSurfaceView keeps the z-order it last asked of SurfaceView and asks only for a change. - AndroidCanvasProps declares every option on every view, `never` where it does not apply, so a SurfaceView option without backingView no longer compiles, and Canvas resolves the union with one exhaustive switch instead of a cast. AndroidBackingView and the web spec's props derive from the codegen spec. - The development warnings follow each release's rule, read the props as the native side receives them, and say when a JavaScript caller hands the z-order options to a view that ignores them.
…o Android 12 Up to Android 12 (API 31) a SurfaceView removes its layer before the window frame without it lands, so an unmounting SurfaceView canvas can leave black where it was for a frame; from 12L the removal lands with the window's next frame. With the backing view named by the app, the docs say so where they describe the SurfaceView.
The z-order of a mounted media overlay — before / afterOn x86_64 emulator images, release builds of the example with a probe screen, the same JavaScript over
A bold cell is a media overlay left in the default sublayer, where the other canvas covers it. On Android 13 that happens as soon as it leaves the top: before — after — with this PR With this PR a change Android can't apply to the live surface is ignored with a warning, and a remount takes it, as the example's key does; everywhere else it moves in place, as on <View style={{ flex: 1 }}>
<Canvas
ref={red}
style={{ position: "absolute", left: "20%", right: "20%", top: "30%", bottom: "30%" }}
android={{ backingView: "SurfaceView", zOrderMediaOverlay: mediaOverlay, zOrderOnTop: onTop }}
/>
<Canvas ref={green} style={StyleSheet.absoluteFill} android={{ backingView: "SurfaceView" }} />
{/* two buttons toggling onTop and mediaOverlay */}
</View>CI's Android e2e step on an API 35 emulator: 226 of 228 here, the two YUV cases |
…and say so A backingView value no view takes resolves to the default TextureView, and now keeps the opacity the android prop asks for, as main does; in development the canvas names the unknown value in a warning. The native props of a view without a z-order of its own come from one builder. The tests also warn about the z-order options on a HardwareBufferView, and mount one canvas per z-order change case.


Problem
Follow-ups to #574, the same ones wcandillon/react-native-skia#4266 makes to the Skia side; the first four fail on
mainand pass here:zOrderMediaOverlaydoesn't move a mounted canvas before Android 14, and nothing warns.takesZOrderlets both z-order props through from Android 11, but up to 13SurfaceView.setZOrderMediaOverlay()only writes the sublayer the next surface is created with, andupdateSurface()re-applies it only when it creates one (android-13); 14 compares the requested sublayer with the applied one (android-14). Its Javadoc asks for it before the window is attached.SurfaceViews.setZOrderOnTop(false)applies the default sublayer to the live surface at once (android-13), beforesetZOrderMediaOverlay(true)asks for its own. 11 to 12L apply it from a frame callback that reads the sublayer when it runs (android-12), and 14 re-applies the changed request.android={{ opaque: false, zOrderOnTop: true }}compiles and gives a translucentTextureView: with nobackingViewthe union can't pick a member, so its excess-property check accepts a key any member declares.backingViewno view takes falls back to aTextureViewsilently: a JavaScript caller's typo gets the default view with nothing in development to say so.SurfaceViewcanvas can leave black as it unmounts up to Android 12 (API 31): it removes its layer before the window frame without it lands. Since feat(🤖): name the Android backing view explicitly #574 aSurfaceViewis one the app names, so the app needs to know.Solution
BackingViewKindhas one in-place rule per z-order prop:zOrderOnTopfrom Android 11, except on a media overlay on 13, andzOrderMediaOverlayfrom 14. A change the rule refuses is ignored with a warning, like abackingViewchange; the JS warnings, the example's key and the docs follow, and the example gains a "surface media overlay on top" option.WebGPUSurfaceView.setZOrder()keeps the z-order it last asked ofSurfaceViewand asks only for a change, andWebGPUViewhands it the props once per transaction, inupdateView()with the opacity, so a change of both props moves the surface once.AndroidCanvasPropsdeclares every option,neverwhere it doesn't apply;Canvasresolves the union in one exhaustive switch, without theas AndroidOptionscast, andAndroidBackingViewand the web spec's props derive from the codegen spec.backingViewno view takes; the canvas keeps the defaultTextureViewwith the opacity asked for.SurfaceViewparagraph of the canvas page carries the unmount caveat.Tests
src/__tests__/unit/android.test.tsx, in #529's unit tier: the union's type rules, the native props eachandroidvalue resolves to, and the development warnings at mount and on a change, for each z-order prop on API 29, 30, 33 and 34 and forzOrderOnTopon a media overlay on API 30, 32, 33 and 34. Run against #529's head they fail for each defect: TS2322 on the union, and 6 of 29 cases (no warning for a media overlay change on API 30 and 33, forzOrderOnTopon a media overlay on API 33, for the z-order options on aTextureViewor aHardwareBufferView, or for abackingViewno view takes).BackingViewKind's rules have no JVM test here: the library's Java gets a host tier with #561.Verification
yarn lint·yarn tsc· package build · the Expo template's web build ·yarn test:unit38 passed ·yarn test:plugin10 passed ·yarn semantic-release --dry-run·yarn build:docs·yarn check-dawn· cpplint · CI's Android e2e step: on an Android 15 emulator, 226 of 228, the two YUV cases that fail onmainon that emulator too; on the revision before the last commit, which changesCanvas.tsxalone, on a Galaxy S23 (Android 14), 228 of 228, and on a Mi MIX 3 (Android 10), 200 of 200 without the AHardwareBuffer import suites, whose first import loses the device on that phone's Adreno 630 driver onmaintoo. CI's iOS e2e step, a Debug build against Metro, on an iPhone 12 Pro Max (iOS 27.0.1): 228 of 228.On Android 11, 12L, 13 and 14 emulators, a media overlay canvas over a second
SurfaceViewcanvas, its z-order props changed in place (evidence comment): onmain, the backdrop covers it once the media overlay is turned back on (11 to 13) or once it leaves the top (13). Here it keeps the z-order it mounted with, with a warning, wherever Android can't move it, and moves in place everywhere else, 14 included.Related
Stacked on #529, whose unit tier holds the tests; the Skia side is wcandillon/react-native-skia#4266.
Base
Cut from
mainatc080934e: #529's re-cut, then three commits (the fix, the docs caveat, and the opacity and warning for abackingViewno view takes); it merges cleanly withmainatdbb17185.