Compare commits

...
Author SHA1 Message Date
CodemodService BotandFacebook GitHub Bot 295d4f18a0 Fix CQS signal modernize-use-nullptr in xplat/js/react-native-github/packages/react-native/ReactCommon/react/renderer/components/textinput/platform/android/react/renderer/components/androidtextinput
Reviewed By: javache

Differential Revision: D77010402
2025-06-20 03:44:18 -07:00
Mateo GuzmánandFacebook GitHub Bot a44098ea27 Kotlin: clean up redundant visibility modifiers (2/2) (#52139)
Summary:
Follow up from https://github.com/facebook/react-native/issues/51960, this PR cleans up the remaining warnings for redundant visibility modifiers detected by static code analysis.

## Changelog:

[INTERNAL] - Kotlin: clean up redundant visibility modifiers (2/2)

Pull Request resolved: https://github.com/facebook/react-native/pull/52139

Test Plan:
```sh
yarn android
yarn test-android
```

Reviewed By: cortinico

Differential Revision: D76997415

Pulled By: sbuggay

fbshipit-source-id: 77244dc83f8ecbf90cf3ade0f299930871ab3ae9
2025-06-20 00:00:28 -07:00
Arushi KesarwaniandFacebook GitHub Bot 0f7bf66bba Adding shouldForwardToReactInstance check in ReactDelegate for Bridgeless (#52112)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/52112

Adding `shouldForwardToReactInstance` check in Bridgeless since it was missed in https://github.com/facebook/react-native/pull/43351

**Changelog:**
[ANDROID][FIXED] - Adding `shouldForwardToReactInstance` check in ReactDelegate for Bridgeless

Reviewed By: cortinico, javache

Differential Revision: D76908041

fbshipit-source-id: 20b8fce248d8e560ab862cf325b6f8b15be870e3
2025-06-19 18:37:50 -07:00
Sophie LandFacebook GitHub Bot 14a213229a fix: improve the grammar/clarity of the stale bot comments (#52124)
Summary:
this PR makes the stale bot messages a bit clearer and fixes a grammatical issue.

# Changelog:

[INTERNAL] [FIXED] Tweak stale bot messages

Pull Request resolved: https://github.com/facebook/react-native/pull/52124

Test Plan: N/A

Reviewed By: andrewdacenko

Differential Revision: D76969503

Pulled By: cortinico

fbshipit-source-id: d030a0488b44521f61447e252bae5ded10826dbb
2025-06-19 10:42:05 -07:00
Nicola CortiandFacebook GitHub Bot 4223285a23 Fix broken LayoutableShadowNodeTest after Modal fix. (#52130)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/52130

After D73948178, Modals now need to access JNI to get the dimension of the
screen to properly position the modal on first rendering.

Before my change, the Modal was positioned in 0,0 (which is the default
behavior for CXX).
I'm suppressing this test for Android, as it will keep on running with the
previous behavior for CXX.

Changelog:
[Internal] [Changed] -

Reviewed By: javache

Differential Revision: D76979787

fbshipit-source-id: 78675712f97baee29036f943b2a8bcd23047e4ed
2025-06-19 10:31:53 -07:00
Nicola CortiandFacebook GitHub Bot 55b87a5293 Make PointerEvent internal (#52131)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/52131

This class is not used in OSS and can be made internal.

Changelog:
[Internal] [Changed] -

Reviewed By: javache

Differential Revision: D76979122

fbshipit-source-id: 2d81e3605e6c51336b3bdb2671dd9faf8f25639b
2025-06-19 09:59:27 -07:00
Nicola CortiandFacebook GitHub Bot 0cf2985d27 Convert PointerEvent to Kotlin (#52132)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/52132

This is yet another class that is getting converted from Java to Kotlin.

Changelog:
[Internal] [Changed] -

Reviewed By: javache

Differential Revision: D76979123

fbshipit-source-id: d1fc54e61d64b78a56cf0198ae5fe588702a8698
2025-06-19 09:59:27 -07:00
nishan (o^▽^o)andFacebook GitHub Bot 2f3b104224 - Use CAGradientLayer for linear gradient (#52096)
Summary:
This PR replaces Core Graphics implementation with Core Animation for linear gradients. I came across a great [solution](https://stackoverflow.com/questions/38821631/cagradientlayer-diagonal-gradient/43176174#43176174) that makes the `CAGradientLayer`'s start and end point behaviour CSS spec compliant. This will make gradients much more performant.

## Changelog:

[IOS] [CHANGED] - Optimised Linear Gradients.
<!-- Help reviewers and the release process by writing your own changelog entry.

Pick one each for the category and type tags:

[ANDROID|GENERAL|IOS|INTERNAL] [BREAKING|ADDED|CHANGED|DEPRECATED|REMOVED|FIXED|SECURITY] - Message

For more details, see:
https://reactnative.dev/contributing/changelogs-in-pull-requests

Pull Request resolved: https://github.com/facebook/react-native/pull/52096

Test Plan:
Non breaking change. Test Linear gradient example from RNTester. Compare results with web, android and iOS. Each platform should render the gradients identically.

## Note:

I will be doing a PR to use `CAGradientLayer` for radial gradients as well. The next properties that I have locally working are `background-size`, `background-position` and `background-repeat`. These will be addressed in small PRs.

Reviewed By: NickGerleman

Differential Revision: D76905215

Pulled By: javache

fbshipit-source-id: 0094bdf70869d619272d491dd496983316b0dbf0
2025-06-19 09:50:45 -07:00
Pieter De BaetsandFacebook GitHub Bot 6a3116a8a9 Add screenshot tests for LinearGradient (#52128)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/52128

Changelog: [Internal]

Reviewed By: andrewdacenko

Differential Revision: D76972006

fbshipit-source-id: 657b78a842578df46f81d2416726665f49655a4f
2025-06-19 09:50:45 -07:00
Riccardo CipolleschiandFacebook GitHub Bot 432ad09c41 Remove usage of SafeAreaView from RNTester (#52129)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/52129

This remove all the usages of SafeAreaView from RNTester.
The problem is that we introduced a warning that SafEAreaView is deprecated and, therefore, we had a warning in debug mode.

This was causing a yellow bubble to appear and the OSS E2E test started failing.

## Changelog:
[Internal] -

Reviewed By: cortinico

Differential Revision: D76978227

fbshipit-source-id: c45a31bae1602bc307e4fbbd71e7987a8ed78858
2025-06-19 09:29:40 -07:00
Nicola CortiandFacebook GitHub Bot b950fa2afb Fix Modal first frame being rendered on top-left corner (#51048)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/51048

Fixes https://github.com/facebook/react-native/issues/50442
Closes https://github.com/facebook/react-native/pull/50704

Users reported that Modals on Android are first renderer anchored in 0,0.
That results in them being on the top left corner of the screen for some seconds.

This is happening because the native state of the Modal on Android as width/height set at 0,0 - which we then update in a subsequent callback.

I'm fixing this by making sure we render the Modal the first time with the right screen size - the status bar size

Changelog:
[Android] [Fixed] - Fix Modal first frame being rendered on top-left corner

Reviewed By: javache

Differential Revision: D73948178

fbshipit-source-id: 055c12aa62d70acc1e4c5a2a5c4ea0c5608e22c7
2025-06-19 06:21:16 -07:00
Ruslan LesiutinandFacebook GitHub Bot 52df5dee91 Update debugger-frontend from 68cfd0a...d95ac13 (#52119)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/52119

Changelog: [Internal] - Update `react-native/debugger-frontend` from 68cfd0a...d95ac13

Resyncs `react-native/debugger-frontend` from GitHub - see `rn-chrome-devtools-frontend` [changelog](https://github.com/facebook/react-native-devtools-frontend/compare/68cfd0ae84acb0ed8e47b421afd64ae3b0b5b727...d95ac13bf0ab64a5e6c2eb18eb138587063b9c34).

### Changelog

| Commit | Author | Date/Time | Subject |
| ------ | ------ | --------- | ------- |
| [d95ac13bf](https://github.com/facebook/react-native-devtools-frontend/commit/d95ac13bf) | Ruslan Lesiutin (rdlesyutin@gmail.com) | 2025-06-18T12:35:02+01:00 | [fix: hide workplace suggest on Sources placeholder element (#180)](https://github.com/facebook/react-native-devtools-frontend/commit/d95ac13bf) |

Reviewed By: huntie

Differential Revision: D76919604

fbshipit-source-id: 55aa28f9fc9288df71019e8ca9001b35863395b1
2025-06-19 05:08:55 -07:00
Mateo GuzmánandFacebook GitHub Bot 50ea5b4380 Migrate ReactDelegate to Kotlin (#52024)
Summary:
Migrate com.facebook.react.ReactDelegate to Kotlin.

## Changelog:

[ANDROID][BREAKING] - Migrate com.facebook.react.ReactDelegate to Kotlin. Some users implementing this class in Kotlin could have breakages.

Pull Request resolved: https://github.com/facebook/react-native/pull/52024

Test Plan:
```bash
yarn test-android
yarn android
```

Reviewed By: javache

Differential Revision: D76740335

Pulled By: cortinico

fbshipit-source-id: d9ad948a71438070500685042c067f621fd3ea26
2025-06-19 04:33:53 -07:00
Nicola CortiandFacebook GitHub Bot 184664d47b Fix support for react.internal.useHermesNightly (#52127)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/52127

Currently using `react.internal.useHermesNightly` is broken locally because we try to search for versions such as 0.0.0.+ while the nightlies version are of the form 0.81.0-...

This fixes it.

Changelog:
[Internal] [Changed] -

Reviewed By: NickGerleman

Differential Revision: D76901197

fbshipit-source-id: 51f7b2e7ec936aace67d4d62a8019554800347fc
2025-06-19 03:51:52 -07:00
Mateo GuzmánandFacebook GitHub Bot 2b75479fa2 Migrate to Kotlin and internalize MountItemDispatcher (#52121)
Summary:
Migrate to Kotlin and internalize com.facebook.react.fabric.mounting.MountItemDispatcher.

This class has [no relevant OSS usages](https://github.com/search?type=code&q=NOT+is%3Afork+NOT+org%3Afacebook+NOT+repo%3Areact-native-tvos%2Freact-native-tvos+NOT+repo%3Anuagoz%2Freact-native+NOT+repo%3A2lambda123%2Freact-native+NOT+repo%3Abeanchips%2Ffacebookreactnative+NOT+repo%3AfabOnReact%2Freact-native-notes+NOT+user%3Ahuntie+NOT+user%3Acortinico+NOT+repo%3AMaxdev18%2Fpowersync_app+NOT+repo%3Acarter-0%2Finstagram-decompiled+NOT+repo%3Am0mosenpai%2Finstadamn+NOT+repo%3AA-Star100%2FA-Star100-AUG2-2024+NOT+repo%3Alclnrd%2Fdetox-scrollview-reproductible+NOT+repo%3ADionisisChytiris%2FWorldWiseTrivia_Main+NOT+repo%3Apast3l%2Fhi2+NOT+repo%3AoneDotpy%2FCaribouQuest+NOT+repo%3Abejayoharen%2Fdailytodo+NOT+repo%3Amolangning%2Freversing-discord+NOT+repo%3AScottPrzy%2Freact-native+NOT+repo%3Agabrieldonadel%2Freact-native-visionos+NOT+repo%3AGabriel2308%2FTestes-Soft+NOT+repo%3Adawnzs03%2FflakyBuild+NOT+repo%3Acga2351%2Fcode+NOT+repo%3Astreeg%2Ftcc+NOT+repo%3Asoftware-mansion-labs%2Freact-native-swiftui+NOT+repo%3Apkcsecurity%2Fdecompiled-lightbulb+com.facebook.react.fabric.mounting.MountItemDispatcher) so it can be internalized as well as it exposes some other internal classes and we don't want to make those public again.

## Changelog:

[ANDROID][CHANGED] - Migrate to Kotlin and internalize com.facebook.react.fabric.mounting.MountItemDispatcher

Pull Request resolved: https://github.com/facebook/react-native/pull/52121

Test Plan:
```bash
yarn test-android
yarn android
```

Reviewed By: cortinico, rshest

Differential Revision: D76967018

Pulled By: javache

fbshipit-source-id: def4be448db15705fc11e50e3fe8f9e532d973bb
2025-06-19 03:48:27 -07:00
Rubén NorteandFacebook GitHub Bot 2a52ee8ddb Clean up feature flag enableIntersectionObserverEventLoopIntegration (#52102)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/52102

Changelog: [internal]

This ships the variant of IntersectionObserver that integrates directly with the Event Loop, avoiding dispatching notifications during observation and waiting for the end of the Event Loop tick instead. Also cleans up all the associated feature flags.

Reviewed By: lenaic

Differential Revision: D76892649

fbshipit-source-id: 9364b43a4d60b75c25b9a2d6ced7937b03376b04
2025-06-19 02:48:39 -07:00
Mateo GuzmánandFacebook GitHub Bot 1d945629bf Clean up incorrect @VisibleForTesting annotation usages (#52025)
Summary:
Static code analysis reports 18 warnings for incorrect usages of the `VisibleForTesting` annotation as some of the classes/functions/properties that are annotated are not used only in tests but also in other non-test files across the codebase. This PR cleans that up to fix those warnings.

## Changelog:

[INTERNAL] - Clean up incorrect VisibleForTesting annotation usages

Pull Request resolved: https://github.com/facebook/react-native/pull/52025

Test Plan:
```sh
yarn test-android
yarn android
```

Reviewed By: rshest

Differential Revision: D76745241

Pulled By: sbuggay

fbshipit-source-id: 4702a7258002916cc95c178dc8931c8bb471f7bc
2025-06-18 22:25:09 -07:00
Mateo GuzmánandFacebook GitHub Bot 7885508f9b Migrate ReactTextShadowNode to Kotlin (#52116)
Summary:
Migrate com.facebook.react.views.text.ReactTextShadowNode to Kotlin.

## Changelog:

[INTERNAL] - Migrate com.facebook.react.views.text.ReactTextShadowNode to Kotlin

Pull Request resolved: https://github.com/facebook/react-native/pull/52116

Test Plan:
```bash
yarn test-android
yarn android
```

Reviewed By: NickGerleman

Differential Revision: D76930707

Pulled By: sbuggay

fbshipit-source-id: 8e5c2c1b96ccaa2185d5c7f606e204470c401cbd
2025-06-18 20:59:02 -07:00
Joe VilchesandFacebook GitHub Bot 5e331304cb Comma separate co-opted accessibility labels (#52120)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/52120

It is pretty jarring for semantically different labels to be read all at once, let's comma separate them - which VoiceOver will pause at. Android does this by default with its coopting implementation

Changelog: [Internal]

Reviewed By: jorge-cab

Differential Revision: D76921000

fbshipit-source-id: afe1f93e38babde918137576d0693c1579101ef7
2025-06-18 15:20:56 -07:00
Luna WeiandFacebook GitHub Bot f00b449498 Export unstable_VirtualView with iOS, Android implementations (#51980)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/51980

Changelog: [Internal] - Export unstable_VirtualView. This is currently experimental

Reviewed By: mdvacca

Differential Revision: D76471250

fbshipit-source-id: 107b82c9ac93b31d7e30ea3473e341788176cdde
2025-06-18 14:11:39 -07:00
Peter AbbondanzoandFacebook GitHub Bot 94cbf206d6 Remove focus change listener and restore original when dropping view instance (#52093)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/52093

This change updates the `BaseViewManager` implementation to drop and restore the original focus listener when a view instance has its `onDropViewInstance` method called. This is necessary to support view recycling, since the `addEventEmitters` method is called each time a recycled view is popped out of the stack. This would result in N+1 `onFocus`/`onBlur` calls for each time the view is recycled.

Changelog: [Android][Fixed] - Remove focus change listener when dropping/recycling view instances

Reviewed By: NickGerleman

Differential Revision: D76852137

fbshipit-source-id: 9e980e7a1850a952baf04724bc251ff32186c6fa
2025-06-18 11:21:32 -07:00
Rubén NorteandFacebook GitHub Bot 6bfc1187a8 Improve formatting for benchmark output (#52106)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/52106

Changelog: [internal]

This slightly improves the formatting of the output produced by benchmarks, so we can just copy&paste the result to share it as valid Markdown.

Reviewed By: christophpurrer

Differential Revision: D76898244

fbshipit-source-id: dc1040ee3787c7f0dcb747c9fba8eb14086a0087
2025-06-18 09:36:36 -07:00
Rubén NorteandFacebook GitHub Bot 29704b1f02 Add support for Static Hermes staging in Fantom (#52105)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/52105

Changelog: [internal]

I just learnt there's a Hermes variant that we don't support (staging) so this adds support for it.

Reviewed By: christophpurrer

Differential Revision: D76897715

fbshipit-source-id: 3113edde3c785d71ad4a57dd435f16e13ab46976
2025-06-18 09:36:36 -07:00
Mateo GuzmánandFacebook GitHub Bot ccc8ce0d01 Remove lint-java (#52092)
Summary:
Following up from https://github.com/facebook/react-native/pull/52064#discussion_r2151906096, this PR removes lint-java and its related files.

The codebase is moving entirely to Kotlin and a Kotlin linter is being setup as well, the usage of the Java linter will become unnecessary.

## Changelog:

[INTERNAL] - Remove lint-java

Pull Request resolved: https://github.com/facebook/react-native/pull/52092

Test Plan: Relying on CI here to be green.

Reviewed By: cortinico

Differential Revision: D76880712

Pulled By: sbuggay

fbshipit-source-id: 2736772e7347f435b17d007e0322e1afc2fb2d7b
2025-06-18 08:22:04 -07:00
Krzysztof PiaskowyandFacebook GitHub Bot 1cc12ce7fd Fix the serialization of the alpha channel in the rgba() color string format (#52087)
Summary:
The alpha channel in the `rgba()` color string format should be a value between [0, 1] instead of [0, 255], as stated in the [React Native Documentation](https://reactnative.dev/docs/colors#red-green-blue-rgb).

## Changelog:

[GENERAL] [FIXED] - Fix the serialization of the alpha channel in the `rgba()` color string format.

Pull Request resolved: https://github.com/facebook/react-native/pull/52087

Test Plan: I didn't find any usage of that function in React Native, but it is part of the public API, and I just wanted to use it in Reanimated.

Reviewed By: fabriziocucci

Differential Revision: D76822560

Pulled By: javache

fbshipit-source-id: 370d9729881c1eda794c1b9fe54d258242fde814
2025-06-18 07:28:34 -07:00
Oskar KwaśniewskiandFacebook GitHub Bot 28986a7599 fix(iOS): allow to interactively swipe down the modal (#51483)
Summary:
This PR allows to interactively close the modal using the swipe down gesture.

It fixes 5 year old issue: https://github.com/facebook/react-native/issues/29319

In short it removes `modalInPresentation` which according to the documentation causes: "UIKit ignores events outside the view controller’s bounds and **prevents the interactive dismissal of the view controller while it is onscreen.**".

It also adds another delegate event to call onRequestClose whenever modal is closed by gesture.

https://github.com/user-attachments/assets/8849ecba-f762-47ec-a28b-b41c1991a882

## Changelog:

[IOS] [ADDED] - Allow to interactively swipe down the modal.
Add allowSwipeDismissal prop.

Pull Request resolved: https://github.com/facebook/react-native/pull/51483

Test Plan: Test if swiping down the modal calls onRequestClose

Reviewed By: rshest

Differential Revision: D75125438

Pulled By: javache

fbshipit-source-id: d4f2c8b59447680f405b725d0809573a937f97cf
2025-06-18 07:18:40 -07:00
Zeya PengandFacebook GitHub Bot ce75271d47 Gate logic to remove js sync at end of native animation loop (#52068)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/52068

## Changelog:

[Internal] [Changed] - Gate logic to remove js sync at end of native animation loop

because https://github.com/facebook/react-native/pull/51264 is causing regression in some cases, and was recently reverted again in https://github.com/facebook/react-native/pull/51933 . Using an extra feature flag to gate this potential optimization

Reviewed By: lenaic

Differential Revision: D76759441

fbshipit-source-id: 6950ab746b02af6e0b710ded0ddb6993673e5212
2025-06-18 06:54:09 -07:00
Zeya PengandFacebook GitHub Bot 99c60bfe0a Create feature flag cxxNativeAnimatedRemoveJsSync (#52067)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/52067

## Changelog:

[Internal] [Changed] - Create feature flag cxxNativeAnimatedRemoveJsSync for optimization in cxx animated

Reviewed By: christophpurrer

Differential Revision: D76763197

fbshipit-source-id: 503d7cea53b6a5c3ed769b0d77a9d64730029433
2025-06-18 06:54:09 -07:00
Ruslan ShestopalyukandFacebook GitHub Bot a477ee6ced Update ReactNativeCPP.api file (#52103)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/52103

## Changelog:
[Internal] -

This got out of sync again, causing the corresponding signal to fail.

Reviewed By: lenaic

Differential Revision: D76893994

fbshipit-source-id: e0e78cd41633972be53aa883c6015588b547b9c2
2025-06-18 06:38:35 -07:00
Rubén NorteandFacebook GitHub Bot a17a7003b1 Remove unnecessary feature flags from fantom tests (#52101)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/52101

Changelog: [internal]

`enableAccessToHostTreeInFabric` is enabled by default in tests, so doesn't need to be explicitly set.

Reviewed By: lenaic

Differential Revision: D76894319

fbshipit-source-id: 437a763bd9c557ecbafa53e31410da1120151b86
2025-06-18 06:03:12 -07:00
Vincent WongandFacebook GitHub Bot f70c01cc30 Revert D76757706: Fix LegacyViewManagerInteropComponentDescriptor
Differential Revision:
D76757706

Original commit changeset: 6fc6127b6ff4

Original Phabricator Diff: D76757706

fbshipit-source-id: 88a72c06dd799c322320870c69906cb843a6021d
2025-06-18 04:54:32 -07:00
Ruslan ShestopalyukandFacebook GitHub Bot bc6b4f0525 Specify missing default destructor and assignment operator declarations in ImageRequest (#52099)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/52099

# Changelog:
[Internal] -

Fixes a linter warning for the `ImageRequest` class declaration (AKA "C++ rule of 5").

Reviewed By: javache

Differential Revision: D76888523

fbshipit-source-id: aa080a582942bd083030d6b6ecd82294566b507c
2025-06-18 04:43:28 -07:00
Enrique López MañasandFacebook GitHub Bot 73133a31d5 Deprecating SafeAreaView (#51896)
Summary:
`SafeAreaView` has been marked for deprecation some time ago. The [following discussion](https://github.com/react-native-community/discussions-and-proposals/discussions/827) summarizes some of the arguments for it, but in a nutshell: it is iOS-only, limited in functionality, and incompatible with Android 15's enforced edge-to-edge rendering.

~~The Android/Kotlin classes have also been deprecated.~~ Not anymore, they are internal and might stay as they are.

## Changelog:

[General] [Deprecated] - Deprecate SafeAreaView due to its iOS-only support and incompatibility with Android 15 edge-to-edge behavior; recommend using react-native-safe-area-context instead.

<!-- Help reviewers and the release process by writing your own changelog entry.

Pick one each for the category and type tags:

[ANDROID|GENERAL|IOS|INTERNAL] [BREAKING|ADDED|CHANGED|DEPRECATED|REMOVED|FIXED|SECURITY] - Message

For more details, see:
https://reactnative.dev/contributing/changelogs-in-pull-requests

Pull Request resolved: https://github.com/facebook/react-native/pull/51896

Test Plan: Access `SafeAreaView` in your code and check that the deprecation warning logs exactly once in the console.

Reviewed By: rshest

Differential Revision: D76816160

Pulled By: cortinico

fbshipit-source-id: 60f539f9425f8ed8e19578a4530cb1f7d9b54488
2025-06-18 04:15:05 -07:00
Nicola CortiandFacebook GitHub Bot 9258e90c92 Publish, Close and Release the Maven repository during build_npm_package
Summary:
Due to us moving to central.sonatype.com for publishing, we cannot publish and release the Maven repository in 2 distinct invocations.
This consolidates all the publishing job to happen during build_npm_package

Changelog:
[Internal] [Changed] -

Reviewed By: fabriziocucci

Differential Revision: D76888543

fbshipit-source-id: 3cb0db6176ed2221a12b4f3f1f575232aa006a6c
2025-06-18 04:02:28 -07:00
Fabrizio CucciandFacebook GitHub Bot f783d30beb Add changelog for 0.79.4 (#52100)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/52100

As pet title.

Changelog: [Internal]

Reviewed By: cortinico

Differential Revision: D76889179

fbshipit-source-id: 7551ade832a88ff8d6501945228e50d2572006bb
2025-06-18 03:16:48 -07:00
Samuel SuslaandFacebook GitHub Bot 2915902763 introduce gesture driven animations sync mechanism for C++ Animated (#52081)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/52081

changelog: [internal]

# The problem in Java and Obj-C Native Animated
--------------------

Event based animations in Native Animated have one big problem: detecting when the gesture is completed. This has led to number of bugs over the years that manifest as Pressable's onPress not triggering. The root cause of the problem is disconnect between where a piece of UI is on the screen and where Fabric thinks it is. Fabric is used for hit testing, and other things like intersection observing, and if Fabric does not have up to date values, weird things start happening.

This diff implements completion detection for event based animations. Unlike previous solutions, it does not depend on sequence of events nor it uses React setState to update shadow tree.

# Completion Detection
--------------------

The key mechanism for marking event animations as completed is in the `onRender` method:

- After processing animations, if `onAnimationFrame` returns false - indicating no UI changes, the system assumes all event-driven animations have completed.
- it then:
    *   Collects all animated value nodes connected to event drivers.
    *   Updates all nodes connected to these "finished" animations.
    *   isEventAnimationInProgress_ to false.
    *   Commits final property values to the UI.

The benefits over the previous solution:
- It works across all platforms without the need to emit "completion events".
- It uses a UI frame that wouldn't animate anything to do syncing work. Using main thread IDLE time towards bookkeeping.
- No need to call into JavaScript for synchronisation.

Reviewed By: zeyap

Differential Revision: D76743467

fbshipit-source-id: b21ab5ef9c71ff93fd0250e6af6b22ded395be8d
2025-06-18 02:38:43 -07:00
Soe LynnandFacebook GitHub Bot c31ff55b92 Fix LegacyViewManagerInteropComponentDescriptor (#52094)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/52094

Changelog [Internal]:
- LegacyViewManagerInteropComponentDescriptor was initiaizing Module just to get the `Class` for `componentName`. This can cause deadlock issue when we have module that needs mainQueue to initialize.

Reviewed By: RSNara

Differential Revision: D76757706

fbshipit-source-id: 6fc6127b6ff472fda509364f0c3daa13b6a6df1e
2025-06-17 22:16:43 -07:00
Jorge Cabiedes AcostaandFacebook GitHub Bot d405e306e3 Make experimental_accessibilityOrder a View prop only (#52056)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/52056

This prop should only be used on Views using it in Text is not very intuitive and also would require some unnecessary edge case handling on Android which we now also remove

Changelog: [Internal]

Reviewed By: joevilches

Differential Revision: D76744972

fbshipit-source-id: 23113789747295ea1a306727f1e29e3ddc078ac4
2025-06-17 18:07:58 -07:00
Ramanpreet NaraandFacebook GitHub Bot ad88309843 Compile out the legacy runtime
Summary:
Compile out the legacy c++ core from android.

Changelog: [Internal]

Reviewed By: mdvacca

Differential Revision: D72991984

fbshipit-source-id: 56bb6ca1c4797adb9235e982121712b11e85e109
2025-06-17 16:02:03 -07:00
FBLite Revert BotandFacebook GitHub Bot fd29d68c16 Revert D76793705: Fix possible invalid measurements when width or height is zero pixels
Differential Revision:
D76793705

Original commit changeset: ea4c00e68891

Original Phabricator Diff: D76793705

fbshipit-source-id: 95d6b66ab08e073da9fb07fd4094a7e30e2f453b
2025-06-17 14:33:32 -07:00
Nick GerlemanandFacebook GitHub Bot 5348f9ff1e Fix possible invalid measurements when width or height is zero pixels (#52073)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/52073

X-link: https://github.com/facebook/yoga/pull/1820

Fixes https://github.com/facebook/yoga/issues/1819

Yoga has a fast path when measuring a node, if it thinks it knows its dimensions ahead of time.

This path has some eroneous logic, to set both axis to owner size, if *either* will evaluate to zero, while having an `YGMeasureModeAtMost`/`FitContent` constraint. This means that if a node is given a zero width, and Yoga later measures with with `FitContent`, its height will become the maximum allowable height, even if it shouldn't be that large.

We can fix this, by only allowing if both axis are this fixed case, instead of just one.

This bug has existed for about a decade (going back to at least D3312496).

Changelog:
[General][Fixed] - Fix possible invalid measurements with width or height is zero pixels

Reviewed By: yungsters

Differential Revision: D76793705

fbshipit-source-id: ea4c00e688912a58c08801e4a14ddf1b293a5d86
2025-06-17 12:15:55 -07:00
Nick GerlemanandFacebook GitHub Bot e08e27a6fa Support Facsimile Views in Jest E2E Text Selectors (#52070)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/52070

Changelog: [Internal]

Reviewed By: joevilches, rshest

Differential Revision: D76783111

fbshipit-source-id: 5f4c3a9cf6d59eff15a071761ef83425bd4c6d23
2025-06-17 12:13:27 -07:00
Nick GerlemanandFacebook GitHub Bot ded4ae1f78 Fix antialiasing for Facsimile on Android < 10 (#52069)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/52069

Noticed an E2E tests against old emulator version showed aliased Text against Facsimile. This is because non-scratch-paint path does not explicitly enable anti-aliasing (like TextView does), older versions of Android don't enable by default, and `updateTextPaint()` no longer sets, after call to `paint.reset()` was removed.

Changelog: [Internal]

Reviewed By: javache

Differential Revision: D76780749

fbshipit-source-id: 26f0de6d6558649fe08339f1ac91390bbd4cce3c
2025-06-17 10:51:23 -07:00
Riccardo CipolleschiandFacebook GitHub Bot 331fab0683 Remove JSC related files from the repo (#51945)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/51945

Remove code from jsc folder from React Native

## Changelog:
[iOS][Removed] - Remove code from jsc folder from React Native

Reviewed By: rshest

Differential Revision: D76419587

fbshipit-source-id: 6d5079c8f2843872172620601629e9643c7debdc
2025-06-17 09:38:21 -07:00
Nicola CortiandFacebook GitHub Bot 33aa7b9161 Remove unreferenced CMakeLists.txt for ReactCommon/jsc (#52078)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/52078

This Cmake file is not imported anymore and can be removed now.

Changelog:
[Internal] [Changed] -

Reviewed By: cipolleschi

Differential Revision: D76808417

fbshipit-source-id: 81824c7c46080bc16c891b5c11c3a8946f16c1b9
2025-06-17 06:26:18 -07:00
Christian FalchandFacebook GitHub Bot 560ac23001 Added missing define to target .reactRuntime (#52082)
Summary:
The target needs the HERMES_ENABLE_DEBUGGER flag in debug just like .reactHermes does.

This commit fixes this by adding the define to the reactRuntime target.

## Changelog:

[IOS] [FIXED] - Added HERMES_ENABLE_DEBUGGER to debug configuration for the reactRuntime target.

Pull Request resolved: https://github.com/facebook/react-native/pull/52082

Test Plan: Prebuild React Core

Reviewed By: robhogan

Differential Revision: D76813200

Pulled By: cipolleschi

fbshipit-source-id: cb81a40fb9c5a91ca40c3a27ae4ccdf043186bac
2025-06-17 06:21:19 -07:00
Pieter De BaetsandFacebook GitHub Bot 87cd1c0a0a Add rncore stub headers for backwards-compat (#52083)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/52083

These headers were removed in D55037569 but we may have some targets still depending on them. Add redirection headers with warnings to help users migrate without this being a breaking change.

Changelog: [Internal]

Reviewed By: cipolleschi

Differential Revision: D76810433

fbshipit-source-id: 43cddcc69eefbcff0c0140e165fb893bee493c79
2025-06-17 06:18:49 -07:00
Riccardo CipolleschiandFacebook GitHub Bot 3ea4f156c9 Change imports from jsireact to reacthermes (#52011)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/52011

This change aligns the import path of `jsireact/HermesExecutorFactory` to the OSS one: `reacthermes/HermesExecutorFactory`

## Changelog:
[Internal] -

Reviewed By: javache

Differential Revision: D76600638

fbshipit-source-id: d9ae35dec000196e4a51668c482fc27f3e6018b4
2025-06-17 05:51:02 -07:00
Nicola CortiandFacebook GitHub Bot 5e1768746a Unblock RNTester which is currently instacrashing. (#52077)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/52077

RNTester is currently crashing on release due to a stripped `mHybridData` field.

Changelog:
[Internal] [Changed] -

Reviewed By: cipolleschi

Differential Revision: D76808165

fbshipit-source-id: 049cca49f683c5dc92aa1f9a37dd7b4371dcbfd6
2025-06-17 04:54:47 -07:00
Samuel SuslaandFacebook GitHub Bot e259961e8b return object with string keys from getDirectManipulationProps
Summary:
changelog: [internal]

make API getDirectManipulationProps slightly nicer by returning `[string]: mixed` instead of mixed.

Reviewed By: rubennorte

Differential Revision: D76763893

fbshipit-source-id: 56c56578251b991f5fb3e68d44e834da17fc4a3b
2025-06-17 04:49:44 -07:00
Andrew DatsenkoandFacebook GitHub Bot aeaae4294f Add hermes and jsi (#52060)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/52060

Changelog: [Internal]

Add hermes and jsi so we can evaluate JS.

Reviewed By: christophpurrer

Differential Revision: D76746362

fbshipit-source-id: a5fbb90d06a8608154b6ae80242b969e7cb03cea
2025-06-17 04:18:08 -07:00
Andrew DatsenkoandFacebook GitHub Bot 74b6acb1f0 Add gflags (#52015)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/52015

Changelog: [Internal]
Add gflags to fantom_tester so we can pass in data like featureFlags

Reviewed By: cortinico

Differential Revision: D76618409

fbshipit-source-id: a18e642a02c405eef972a7418a606a5980253b6a
2025-06-17 04:18:08 -07:00
Fabrizio CucciandFacebook GitHub Bot a91e598e6a Minor renaming in ViewManagerRegistry (#52079)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/52079

After the conversion to Kotlin (D74571782), there seems to be a synchronization issue when iterating on `viewManagers` (see T226095884).

There could a shadowing problem kicking in in a few places due to the fact that we are declaring a `viewManagers` local variable when there is already a `viewManagers` instance variable within `ViewManagerRegistry`.

To remove any ambiguity, here we are renaming the instance variable `viewManagers` as `viewManagersMap` (which also makes more sense).

Changelog: [Internal]

Reviewed By: cortinico

Differential Revision: D76807070

fbshipit-source-id: 4f598700e04251409ee19b60515639e90699cc9e
2025-06-17 04:12:54 -07:00
Rubén NorteandFacebook GitHub Bot 10cabcc0a7 Refactor surface creation in Fantom (#52076)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/52076

Changelog: [internal]

This refactors the implementation of surface creation in Fantom to make the surface ID handled in native, and treated as an opaque type the same way we do at runtime in RN.

Reviewed By: andrewdacenko

Differential Revision: D76744096

fbshipit-source-id: 1b49a1cbdf0a8d6804de3b87ede727207bc662d9
2025-06-17 03:52:09 -07:00
zhongwuzwandFacebook GitHub Bot 3bfbaa8455 Fabric: Only add selection of textinput payload when event is selectionChange (#51051)
Summary:
Fixes https://github.com/facebook/react-native/issues/51020: TextInputChangeEventData does not contain selection (see [here](https://github.com/facebook/react-native/blob/main/packages/react-native/Libraries/Components/TextInput/TextInput.d.ts#L515-L518)). Additionally, Android does not have selection either (see [here](https://github.com/facebook/react-native/blob/main/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactTextChangedEvent.kt#L33-L37)) (iOS old arch also not contains it). We can consolidate these to prevent any misleading information.

## Changelog:

[IOS] [FIXED] -  Fabric: Only add selection of textinput payload when event is selectionChange

Pull Request resolved: https://github.com/facebook/react-native/pull/51051

Test Plan: TextInput `onChange`'s event should not contains `selection`.

Reviewed By: javache

Differential Revision: D76253999

Pulled By: cipolleschi

fbshipit-source-id: 8a889cce4998be0e31f4aa33961759e7d228e06f
2025-06-17 03:23:48 -07:00
Tim YungandFacebook GitHub Bot 742ffa39fb RN: Update Fantom Path in EXCLUDED_FIRST_PARTY_PATHS (#52051)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/52051

In {D76368959}, I moved `react-native-fantom` from `packages/` to `private/` and missed this reference.

Changelog:
[Internal]

Reviewed By: rubennorte

Differential Revision: D76743071

fbshipit-source-id: f99d3f2ac5e14fd23f7cf208ca030541844dddc6
2025-06-17 02:59:42 -07:00
Dawid MałeckiandFacebook GitHub Bot 6b40f35032 Add diff-api-snapshot for public API breaking change detection (#51972)
Summary:
This diff adds snapshot `diff-api-snapshot` script for public JS API breaking change detection.

### Motivation
Detecting if there are any breaking changes introduced in the commit. It is achieved by comparing `ReactNativeApi.d.ts` rollup from the current and previous revision.

This is a naive implementation with a three possible outcomes:
- BREAKING
- POTENTIALLY_NOT_BREAKING,
- NOT_BREAKING

The algorithm analyses exported top-level statements (after inlining) in both rollups and tries to create a mapping between them by name.

The **BREAKING** outcome happens whenever the statement is:
- removed
- renamed
- changed
- not exported anymore (private)

The **POTENTIALLY_NOT_BREAKING** outcome  happens if it's not BREAKING and the new statement is added.

The **NOT_BREAKING** outcome happens if public API snapshot doesn't change.

Changelog:
[General][Added] - Add public JS API breaking change detection under `yarn diff-api-snapshot` script.

Pull Request resolved: https://github.com/facebook/react-native/pull/51972

Test Plan:
Signals, added tests.

In `react-native-github` run:
`yarn test scripts/diff-api-snapshot/__tests__/diffApiSnapshot-test.js`

Rollback Plan:

Reviewed By: j-piasecki

Differential Revision: D76430965

Pulled By: coado

fbshipit-source-id: 095a196aa4f643501db0af9262556ddefff5d30d
2025-06-17 02:27:53 -07:00
Rubén NorteandFacebook GitHub Bot 4556bdf4fc Make OpaqueNativeObserverHandle really opaque (#52054)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/52054

Changelog: [internal]

This makes `OpaqueNativeObserverHandle` really opaque and fixes the problems in the observer implementation caused by it.

Reviewed By: yungsters

Differential Revision: D76744094

fbshipit-source-id: a8b6fa43ee8a5ee9d15f0171a83fe0badd46d9c3
2025-06-17 02:24:22 -07:00
Rubén NorteandFacebook GitHub Bot 98fdc965b9 Make IdleCallbackID opaque in NativeIdleCallbacks implementation (#52055)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/52055

Changelog: [internal]

This migrates NativeIdleCallbacks to use opaque types now that they're supported in the codegen

Reviewed By: yungsters

Differential Revision: D76744095

fbshipit-source-id: d9d1beea8df7f5635fc531a2cef001ea0aed38b4
2025-06-17 02:24:22 -07:00
Rubén NorteandFacebook GitHub Bot e8cdb99baa Make NativeIntersectionObserverToken opaque in NativeIntersectionObserver implementation (#52053)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/52053

Changelog: [internal]

The codegen allows us to do this already! :D

Reviewed By: yungsters

Differential Revision: D76741113

fbshipit-source-id: d460685bc6ad6ba11f7132136e8603bd57488014
2025-06-17 02:24:22 -07:00
Rubén NorteandFacebook GitHub Bot a15fc102e6 Add support for opaque types in Flow codegen for native modules (#52052)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/52052

Changelog: [General][Added] - Add support for Flow opaque types in codegen for native modules

This allows us to codegen native modules that expose opaque types, but the implementation sees the type the same way they're visible in the JS spec.

Reviewed By: yungsters

Differential Revision: D76741112

fbshipit-source-id: 100ca9aa7f93d35120c52153f756436c9c380b07
2025-06-17 02:24:22 -07:00
Samuel SuslaandFacebook GitHub Bot 6ddfa708b0 small adjustments to when value listener is fired (#52063)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/52063

changelog: [internal]

`addListener` API shouldn't fire when offset for node changes. Check https://github.com/facebook/react-native/blob/main/packages/react-native/Libraries/NativeAnimation/Nodes/RCTValueAnimatedNode.mm for reference.

Reviewed By: mdvacca

Differential Revision: D76740873

fbshipit-source-id: 04fb59ee9b630402803c4741f501d9d10591fa71
2025-06-17 02:16:32 -07:00
Samuel SuslaandFacebook GitHub Bot 1456d06c86 add comment explaining why addListener and removeListeners are not needed (#52062)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/52062

changelog: [internal]

- update comments to be more descriptive.
- use `#pragma mark -` to better group methods in `NativeAnimatedNodesManager`. It is nicely formatted in VSCode.

{F1979345410}

Reviewed By: mdvacca

Differential Revision: D76737257

fbshipit-source-id: c4b22ca45cd5dec2c72e7931bfec4466cda3070c
2025-06-17 02:16:32 -07:00
Jakub PiaseckiandFacebook GitHub Bot 7bfadb429c Update prettier config for the API Snapshot (#52031)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/52031

Changelog: [Internal]

Reviewed By: cortinico

Differential Revision: D76710754

fbshipit-source-id: 36a07a2797b1270681420471aa66987163269a11
2025-06-17 00:06:13 -07:00
Jakub PiaseckiandFacebook GitHub Bot faaec467b9 Strip unstable/experimental identifiers from the API snapshot (#52008)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/52008

Changelog: [Internal]

Reviewed By: huntie

Differential Revision: D73102445

fbshipit-source-id: fe2ce04b32c9d839fec8b33eda9c67e551918ba3
2025-06-17 00:06:13 -07:00
Nick GerlemanandFacebook GitHub Bot 64ce7695f5 Fix Facsimile Hit Testing Bugs (#52065)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/52065

1. We can crash when tapping at the boundary between two spans. Previous ReactTextView had some custom heuristic for overlapping ReactTags, assuming they could be nested, which never happens (We only have a single tag per AttributedString fragment), but spans may overlap at a single character, where one is meant to be exclusive, and the other inclusive. We add logic for that.
2. We don't incorporate the offset of the text layout within the view for hit testing, needed for padding or `textAlignVertical`.

Changelog: [Internal]

Reviewed By: joevilches

Differential Revision: D76764051

fbshipit-source-id: 308756c45d8ff574420dfc1c88678bae7e03e767
2025-06-16 20:51:23 -07:00
Gabriel DonadelandFacebook GitHub Bot 3ae9328571 Bump @types/react to 19.1 (#52059)
Summary:
While testing I notice that `types/react` was not updated in some peer depencies

## Changelog:

[GENERAL] [CHANGED] - Bump types/react to 19.1

Pull Request resolved: https://github.com/facebook/react-native/pull/52059

Test Plan: CI should be green

Reviewed By: christophpurrer

Differential Revision: D76763084

Pulled By: sbuggay

fbshipit-source-id: c078c03aa57ca04040c64986dd7957da8a6f2c2d
2025-06-16 17:45:11 -07:00
Christoph PurrerandFacebook GitHub Bot 89401b78cd Update outdated ReactNativeCPP.api (#52061)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/52061

changelog: [internal] Update outdated ReactNativeCPP.api

Reviewed By: sammy-SC

Differential Revision: D76763455

fbshipit-source-id: bb9b72742594c19d70d930fa3827b450922428bb
2025-06-16 15:13:01 -07:00
Christoph PurrerandFacebook GitHub Bot 089fb10740 Remove folly::EvictingCacheMap (#52019)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/52019

Replace folly::EvictionMap with pure std implementation

changelog: [internal][Breaking] Remove unused set() method of SimpleThreadSafeCache

Reviewed By: javache, sammy-SC

Differential Revision: D76631579

fbshipit-source-id: 15dfa8a8c0d2809440c214a72e48def6e1dba2f0
2025-06-16 15:06:17 -07:00
Samuel SuslaandFacebook GitHub Bot 2528e48e93 implement Value.extractOffset in C++ Animated (#52049)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/52049

changelog: [internal]

As title says, add implementation of [Value.extractOffset](https://reactnative.dev/docs/animatedvalue#extractoffset)

Reviewed By: zeyap

Differential Revision: D76735003

fbshipit-source-id: db09e2ed1c8b540d911970d2268fcd0374717e96
2025-06-16 14:30:19 -07:00
Samuel SuslaandFacebook GitHub Bot cb24596c33 add Value.flattenOffset to C++ Animated (#52047)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/52047

changelog: [internal]

implement AnimatedValue.[flattenOffset](https://reactnative.dev/docs/animatedvalue#flattenoffset) in C++ Animated.

Reviewed By: javache

Differential Revision: D76733524

fbshipit-source-id: df969cf8be421dd6fa22c4100ccd2c33aa7c91d8
2025-06-16 14:30:19 -07:00
Peter AbbondanzoandFacebook GitHub Bot cfb6c968dd Mark hasTVPreferredFocus as deprecated (#52043)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/52043

The `hasTVPreferredFocus` prop is functionally similar to the `focusable` prop. On iOS, the props are identical. The primary difference appears on Android, where the prop attempts to request focus when set to true. Attempting to invoke imperative API calls through declarative means has been [a source of confusion](https://github.com/react-native-tvos/react-native-tvos/issues/237) and we should instead recommend requesting focus through imperative means, like calling `focus()` on a specific view's ref instead. Workarounds presented rely on lifecycle methods to request focus natively.

This change only marks these methods as deprecated on JS. In the following version, they will be removed from the public API.

Changelog: [General][Deprecated] - Deprecate `hasTVPreferredFocus`

Reviewed By: andrewdacenko

Differential Revision: D76732539

fbshipit-source-id: 64912b4dacb76cd40e79148c1082d8ed8f573879
2025-06-16 12:05:47 -07:00
Mathieu ActhernoeneandFacebook GitHub Bot 3c17e101ee Add UiModeUtils.isDarkMode helper (#51925)
Summary:
This PR is part of the [edge-to-edge core implementation effort](https://github.com/facebook/react-native/pull/47554). It adds a helper to `ContextUtils`, called `isDarkMode` in order to be reused accross the Android codebase.

## Changelog:

- [Internal] [Added] - Add `UiModeUtils.isDarkMode` helper

Pull Request resolved: https://github.com/facebook/react-native/pull/51925

Test Plan:
<img width="300" alt="Screenshot 2025-06-10 at 17 50 19" src="https://github.com/user-attachments/assets/48795406-e852-486a-bae8-54507ad769ee" />

<img width="300" alt="Screenshot 2025-06-10 at 17 50 42" src="https://github.com/user-attachments/assets/0dbdece9-04f7-487b-ace8-4786c8da381d" />

Rollback Plan:

Reviewed By: cortinico

Differential Revision: D76352849

Pulled By: alanleedev

fbshipit-source-id: fbbc772d1dfa3e30e62a71a711de11b85cdec446
2025-06-16 10:35:35 -07:00
Nicola CortiandFacebook GitHub Bot 8174d02811 Cleanup all the JSC 1st party code. (#51949)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/51949

This code is no longer necessary now that JSC support is offered via
https://github.com/react-native-community/javascriptcore

Instructions for users on how to continue using JSC are available in the README of such library.

Changelog:
[Android] [Removed] - Remove 1st party JSC support

Reviewed By: javache

Differential Revision: D76420382

fbshipit-source-id: f8e61556bb02fe4d5b34f89b40f8e5e38ac1c8d6
2025-06-16 10:30:39 -07:00
Janic DuplessisandFacebook GitHub Bot 7e2f17ffe2 Only generate recursive accessibility label for accessible elements (#51988)
Summary:
When trying to implement e2e tests using maestro in a large app I ran into major performance issues. I tracked it down to the generation of recursive accessibility labels.

The maestro iOS driver uses [XCUIElement snapshot dictionaryRepresentation](https://developer.apple.com/documentation/xctest/xcuielementsnapshot/dictionaryrepresentation) [here](https://github.com/mobile-dev-inc/Maestro/blob/96e8c9a2be3430be991c13d033486d52d2001334/maestro-ios-xctest-runner/maestro-driver-iosUITests/Routes/Handlers/ViewHierarchyHandler.swift#L234) to get a representation of the view hierarchy. The problem is that will query the accessibilityLabel for every single view, starting from the root of the app. It goes without saying that this is extremely slow since it traverses the view hierarchy, executing a recursive function on each one.

I think the only way to fix this is to avoid generating these recursive labels when not needed. From my understanding these should only be needed for accessible views.

## Changelog:

[IOS] [CHANGED] - Only generate recursive accessibility label for accessible elements

Pull Request resolved: https://github.com/facebook/react-native/pull/51988

Test Plan:
- Tested using VoiceOver in RN tester to make sure it works exactly the same.
- Tested in an app using Maestro to make sure this fixes the performance issue.
- Tested in RNTester running Maestro e2e test by creating a larger view hierarchy to make the problem more apparent, and simulate a real app. Using [this code](https://gist.github.com/janicduplessis/9f6b302d92b4e22ff5e8462a8a84e237) in RNTesterAppShared.js

Before:

```
❯ yarn e2e-test-ios
yarn run v1.22.22
$ ./scripts/maestro-test-ios.sh

Waiting for flows to complete...
[Passed] flatlist (14s)
[Passed] text (28s)
[Passed] modal (16s)
[Passed] image (8s)
[Passed] button (10s)
[Passed] legacy-native-module (32s)
[Passed] pressable (32s)
[Passed] new-arch-examples (35s)

8/8 Flows Passed in 2m 55s

  Done in 180.26s.
```

After:

```
❯ yarn e2e-test-ios
yarn run v1.22.22
$ ./scripts/maestro-test-ios.sh

Waiting for flows to complete...
[Passed] flatlist (7s)
[Passed] text (15s)
[Passed] modal (10s)
[Passed] image (4s)
[Passed] button (6s)
[Passed] legacy-native-module (16s)
[Passed] pressable (16s)
[Passed] new-arch-examples (17s)

8/8 Flows Passed in 1m 31s

  Done in 97.53s.
```

Reviewed By: christophpurrer, joevilches

Differential Revision: D76581949

Pulled By: cipolleschi

fbshipit-source-id: 0689c7d43a0c865572c4ee5ea32ee9b2dcb33ad5
2025-06-16 10:30:33 -07:00
Jorge Cabiedes AcostaandFacebook GitHub Bot e13ee7a3d0 Fix accessibilityRespondsToUserInteraction not setting on first render (#51986)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/51986

"{}" defaults to false on C++ but the prop is not initialized which means that if accessibilityEnablesUserInteraction is set to false it will not be applied on first render. setting the default to true fixes that issue

Changelog: [Internal]

Reviewed By: joevilches

Differential Revision: D76532158

fbshipit-source-id: 51cba8b89eb239e01db985d412dd2b19e482f068
2025-06-16 10:22:05 -07:00
Alan LeeandFacebook GitHub Bot 9613d377de Fix Type comparison (v2) in TurboModuleInteropUtils.kt (2/2) (#51979)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/51979

Fix type checking that was left out from the last diff; D73224138

We should unify the type checking so remove uses of `java.lang.Boolean` and just use Kotlin version of `Boolean`.

Changelog:
[Internal]

Reviewed By: mdvacca

Differential Revision: D76523674

fbshipit-source-id: 293ae5998c78c98a20c7d6cf962ab7b19087fd9c
2025-06-16 09:48:44 -07:00
David VaccaandFacebook GitHub Bot 3a0a7b0701 Export RNCodegen types (#52048)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/52048

This diff exports types defined in RNCodegen to be used by other codegens

changelog: [internal] internal

Reviewed By: christophpurrer

Differential Revision: D76472492

fbshipit-source-id: fa236a254a9a4211d2e00ace436f55978a262a76
2025-06-16 09:46:31 -07:00
Riccardo CipolleschiandFacebook GitHub Bot c777ad0b84 Fix nightlies artefacts download (#52041)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/52041

After changing the publishing logic on Maven, the download logic for the artefacts published in a Snapshot was broken, because Maven does not support the redirect anymore.

## Changelog:
[Internal] -

Reviewed By: cortinico

Differential Revision: D76725418

fbshipit-source-id: 8bad88915d9bad96355a048486972a55f232d109
2025-06-16 09:26:41 -07:00
Mateo GuzmánandFacebook GitHub Bot 3412215e3a Kotlin: clean up some unused symbols (#52029)
Summary:
Static code analysis reports numerous unused symbols across the codebase, which accounts for several static code analysis warnings. I've cleaned up some of them (not all of them because many of them are false positives from testing files)

The cleaned-up symbols are mostly private and from internal classes, so this should not impact users (unless they are used internally at Meta – in that case, let me know so I can revert accordingly)

## Changelog:

[INTERNAL] - Kotlin: clean up some unused symbols

Pull Request resolved: https://github.com/facebook/react-native/pull/52029

Test Plan:
```sh
yarn test-android
yarn android
```

Reviewed By: javache

Differential Revision: D76722313

Pulled By: cortinico

fbshipit-source-id: 8c7dfe204fa7c457b7484a7edd120ae45e1d604d
2025-06-16 09:04:51 -07:00
Nicola CortiandFacebook GitHub Bot 19fd1536a4 Clarify JS documentation for StatusBar on Android 15+ (#52044)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/52044

This clarifies that some props on `StatusBar` are not working on Android 15+
See https://github.com/facebook/react-native/issues/51928

Changelog:
[Internal] [Changed] -

Reviewed By: yungsters

Differential Revision: D76734324

fbshipit-source-id: 88b6f30f63ca0e269d5115e69a8a2b0ca222c2f4
2025-06-16 08:56:27 -07:00
Nicola CortiandFacebook GitHub Bot f6b7bd93d8 Fix crash on ReactInstance due to null returned for getViewManagerNames (#52035)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/52035

Fixes https://github.com/facebook/react-native/issues/52014

Some OSS library is still returning null for `getViewManagerNames` especially if they're
implementing the `ViewManagerOnDemandReactPackage` in Java.
I'm adding a try-catch here so that we prevent the NPE for those scenarios.

Changelog:
[Android] [Fixed] - Fix crash on ReactInstance due to null returned for getViewManagerNames

Reviewed By: javache

Differential Revision: D76723826

fbshipit-source-id: cc159dee389257c6877b03a67840a45ee5bec165
2025-06-16 07:49:11 -07:00
Riccardo CipolleschiandFacebook GitHub Bot 2ade12d747 Fix OSS CI (#52042)
Summary:
The OSS CI for iOS is broken because of a couple of commit that landed:
- Commit 05a61e8161 : dynamic frameworks are broken
- Commit abc8fe1c92 : pod donwload is broken

This change fixes both of them

## Changelog:
[Internal] - Fix OSS CI

Pull Request resolved: https://github.com/facebook/react-native/pull/52042

Test Plan:
Tested locally by building RNTester with Dynamic frameworks

```
USE_FRAMEWORKS=dynamic bundle exec pod install
```

Reviewed By: rshest, lenaic, GijsWeterings

Differential Revision: D76730331

Pulled By: cipolleschi

fbshipit-source-id: 71cca1f50763d24773dedcd8267130df261b01dc
2025-06-16 07:29:00 -07:00
Tim YungandFacebook GitHub Bot f53f6137e6 RN: Migrate View to Component Syntax (#52018)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/52018

Migrates `View` to use Flow's [Component Syntax](https://flow.org/en/docs/react/component-syntax/).

Component Syntax offers more concise syntax for expressing component definitions and eliminates the need for `View` to be cast as a component type. Component types improve type safety by more accurately modeling `ref` as props and enabling the use of modern type utilities such as `React.PropsOf<Component>` and `React.RefOf<Component>`.

This represents the first introduction of Component Syntax in the open source React Native repository, which is lowered to standard JavaScript through the use of [`hermes-parser`](https://www.npmjs.com/package/hermes-parser) and the following supporting packages:

- [`babel-plugin-syntax-hermes-parser`](https://www.npmjs.com/package/babel-plugin-syntax-hermes-parser)
- [`hermes-eslint`](https://www.npmjs.com/package/hermes-eslint)
- [`prettier-plugin-hermes-parser`](https://www.npmjs.com/package/prettier-plugin-hermes-parser)

By default, [`react-native/babel-preset`](https://www.npmjs.com/package/react-native/babel-preset) already provides configuration for `babel-plugin-syntax-hermes-parser`.

Although this is the first usage of Component Syntax, there are already Flow syntax features used in React Native that have required `hermes-parser` (e.g. [type casting expressions](https://flow.org/en/docs/types/casting/), [type guards](https://flow.org/en/docs/types/type-guards/), [const expressions](https://flow.org/en/docs/types/const-expression/)). As such, we do not anticipate this to introduce new build configuration problems for developers.

Changelog:
[Internal]

Reviewed By: javache, NickGerleman

Differential Revision: D76632879

fbshipit-source-id: e0a83dfc2e2014933000083de1825a311faad707
2025-06-16 06:52:07 -07:00
Samuel SuslaandFacebook GitHub Bot ffe928ce50 pass config in C++ Animated with std::move (#52039)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/52039

changelog: [internal]

config can be passed with std::move to avoid copy.

Reviewed By: rshest, javache

Differential Revision: D76628921

fbshipit-source-id: 82a90ce604d2a82a388eb4fa63f22deda6ec92ed
2025-06-16 04:50:47 -07:00
Samuel SuslaandFacebook GitHub Bot d20b9cfdf0 Fix value passed to animation's endCallback (#52038)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/52038

changelog: [internal]

The changes in this diff include:
- pass endCallback with std::move to avoid copy.
- call endCallback with rawValue instead of rawValue + offset.
- remove unused method AnimationDriver::endCallback.

Reviewed By: javache

Differential Revision: D76627693

fbshipit-source-id: 8653ed3ec59b3629ad3956323b73f02a1bbd9c48
2025-06-16 04:50:47 -07:00
Samuel SuslaandFacebook GitHub Bot b6e1938959 make style.transform testable in Fantom (#52037)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/52037

changelog: [internal]

Reviewed By: javache

Differential Revision: D76606386

fbshipit-source-id: 87321f75803d9e6080d5345021aca9c56b6d2ecf
2025-06-16 04:50:47 -07:00
Samuel SuslaandFacebook GitHub Bot ddafd94124 implement setOffset (#52034)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/52034

changelog: [internal]

implement `setAnimatedNodeOffset`.

Reviewed By: christophpurrer

Differential Revision: D76604263

fbshipit-source-id: 36b86aada836098f502f8d24af07e2a98adf0446
2025-06-16 04:50:47 -07:00
Samuel SuslaandFacebook GitHub Bot eecd07c58b Set final value via direct manipulation to fix animation glitches (#52012)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/52012

changelog: [internal]

On iOS, once a prop on a view is controlled by animated, the control is never released to Fabric or React. That's why it is important to use direct manipulation to commit even final value.

Reviewed By: lenaic

Differential Revision: D76601913

fbshipit-source-id: ea02219e158f28977018b34ac7152b899723b35a
2025-06-16 04:50:47 -07:00
Pieter De BaetsandFacebook GitHub Bot 0e42d33cbc Sync ReactNativeAttributePayload with upstream (#51998)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/51998

Sync with https://github.com/facebook/react/blob/main/packages/react-native-renderer/src/ReactNativeAttributePayloadFabric.js

Next step is to remove the copy in React upstream and consume it from ReactNativePrivateInterface, so I've copied over the tests as well.

Changelog: [Internal]

Reviewed By: mdvacca

Differential Revision: D75943237

fbshipit-source-id: c89c8711b55882f7069c54f6c1ce389f46760089
2025-06-16 04:35:17 -07:00
274 changed files with 18835 additions and 7859 deletions
+1
View File
@@ -48,6 +48,7 @@ packages/react-native/flow/
[options]
enums=true
casting_syntax=both
component_syntax=true
emoji=true
-3
View File
@@ -26,9 +26,6 @@ runs:
- name: Lint code
shell: bash
run: ./.github/workflow-scripts/exec_swallow_error.sh yarn lint --format junit -o ./reports/junit/eslint/results.xml
- name: Lint java
shell: bash
run: ./.github/workflow-scripts/exec_swallow_error.sh yarn lint-java --check
- name: Lint file structure
shell: bash
run: ./.github/workflow-scripts/lint_files.sh
-3
View File
@@ -13,9 +13,6 @@ export GITHUB_REPO=-react-native
echo flow
npm run flow-check --silent --json
echo google-java-format
node scripts/lint-java.js --diff
} | node private/react-native-bots/code-analysis-bot.js
STATUS=$?
-22
View File
@@ -140,27 +140,6 @@ jobs:
hermes-version: ${{ needs.prepare_hermes_workspace.outputs.hermes-version }}
react-native-version: ${{ needs.prepare_hermes_workspace.outputs.react-native-version }}
build_android:
runs-on: 8-core-ubuntu
needs: [set_release_type]
container:
image: reactnativecommunity/react-native-android:latest
env:
TERM: "dumb"
GRADLE_OPTS: "-Dorg.gradle.daemon=false"
ORG_GRADLE_PROJECT_SIGNING_PWD: ${{ secrets.ORG_GRADLE_PROJECT_SIGNING_PWD }}
ORG_GRADLE_PROJECT_SIGNING_KEY: ${{ secrets.ORG_GRADLE_PROJECT_SIGNING_KEY }}
ORG_GRADLE_PROJECT_SONATYPE_USERNAME: ${{ secrets.ORG_GRADLE_PROJECT_SONATYPE_USERNAME }}
ORG_GRADLE_PROJECT_SONATYPE_PASSWORD: ${{ secrets.ORG_GRADLE_PROJECT_SONATYPE_PASSWORD }}
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Build Android
uses: ./.github/actions/build-android
with:
release-type: ${{ needs.set_release_type.outputs.RELEASE_TYPE }}
gradle-cache-encryption-key: ${{ secrets.GRADLE_CACHE_ENCRYPTION_KEY }}
build_npm_package:
runs-on: 8-core-ubuntu
needs:
@@ -170,7 +149,6 @@ jobs:
build_hermes_macos,
build_hermesc_linux,
build_hermesc_windows,
build_android,
prebuild_apple_dependencies,
prebuild_react_native_core,
]
+2 -2
View File
@@ -14,8 +14,8 @@ jobs:
with:
repo-token: ${{ secrets.REACT_NATIVE_BOT_GITHUB_TOKEN }}
days-before-stale: 180
stale-issue-message: 'This issue is stale because it has been open 180 days with no activity. Remove stale label or comment or this will be closed in 7 days.'
stale-pr-message: 'This PR is stale because it has been open 180 days with no activity. Remove stale label or comment or this will be closed in 7 days.'
stale-issue-message: 'This issue is stale because it has been open for 180 days with no activity. It will be closed in 7 days unless you comment on it or remove the "Stale" label.'
stale-pr-message: 'This PR is stale because it has been open for 180 days with no activity. It will be closed in 7 days unless you comment on it or remove the "Stale" label.'
close-issue-message: 'This issue was closed because it has been stalled for 7 days with no activity.'
close-pr-message: 'This PR was closed because it has been stalled for 7 days with no activity.'
exempt-issue-labels: 'Help Wanted :octocat:, Good first issue, Never gets stale, Issue: Author Provided Repro'
-1
View File
@@ -139,7 +139,6 @@ vendor/
/packages/react-native/React/FBReactNativeSpec/
/packages/react-native-codegen/lib
/packages/react-native-codegen/tmp/
/packages/react-native/ReactCommon/react/renderer/components/rncore/
/packages/rn-tester/NativeModuleExample/ScreenshotManagerSpec*
/**/RCTThirdPartyFabricComponentsProvider.*
+16
View File
@@ -321,6 +321,22 @@
- Fix bug: unstable_hasComponent(*) = true for unregistered components for n > 1th call. ([f4d99d6a23](https://github.com/facebook/react-native/commit/f4d99d6a23a08c2ef2c2fff78dc966961608683b) by [@RSNara](https://github.com/RSNara))
- RCTDeviceInfo: fix crash due to failure to get AccessibilityManager ([ac23323da1](https://github.com/facebook/react-native/commit/ac23323da1bda2ce271797aa58dd74ffb0a5992f) by Adam Ernst)
## v0.79.4
### Fixed
- **Codegen:** Backports fix: exclusion of selectively disabled libraries from codegen generation ([3acfe01382](https://github.com/facebook/react-native/commit/3acfe01382e67b16386bb04f7730abc479fec1cc) by [@cipolleschi](https://github.com/cipolleschi))
- **Flow:** Publish top-level Flow types for `react-native` ([50667eceb1](https://github.com/facebook/react-native/commit/50667eceb1be4771375d6a3cc2f4e42d4d8aad3a) by [@aswinandro](https://github.com/aswinandro))
- **Runtime:** Disabling `updateRuntimeShadowNodeReferencesOnCommit` by default. ([2cd074d77a](https://github.com/facebook/react-native/commit/2cd074d77a706f4fe4396ffdf87408f781e935a4) by [@lenaic](https://github.com/lenaic))
#### Android specific
- **TextInput:** Fix broken focus behavior for TextInput in older Android versions (< 9) ([0ebaaed281](https://github.com/facebook/react-native/commit/0ebaaed281fcfa01b20a17a461c1d0b489a7f0a2) by [@joevilches](https://github.com/joevilches))
#### iOS specific
- **Dimensions:** Fix Dimensions not changing when orientation changes ([235ac2cca0](https://github.com/facebook/react-native/commit/235ac2cca0b173934d329f3a797f136a219d315f) by [@riteshshukla04](https://github.com/riteshshukla04))
## v0.79.3
### Fixed
+1 -1
View File
@@ -124,7 +124,7 @@ if (project.findProperty("react.internal.useHermesNightly")?.toString()?.toBoole
configurations.all {
resolutionStrategy.dependencySubstitution {
substitute(project(":packages:react-native:ReactAndroid:hermes-engine"))
.using(module("com.facebook.react:hermes-android:0.0.0-+"))
.using(module("com.facebook.react:hermes-android:0.+"))
.because("Users opted to use hermes from nightly")
}
}
+1 -2
View File
@@ -17,7 +17,6 @@
"format": "npm run prettier && npm run clang-format",
"featureflags": "yarn --cwd packages/react-native featureflags",
"lint-ci": "./.github/workflow-scripts/analyze_code.sh && yarn shellcheck",
"lint-java": "node ./scripts/lint-java.js",
"lint-markdown": "markdownlint-cli2 2>&1",
"lint": "eslint --max-warnings 0 .",
"prettier": "prettier --write \"./**/*.{js,md,yml,ts,tsx}\"",
@@ -55,7 +54,7 @@
"@react-native/metro-babel-transformer": "0.80.0-main",
"@react-native/metro-config": "0.80.0-main",
"@tsconfig/node22": "22.0.2",
"@types/react": "^19.0.0",
"@types/react": "^19.1.0",
"@typescript-eslint/parser": "^7.1.1",
"ansi-styles": "^4.2.1",
"babel-plugin-minify-dead-code-elimination": "^0.5.2",
+2 -2
View File
@@ -1,5 +1,5 @@
@generated SignedSource<<9ada78dff12dcfc7937c8934261f47f4>>
Git revision: 68cfd0ae84acb0ed8e47b421afd64ae3b0b5b727
@generated SignedSource<<cf5be4596d1f07e220b710fc286d02fa>>
Git revision: d95ac13bf0ab64a5e6c2eb18eb138587063b9c34
Built with --nohooks: false
Is local checkout: false
Remote URL: https://github.com/facebook/react-native-devtools-frontend
File diff suppressed because one or more lines are too long
@@ -0,0 +1,116 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
package com.facebook.react.tasks.internal
import javax.inject.Inject
import org.gradle.api.DefaultTask
import org.gradle.api.file.ConfigurableFileCollection
import org.gradle.api.file.CopySpec
import org.gradle.api.file.DirectoryProperty
import org.gradle.api.file.DuplicatesStrategy
import org.gradle.api.file.FileSystemOperations
import org.gradle.api.provider.Property
import org.gradle.api.tasks.*
/**
* A task that takes care of extracting gflags from a source folder/zip and preparing it to be
* consumed by the NDK. This task will also take care of applying the mapping for gflags parameters.
*/
abstract class PrepareGflagsTask : DefaultTask() {
@get:InputFiles abstract val gflagsPath: ConfigurableFileCollection
@get:InputDirectory abstract val gflagsThirdPartyPath: DirectoryProperty
@get:Input abstract val gflagsVersion: Property<String>
@get:OutputDirectory abstract val outputDir: DirectoryProperty
@get:Inject abstract val fs: FileSystemOperations
@TaskAction
fun taskAction() {
val commonCopyConfig: (action: CopySpec) -> Unit = { action ->
action.from(gflagsPath)
action.from(gflagsThirdPartyPath)
action.duplicatesStrategy = DuplicatesStrategy.INCLUDE
action.includeEmptyDirs = false
action.into(outputDir)
}
fs.copy { action ->
commonCopyConfig(action)
action.include(
"gflags-${gflagsVersion.get()}/src/*.h",
"gflags-${gflagsVersion.get()}/src/*.cc",
"CMakeLists.txt")
action.filesMatching("*/src/*") { matchedFile ->
matchedFile.path = "gflags/${matchedFile.name}"
}
}
fs.copy { action ->
commonCopyConfig(action)
action.include("gflags-${gflagsVersion.get()}/src/gflags_declare.h.in")
action.filesMatching("*/src/*") { matchedFile ->
matchedFile.filter { line ->
// Replace all placeholders with appropriate values
// see https://github.com/gflags/gflags/blob/v2.2.0/src/gflags_declare.h.in
line
.replace(Regex("@GFLAGS_NAMESPACE@"), "gflags")
.replace(
Regex(
"@(HAVE_STDINT_H|HAVE_SYS_TYPES_H|HAVE_INTTYPES_H|GFLAGS_INTTYPES_FORMAT_C99)@"),
"1")
.replace(Regex("@([A-Z0-9_]+)@"), "1")
}
matchedFile.path = "gflags/${matchedFile.name.removeSuffix(".in")}"
}
}
fs.copy { action ->
commonCopyConfig(action)
action.include("gflags-${gflagsVersion.get()}/src/config.h.in")
action.filesMatching("*/src/*") { matchedFile ->
matchedFile.filter { line -> line.replace(Regex("^#cmakedefine"), "//cmakedefine") }
matchedFile.path = "gflags/${matchedFile.name.removeSuffix(".in")}"
}
}
fs.copy { action ->
commonCopyConfig(action)
action.include("gflags-${gflagsVersion.get()}/src/gflags_ns.h.in")
action.filesMatching("*/src/*") { matchedFile ->
matchedFile.filter { line ->
line.replace(Regex("@ns@"), "google").replace(Regex("@NS@"), "google".uppercase())
}
matchedFile.path = "gflags/gflags_google.h"
}
}
fs.copy { action ->
commonCopyConfig(action)
action.include("gflags-${gflagsVersion.get()}/src/gflags.h.in")
action.filesMatching("*/src/*") { matchedFile ->
matchedFile.filter { line ->
line
.replace(Regex("@GFLAGS_ATTRIBUTE_UNUSED@"), "")
.replace(Regex("@INCLUDE_GFLAGS_NS_H@"), "#include \"gflags/gflags_google.h\"")
}
matchedFile.path = "gflags/${matchedFile.name.removeSuffix(".in")}"
}
}
fs.copy { action ->
commonCopyConfig(action)
action.include("gflags-${gflagsVersion.get()}/src/gflags_completions.h.in")
action.filesMatching("*/src/*") { matchedFile ->
matchedFile.filter { line -> line.replace(Regex("@GFLAGS_NAMESPACE@"), "gflags") }
matchedFile.path = "gflags/${matchedFile.name.removeSuffix(".in")}"
}
}
}
}
@@ -126,6 +126,8 @@ internal object NdkConfiguratorUtils {
): Pair<List<String>, List<String>> {
val excludes = mutableListOf<String>()
val includes = mutableListOf<String>()
// note: libjsctooling.so is kept here for backward compatibility.
when {
hermesEnabled -> {
excludes.add("**/libjsc.so")
@@ -0,0 +1,209 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
package com.facebook.react.tasks.internal
import com.facebook.react.tests.createProject
import com.facebook.react.tests.createTestTask
import java.io.*
import org.assertj.core.api.Assertions.assertThat
import org.junit.Assert.assertEquals
import org.junit.Rule
import org.junit.Test
import org.junit.rules.TemporaryFolder
class PrepareGflagsTaskTest {
@get:Rule val tempFolder = TemporaryFolder()
@Test(expected = IllegalStateException::class)
fun prepareGflagsTask_withMissingConfiguration_fails() {
val task = createTestTask<PrepareGflagsTask>()
task.taskAction()
}
@Test
fun prepareGflagsTask_copiesCMakefile() {
val gflagspath = tempFolder.newFolder("gflagspath")
val output = tempFolder.newFolder("output")
val project = createProject()
val gflagsThirdPartyPath = File(project.projectDir, "src/main/jni/third-party/gflags/")
val task =
createTestTask<PrepareGflagsTask>(project = project) {
it.gflagsPath.setFrom(gflagspath)
it.gflagsThirdPartyPath.set(gflagsThirdPartyPath)
it.gflagsVersion.set("1.0.0")
it.outputDir.set(output)
}
File(gflagsThirdPartyPath, "CMakeLists.txt").apply {
parentFile.mkdirs()
createNewFile()
}
task.taskAction()
assertThat(output.listFiles()!!.any { it.name == "CMakeLists.txt" }).isTrue()
}
@Test
fun prepareGflagsTask_copiesSourceCodeAndHeaders() {
val gflagspath = tempFolder.newFolder("gflagspath")
val gflagsThirdPartyPath = tempFolder.newFolder("gflagspath/jni")
val output = tempFolder.newFolder("output")
val task =
createTestTask<PrepareGflagsTask> {
it.gflagsPath.setFrom(gflagspath)
it.gflagsThirdPartyPath.set(gflagsThirdPartyPath)
it.gflagsVersion.set("1.0.0")
it.outputDir.set(output)
}
File(gflagspath, "gflags-1.0.0/src/gflags.cc").apply {
parentFile.mkdirs()
createNewFile()
}
File(gflagspath, "gflags-1.0.0/src/util.h").apply {
parentFile.mkdirs()
createNewFile()
}
task.taskAction()
assertThat(File(output, "gflags/gflags.cc").exists()).isTrue()
assertThat(File(output, "gflags/util.h").exists()).isTrue()
}
@Test
fun prepareGflagsTask_replacesTokenCorrectly() {
val gflagspath = tempFolder.newFolder("gflagspath")
val gflagsThirdPartyPath = tempFolder.newFolder("gflagspath/jni")
val output = tempFolder.newFolder("output")
val task =
createTestTask<PrepareGflagsTask> {
it.gflagsPath.setFrom(gflagspath)
it.gflagsThirdPartyPath.set(gflagsThirdPartyPath)
it.gflagsVersion.set("1.0.0")
it.outputDir.set(output)
}
File(gflagspath, "gflags-1.0.0/src/gflags_declare.h.in").apply {
parentFile.mkdirs()
writeText(
"""
#define GFLAGS_NAMESPACE @GFLAGS_NAMESPACE@
#include <string>
#if @HAVE_STDINT_H@
# include <stdint.h>
#elif @HAVE_SYS_TYPES_H@
# include <sys/types.h>
#elif @HAVE_INTTYPES_H@
# include <inttypes.h>
#endif
namespace GFLAGS_NAMESPACE {
#if @GFLAGS_INTTYPES_FORMAT_C99@ // C99
typedef int32_t int32;
typedef uint32_t uint32;
typedef int64_t int64;
typedef uint64_t uint64;
#elif @GFLAGS_INTTYPES_FORMAT_BSD@ // BSD
typedef int32_t int32;
typedef u_int32_t uint32;
typedef int64_t int64;
typedef u_int64_t uint64;
#elif @GFLAGS_INTTYPES_FORMAT_VC7@ // Windows
typedef __int32 int32;
typedef unsigned __int32 uint32;
typedef __int64 int64;
typedef unsigned __int64 uint64;
#else
# error Do not know how to define a 32-bit integer quantity on your system
#endif
} // namespace GFLAGS_NAMESPACE
""")
}
File(gflagspath, "gflags-1.0.0/src/config.h.in").apply {
parentFile.mkdirs()
createNewFile()
writeText("#cmakedefine")
}
File(gflagspath, "gflags-1.0.0/src/gflags_ns.h.in").apply {
parentFile.mkdirs()
createNewFile()
writeText("@ns@ @NS@")
}
File(gflagspath, "gflags-1.0.0/src/gflags.h.in").apply {
parentFile.mkdirs()
createNewFile()
writeText("@GFLAGS_ATTRIBUTE_UNUSED@\n@INCLUDE_GFLAGS_NS_H@")
}
File(gflagspath, "gflags-1.0.0/src/gflags_completions.h.in").apply {
parentFile.mkdirs()
createNewFile()
writeText("@GFLAGS_NAMESPACE@")
}
task.taskAction()
val declareFile = File(output, "gflags/gflags_declare.h")
assertThat(declareFile.exists()).isTrue()
assertEquals(
declareFile.readText(),
"""
#define GFLAGS_NAMESPACE gflags
#include <string>
#if 1
# include <stdint.h>
#elif 1
# include <sys/types.h>
#elif 1
# include <inttypes.h>
#endif
namespace GFLAGS_NAMESPACE {
#if 1 // C99
typedef int32_t int32;
typedef uint32_t uint32;
typedef int64_t int64;
typedef uint64_t uint64;
#elif 1 // BSD
typedef int32_t int32;
typedef u_int32_t uint32;
typedef int64_t int64;
typedef u_int64_t uint64;
#elif 1 // Windows
typedef __int32 int32;
typedef unsigned __int32 uint32;
typedef __int64 int64;
typedef unsigned __int64 uint64;
#else
# error Do not know how to define a 32-bit integer quantity on your system
#endif
} // namespace GFLAGS_NAMESPACE
""")
val configFile = File(output, "gflags/config.h")
assertThat(configFile.exists()).isTrue()
assertEquals(configFile.readText(), "//cmakedefine")
val nsFile = File(output, "gflags/gflags_google.h")
assertThat(nsFile.exists()).isTrue()
assertEquals(nsFile.readText(), "google GOOGLE")
val gflagsFile = File(output, "gflags/gflags.h")
assertThat(gflagsFile.exists()).isTrue()
assertEquals(gflagsFile.readText(), "\n#include \"gflags/gflags_google.h\"")
val completionsFile = File(output, "gflags/gflags_completions.h")
assertThat(completionsFile.exists()).isTrue()
assertEquals(completionsFile.readText(), "gflags")
}
}
+1 -1
View File
@@ -20,7 +20,7 @@
],
"dependencies": {},
"peerDependencies": {
"@types/react": "^19.0.0",
"@types/react": "^19.1.0",
"react": "*",
"react-native": "*"
},
+2 -1
View File
@@ -15,8 +15,9 @@ const lazyImports = require('./lazy-imports');
const EXCLUDED_FIRST_PARTY_PATHS = [
/[/\\]node_modules[/\\]/,
/[/\\]packages[/\\]react-native(?:-fantom)?[/\\]/,
/[/\\]packages[/\\]react-native[/\\]/,
/[/\\]packages[/\\]virtualized-lists[/\\]/,
/[/\\]private[/\\]react-native-fantom[/\\]/,
];
function isTypeScriptSource(fileName) {
+9 -9
View File
@@ -73,9 +73,9 @@ const ALL_GENERATORS = {
generateViewConfigJs: generateViewConfigJs.generate,
};
type FilesOutput = Map<string, string>;
export type FilesOutput = Map<string, string>;
type GenerateFunction = (
export type GenerateFunction = (
libraryName: string,
schema: SchemaType,
packageName?: string,
@@ -83,11 +83,11 @@ type GenerateFunction = (
headerPrefix?: string,
) => FilesOutput;
type LibraryGeneratorsFunctions = $ReadOnly<{
export type LibraryGeneratorsFunctions = $ReadOnly<{
[string]: Array<GenerateFunction>,
}>;
type LibraryOptions = $ReadOnly<{
export type LibraryOptions = $ReadOnly<{
libraryName: string,
schema: SchemaType,
outputDirectory: string,
@@ -97,13 +97,13 @@ type LibraryOptions = $ReadOnly<{
libraryGenerators?: LibraryGeneratorsFunctions,
}>;
type SchemasOptions = $ReadOnly<{
export type SchemasOptions = $ReadOnly<{
schemas: {[string]: SchemaType},
outputDirectory: string,
supportedApplePlatforms?: {[string]: {[string]: boolean}},
}>;
type LibraryGenerators =
export type LibraryGenerators =
| 'componentsAndroid'
| 'componentsIOS'
| 'descriptors'
@@ -116,14 +116,14 @@ type LibraryGenerators =
| 'modulesCxx'
| 'modulesIOS';
type SchemasGenerators = 'providerIOS';
export type SchemasGenerators = 'providerIOS';
type LibraryConfig = $ReadOnly<{
export type LibraryConfig = $ReadOnly<{
generators: Array<LibraryGenerators>,
test?: boolean,
}>;
type SchemasConfig = $ReadOnly<{
export type SchemasConfig = $ReadOnly<{
generators: Array<SchemasGenerators>,
test?: boolean,
}>;
@@ -18,7 +18,10 @@ const flowFixtures = require('../../flow/modules/__test_fixtures__/fixtures.js')
const tsFixtures = require('../../typescript/modules/__test_fixtures__/fixtures.js');
const {compareSnaps, compareTsArraySnaps} = require('../compareSnaps.js');
const flowExtraCases = ['PROMISE_WITH_COMMONLY_USED_TYPES'];
const flowExtraCases = [
'NATIVE_MODULE_WITH_OPAQUE_TYPES',
'PROMISE_WITH_COMMONLY_USED_TYPES',
];
const tsExtraCases = [
'NATIVE_MODULE_WITH_ARRAY2_WITH_ALIAS',
'NATIVE_MODULE_WITH_ARRAY2_WITH_UNION_AND_TOUPLE',
@@ -691,6 +691,37 @@ export default TurboModuleRegistry.getEnforcing<Spec>('SampleTurboModule');
`;
const NATIVE_MODULE_WITH_OPAQUE_TYPES = `
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow strict-local
* @format
*/
'use strict';
import type {TurboModule} from '../RCTExport';
import * as TurboModuleRegistry from '../TurboModuleRegistry';
export opaque type Task = mixed;
export opaque type TimeoutID = number;
export interface Spec extends TurboModule {
+createTask: (callback: () => void) => Task;
+cancelTask: (task: Task) => void;
+setTimeout: (callback: () => void) => TimeoutID;
+clearTimeout: (timeoutID: TimeoutID) => void;
}
export default TurboModuleRegistry.getEnforcing<Spec>('SampleTurboModule');
`;
const ANDROID_ONLY_NATIVE_MODULE = `
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
@@ -976,6 +1007,7 @@ module.exports = {
NATIVE_MODULE_WITH_UNION,
NATIVE_MODULE_WITH_UNION_RETURN_TYPES,
NATIVE_MODULE_WITH_EVENT_EMITTERS,
NATIVE_MODULE_WITH_OPAQUE_TYPES,
EMPTY_NATIVE_MODULE,
ANDROID_ONLY_NATIVE_MODULE,
IOS_ONLY_NATIVE_MODULE,
@@ -2187,6 +2187,108 @@ exports[`RN Codegen Flow Parser can generate fixture NATIVE_MODULE_WITH_OBJECT_W
}"
`;
exports[`RN Codegen Flow Parser can generate fixture NATIVE_MODULE_WITH_OPAQUE_TYPES 1`] = `
"{
'modules': {
'NativeSampleTurboModule': {
'type': 'NativeModule',
'aliasMap': {},
'enumMap': {},
'spec': {
'eventEmitters': [],
'methods': [
{
'name': 'createTask',
'optional': false,
'typeAnnotation': {
'type': 'FunctionTypeAnnotation',
'returnTypeAnnotation': {
'type': 'GenericObjectTypeAnnotation'
},
'params': [
{
'name': 'callback',
'optional': false,
'typeAnnotation': {
'type': 'FunctionTypeAnnotation',
'returnTypeAnnotation': {
'type': 'VoidTypeAnnotation'
},
'params': []
}
}
]
}
},
{
'name': 'cancelTask',
'optional': false,
'typeAnnotation': {
'type': 'FunctionTypeAnnotation',
'returnTypeAnnotation': {
'type': 'VoidTypeAnnotation'
},
'params': [
{
'name': 'task',
'optional': false,
'typeAnnotation': {
'type': 'GenericObjectTypeAnnotation'
}
}
]
}
},
{
'name': 'setTimeout',
'optional': false,
'typeAnnotation': {
'type': 'FunctionTypeAnnotation',
'returnTypeAnnotation': {
'type': 'NumberTypeAnnotation'
},
'params': [
{
'name': 'callback',
'optional': false,
'typeAnnotation': {
'type': 'FunctionTypeAnnotation',
'returnTypeAnnotation': {
'type': 'VoidTypeAnnotation'
},
'params': []
}
}
]
}
},
{
'name': 'clearTimeout',
'optional': false,
'typeAnnotation': {
'type': 'FunctionTypeAnnotation',
'returnTypeAnnotation': {
'type': 'VoidTypeAnnotation'
},
'params': [
{
'name': 'timeoutID',
'optional': false,
'typeAnnotation': {
'type': 'NumberTypeAnnotation'
}
}
]
}
}
]
},
'moduleName': 'SampleTurboModule'
}
}
}"
`;
exports[`RN Codegen Flow Parser can generate fixture NATIVE_MODULE_WITH_PARTIALS 1`] = `
"{
'modules': {
@@ -296,6 +296,7 @@ class FlowParser implements Parser {
if (
node.declaration != null &&
(node.declaration.type === 'TypeAlias' ||
node.declaration.type === 'OpaqueType' ||
node.declaration.type === 'InterfaceDeclaration')
) {
types[node.declaration.id.name] = node.declaration;
@@ -309,6 +310,7 @@ class FlowParser implements Parser {
types[node.declaration.id.name] = node.declaration;
} else if (
node.type === 'TypeAlias' ||
node.type === 'OpaqueType' ||
node.type === 'InterfaceDeclaration' ||
node.type === 'EnumDeclaration'
) {
@@ -543,6 +545,10 @@ class FlowParser implements Parser {
}
nextNodeForTypeAlias(typeAnnotation: $FlowFixMe): $FlowFixMe {
if (typeAnnotation.type === 'OpaqueType') {
return typeAnnotation.impltype;
}
return typeAnnotation.right;
}
@@ -1233,7 +1233,8 @@ function handleGenericTypeAnnotation(
let node;
switch (resolvedTypeAnnotation.type) {
case parser.typeAlias: {
case parser.typeAlias:
case 'OpaqueType': {
typeResolutionStatus = getTypeResolutionStatus(
'alias',
typeAnnotation,
@@ -24,7 +24,7 @@
"@react-native/codegen": "0.80.0-main"
},
"peerDependencies": {
"@types/react": "^19.0.0",
"@types/react": "^19.1.0",
"react": "*",
"react-native": "*"
},
@@ -4,6 +4,7 @@
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @fantom_flags cxxNativeAnimatedRemoveJsSync:*
* @flow strict-local
* @format
*/
@@ -16,6 +17,7 @@ import ensureInstance from '../../../src/private/__tests__/utilities/ensureInsta
import * as Fantom from '@react-native/fantom';
import {createRef} from 'react';
import {Animated, View, useAnimatedValue} from 'react-native';
import * as ReactNativeFeatureFlags from 'react-native/src/private/featureflags/ReactNativeFeatureFlags';
import ReactNativeElement from 'react-native/src/private/webapis/dom/nodes/ReactNativeElement';
test('moving box by 100 points', () => {
@@ -32,11 +34,9 @@ test('moving box by 100 points', () => {
{
width: 100,
height: 100,
backgroundColor: 'red',
},
{transform: [{translateX}]},
]}
testID="box"
/>
);
}
@@ -75,11 +75,15 @@ test('moving box by 100 points', () => {
Fantom.unstable_produceFramesForDuration(500);
// Animation is completed now. C++ Animated will commit the final position to the shadow tree.
expect(viewElement.getBoundingClientRect().x).toBe(100);
if (ReactNativeFeatureFlags.cxxNativeAnimatedRemoveJsSync()) {
expect(viewElement.getBoundingClientRect().x).toBe(100);
// TODO(T223344928): this shouldn't be neccessary
Fantom.runWorkLoop();
} else {
expect(viewElement.getBoundingClientRect().x).toBe(0);
Fantom.runWorkLoop(); // Animated still schedules a React state update for synchronisation to shadow tree
}
// TODO: this shouldn't be needed but C++ Animated still schedules a React state update
// for synchronisation, even though it doesn't need to.
Fantom.runWorkLoop();
expect(viewElement.getBoundingClientRect().x).toBe(100);
});
@@ -143,6 +147,331 @@ test('animation driven by onScroll event', () => {
expect(transform.translateY).toBeCloseTo(100, 0.001);
// TODO(T226364699): this should `toBe(100)` but we are not syncing shadow tree yet.
expect(viewElement.getBoundingClientRect().y).toBe(0);
expect(viewElement.getBoundingClientRect().y).toBe(100);
});
test('animated opacity', () => {
let _opacity;
const viewRef = createRef<HostInstance>();
function MyApp() {
const opacity = useAnimatedValue(1);
_opacity = opacity;
return (
<Animated.View
ref={viewRef}
style={[
{
width: 100,
height: 100,
opacity: opacity,
},
]}
/>
);
}
const root = Fantom.createRoot();
Fantom.runTask(() => {
root.render(<MyApp />);
});
const viewElement = ensureInstance(viewRef.current, ReactNativeElement);
expect(viewElement.getBoundingClientRect().x).toBe(0);
Fantom.runTask(() => {
Animated.timing(_opacity, {
toValue: 0,
duration: 30,
useNativeDriver: true,
}).start();
});
Fantom.unstable_produceFramesForDuration(30);
expect(Fantom.unstable_getDirectManipulationProps(viewElement).opacity).toBe(
0,
);
// TODO(T223344928): this shouldn't be neccessary with cxxNativeAnimatedRemoveJsSync:true
Fantom.runWorkLoop();
expect(root.getRenderedOutput({props: ['opacity']}).toJSX()).toEqual(
<rn-view opacity="0" />,
);
});
test('moving box by 50 points with offset 10', () => {
let _translateX;
const viewRef = createRef<HostInstance>();
function MyApp() {
const translateX = useAnimatedValue(0);
_translateX = translateX;
return (
<Animated.View
ref={viewRef}
style={[
{
width: 100,
height: 100,
},
{transform: [{translateX}]},
]}
/>
);
}
const root = Fantom.createRoot();
Fantom.runTask(() => {
root.render(<MyApp />);
});
const viewElement = ensureInstance(viewRef.current, ReactNativeElement);
expect(viewElement.getBoundingClientRect().x).toBe(0);
let finishValue = null;
Fantom.runTask(() => {
Animated.timing(_translateX, {
toValue: 50,
duration: 1000, // 1 second
useNativeDriver: true,
}).start(result => {
finishValue = result;
});
});
Fantom.runTask(() => {
_translateX.setOffset(10);
});
Fantom.unstable_produceFramesForDuration(500);
// shadow tree is not synchronised yet, position X is still 0.
expect(viewElement.getBoundingClientRect().x).toBe(0);
expect(
// $FlowFixMe[incompatible-use]
Fantom.unstable_getDirectManipulationProps(viewElement).transform[0]
.translateX,
).toBeCloseTo(35, 0.001);
Fantom.unstable_produceFramesForDuration(500);
expect(
// $FlowFixMe[incompatible-use]
Fantom.unstable_getDirectManipulationProps(viewElement).transform[0]
.translateX,
).toBeCloseTo(60, 0.001);
if (ReactNativeFeatureFlags.cxxNativeAnimatedRemoveJsSync()) {
expect(root.getRenderedOutput({props: ['transform']}).toJSX()).toEqual(
<rn-view transform='[{"translateX": 60.000000}]' />,
);
// TODO(T223344928): this shouldn't be neccessary
Fantom.runWorkLoop();
} else {
expect(root.getRenderedOutput({props: ['transform']}).toJSX()).toEqual(
<rn-view transform="[]" />,
);
Fantom.runWorkLoop(); // Animated still schedules a React state update for synchronisation to shadow tree
}
expect(root.getRenderedOutput({props: ['transform']}).toJSX()).toEqual(
<rn-view transform='[{"translateX": 60.000000}]' />, // // must include offset.
);
expect(finishValue?.finished).toBe(true);
expect(finishValue?.value).toBe(50); // must not include offset.
expect(finishValue?.offset).toBe(10);
});
describe('Value.flattenOffset', () => {
it('accumulates offset with onScroll value', () => {
const scrollViewRef = createRef<HostInstance>();
const viewRef = createRef<HostInstance>();
let _onScroll;
function PressableWithNativeDriver() {
_onScroll = useAnimatedValue(0);
return (
<View style={{flex: 1}}>
<Animated.View
ref={viewRef}
style={{
position: 'absolute',
width: 10,
height: 10,
transform: [{translateY: _onScroll}],
}}
/>
<Animated.ScrollView
ref={scrollViewRef}
onScroll={Animated.event(
[
{
nativeEvent: {
contentOffset: {
y: _onScroll,
},
},
},
],
{useNativeDriver: true},
)}>
<View style={{height: 1000, width: 100}} />
</Animated.ScrollView>
</View>
);
}
const root = Fantom.createRoot();
Fantom.runTask(() => {
root.render(<PressableWithNativeDriver />);
});
const fn = jest.fn();
Fantom.runTask(() => {
_onScroll.addListener(fn);
});
const scrollViewelement = ensureInstance(
scrollViewRef.current,
ReactNativeElement,
);
const viewElement = ensureInstance(viewRef.current, ReactNativeElement);
Fantom.scrollTo(scrollViewelement, {
x: 0,
y: 10,
});
expect(fn).toBeCalledWith({value: 10});
Fantom.runTask(() => {
_onScroll.setOffset(15);
_onScroll.flattenOffset();
});
expect(fn).toHaveBeenCalledTimes(1);
Fantom.runTask(() => {
_onScroll.setOffset(15);
});
expect(fn).toHaveBeenCalledTimes(1);
let transform =
// $FlowFixMe[incompatible-use]
Fantom.unstable_getDirectManipulationProps(viewElement).transform[0];
expect(transform.translateY).toBeCloseTo(40, 0.001);
// TODO(T223344928): this shouldn't be neccessary with cxxNativeAnimatedRemoveJsSync:true
Fantom.runWorkLoop();
});
});
describe('Value.extractOffset', () => {
it('sets the offset value to the base value and resets the base value to zero', () => {
const scrollViewRef = createRef<HostInstance>();
const viewRef = createRef<HostInstance>();
let _onScroll;
function PressableWithNativeDriver() {
_onScroll = useAnimatedValue(0);
return (
<View style={{flex: 1}}>
<Animated.View
ref={viewRef}
style={{
position: 'absolute',
width: 10,
height: 10,
transform: [{translateY: _onScroll}],
}}
/>
<Animated.ScrollView
ref={scrollViewRef}
onScroll={Animated.event(
[
{
nativeEvent: {
contentOffset: {
y: _onScroll,
},
},
},
],
{useNativeDriver: true},
)}>
<View style={{height: 1000, width: 100}} />
</Animated.ScrollView>
</View>
);
}
const root = Fantom.createRoot();
Fantom.runTask(() => {
root.render(<PressableWithNativeDriver />);
});
const fn = jest.fn();
Fantom.runTask(() => {
_onScroll.addListener(fn);
});
const scrollViewelement = ensureInstance(
scrollViewRef.current,
ReactNativeElement,
);
const viewElement = ensureInstance(viewRef.current, ReactNativeElement);
Fantom.scrollTo(scrollViewelement, {
x: 0,
y: 10,
});
expect(fn).toBeCalledWith({value: 10});
Fantom.runTask(() => {
_onScroll.setOffset(15);
// Sets offset to be 15 + 10 = 25 (this is not observable from JS).
_onScroll.extractOffset();
});
expect(fn).toHaveBeenCalledTimes(1);
let transform =
// $FlowFixMe[incompatible-use]
Fantom.unstable_getDirectManipulationProps(viewElement).transform[0];
// Animated value is now 0 but offset is 25. The final value is 25.
expect(transform.translateY).toBeCloseTo(25, 0.001);
Fantom.runTask(() => {
// Sets offset 35, overriding the previous `setOffset`.
// Due to `extractOffset`, base value was restarted to 0.
_onScroll.setOffset(35);
});
expect(fn).toHaveBeenCalledTimes(1);
transform =
// $FlowFixMe[incompatible-use]
Fantom.unstable_getDirectManipulationProps(viewElement).transform[0];
// `extractOffset` resets value back to 0.
// Previously we set offset to 35. The final value is 35.
expect(transform.translateY).toBeCloseTo(35, 0.001);
// TODO(T223344928): this shouldn't be neccessary with cxxNativeAnimatedRemoveJsSync:true
Fantom.runWorkLoop();
});
});
@@ -13,6 +13,7 @@ import type AnimatedNode from '../nodes/AnimatedNode';
import type AnimatedValue from '../nodes/AnimatedValue';
import NativeAnimatedHelper from '../../../src/private/animated/NativeAnimatedHelper';
import * as ReactNativeFeatureFlags from '../../../src/private/featureflags/ReactNativeFeatureFlags';
import AnimatedProps from '../nodes/AnimatedProps';
export type EndResult = {
@@ -149,8 +150,15 @@ export default class Animation {
if (value != null) {
animatedValue.__onAnimatedValueUpdateReceived(value, offset);
if (this.__isLooping === true) {
return;
if (
!(
ReactNativeFeatureFlags.cxxNativeAnimatedEnabled() &&
ReactNativeFeatureFlags.cxxNativeAnimatedRemoveJsSync()
)
) {
if (this.__isLooping === true) {
return;
}
}
// Once the JS side node is synced with the updated values, trigger an
@@ -14,12 +14,8 @@
#import <memory>
#if USE_THIRD_PARTY_JSC != 1
#if __has_include(<jsireact/HermesExecutorFactory.h>)
#import <jsireact/HermesExecutorFactory.h>
#elif __has_include(<reacthermes/HermesExecutorFactory.h>)
#import <reacthermes/HermesExecutorFactory.h>
#endif
#endif
#import <ReactCommon/RCTTurboModuleManager.h>
#import <jsireact/JSIExecutor.h>
+1
View File
@@ -63,6 +63,7 @@ export type ButtonProps = $ReadOnly<{
@platform tv
@default false
@deprecated Use `focusable` instead
*/
hasTVPreferredFocus?: ?boolean,
@@ -13,12 +13,21 @@ import {NativeMethods} from '../../../types/public/ReactNativeTypes';
import {ViewProps} from '../View/ViewPropTypes';
/**
* @deprecated
* Use `react-native-safe-area-context` instead. This component is deprecated and will be removed in a future release.
*
* Renders nested content and automatically applies paddings reflect the portion of the view
* that is not covered by navigation bars, tab bars, toolbars, and other ancestor views.
* Moreover, and most importantly, Safe Area's paddings reflect physical limitation of the screen,
* such as rounded corners or camera notches (aka sensor housing area on iPhone X).
*/
declare class SafeAreaViewComponent extends React.Component<ViewProps> {}
declare const SafeAreaViewBase: Constructor<NativeMethods> &
typeof SafeAreaViewComponent;
/**
* @deprecated
* Use `react-native-safe-area-context` instead. This component is deprecated and will be removed in a future release.
*/
export class SafeAreaView extends SafeAreaViewBase {}
@@ -22,6 +22,7 @@ import * as React from 'react';
* Moreover, and most importantly, Safe Area's paddings reflect physical
* limitation of the screen, such as rounded corners or camera notches (aka
* sensor housing area on iPhone X).
* @deprecated Use `react-native-safe-area-context` instead. This component will be removed in a future release.
*/
const exported: component(
ref?: React.RefSetter<React.ElementRef<typeof View>>,
@@ -35,6 +35,8 @@ export interface StatusBarPropsAndroid {
/**
* The background color of the status bar.
*
* Please note that this prop has no effect on Android 15+
*
* @platform android
*/
backgroundColor?: ColorValue | undefined;
@@ -44,6 +46,8 @@ export interface StatusBarPropsAndroid {
* the app will draw under the status bar. This is useful when using a
* semi transparent status bar color.
*
* Please note that this prop has no effect on Android 15+
*
* @platform android
*/
translucent?: boolean | undefined;
@@ -58,6 +58,9 @@ export type StatusBarAnimation = $Keys<{
export type StatusBarPropsAndroid = $ReadOnly<{
/**
* The background color of the status bar.
*
* Please note that this prop has no effect on Android 15+
*
* @platform android
*/
backgroundColor?: ?ColorValue,
@@ -66,6 +69,8 @@ export type StatusBarPropsAndroid = $ReadOnly<{
* When translucent is set to true, the app will draw under the status bar.
* This is useful when using a semi transparent status bar color.
*
* Please note that this prop has no effect on Android 15+
*
* @platform android
*/
translucent?: ?boolean,
@@ -30,6 +30,9 @@ type AndroidProps = $ReadOnly<{
}>;
type IOSProps = $ReadOnly<{
/**
* @deprecated Use `focusable` instead
*/
hasTVPreferredFocus?: ?boolean,
}>;
@@ -29,6 +29,7 @@ type TVProps = {
* *(Apple TV only)* TV preferred focus (see documentation for the View component).
*
* @platform ios
* @deprecated Use `focusable` instead
*/
hasTVPreferredFocus?: ?boolean,
@@ -16,6 +16,7 @@ export interface TVProps {
* *(Apple TV only)* TV preferred focus (see documentation for the View component).
*
* @platform ios
* @deprecated Use `focusable` instead
*/
hasTVPreferredFocus?: boolean | undefined;
@@ -26,6 +26,7 @@ export type TVProps = $ReadOnly<{
* *(Apple TV only)* TV preferred focus (see documentation for the View component).
*
* @platform ios
* @deprecated Use `focusable` instead
*/
hasTVPreferredFocus?: ?boolean,
+12 -13
View File
@@ -16,11 +16,6 @@ import ViewNativeComponent from './ViewNativeComponent';
import * as React from 'react';
import {use} from 'react';
type PropsWithRef = $ReadOnly<{
ref?: React.RefSetter<React.ElementRef<typeof ViewNativeComponent>>,
...ViewProps,
}>;
/**
* The most fundamental component for building a UI, View is a container that
* supports layout with flexbox, style, some touch handling, and accessibility
@@ -28,7 +23,10 @@ type PropsWithRef = $ReadOnly<{
*
* @see https://reactnative.dev/docs/view
*/
function View(props: PropsWithRef): React.Node {
export default component View(
ref?: React.RefSetter<React.ElementRef<typeof ViewNativeComponent>>,
...props: ViewProps
) {
const hasTextAncestor = use(TextAncestor);
let actualView;
@@ -55,7 +53,7 @@ function View(props: PropsWithRef): React.Node {
} = props;
// Since we destructured props, we can now treat it as mutable
const processedProps = otherProps as {...PropsWithRef};
const processedProps = otherProps as {...ViewProps};
const parsedAriaLabelledBy = ariaLabelledBy?.split(/\s*,\s*/g);
if (parsedAriaLabelledBy !== undefined) {
@@ -118,7 +116,12 @@ function View(props: PropsWithRef): React.Node {
};
}
actualView = <ViewNativeComponent {...processedProps} />;
actualView =
ref == null ? (
<ViewNativeComponent {...processedProps} />
) : (
<ViewNativeComponent {...processedProps} ref={ref} />
);
} else {
const {
accessibilityElementsHidden,
@@ -198,6 +201,7 @@ function View(props: PropsWithRef): React.Node {
: importantForAccessibility
}
nativeID={id ?? nativeID}
ref={ref}
/>
);
}
@@ -207,8 +211,3 @@ function View(props: PropsWithRef): React.Node {
}
return actualView;
}
export default View as component(
ref?: React.RefSetter<React.ElementRef<typeof ViewNativeComponent>>,
...props: ViewProps
);
@@ -374,13 +374,6 @@ export type AccessibilityProps = $ReadOnly<{
*/
'aria-label'?: ?Stringish,
/**
* Defines the order in which descendant elements receive accessibility focus.
* The elements in the array represent nativeID values for the respective
* descendant elements.
*/
experimental_accessibilityOrder?: ?Array<string>,
/**
* Indicates to accessibility services to treat UI component like a specific role.
*/
@@ -21,6 +21,9 @@ import {
import {Touchable} from '../Touchable/Touchable';
import {AccessibilityProps} from './ViewAccessibility';
/**
* @deprecated These properties are not implemented natively.
*/
export interface TVViewPropsIOS {
/**
* *(Apple TV only)* When set to true, this view will be focusable
@@ -34,6 +37,7 @@ export interface TVViewPropsIOS {
* *(Apple TV only)* May be set to true to force the Apple TV focus engine to move focus to this view.
*
* @platform ios
* @deprecated Use `focusable` instead
*/
hasTVPreferredFocus?: boolean | undefined;
@@ -280,6 +280,7 @@ export type ViewPropsAndroid = $ReadOnly<{
* Whether to force the Android TV focus engine to move focus to this view.
*
* @platform android
* @deprecated Use `focusable` instead
*/
hasTVPreferredFocus?: ?boolean,
@@ -489,6 +490,13 @@ type ViewBaseProps = $ReadOnly<{
* See https://reactnative.dev/docs/view#removeclippedsubviews
*/
removeClippedSubviews?: ?boolean,
/**
* Defines the order in which descendant elements receive accessibility focus.
* The elements in the array represent nativeID values for the respective
* descendant elements.
*/
experimental_accessibilityOrder?: ?Array<string>,
}>;
export type ViewProps = $ReadOnly<{
@@ -169,6 +169,20 @@ describe('<View>', () => {
});
});
describe('transform style', () => {
it('causes view to be unflattened', () => {
const root = Fantom.createRoot();
Fantom.runTask(() => {
root.render(<View style={{transform: [{translateX: 10}]}} />);
});
expect(root.getRenderedOutput({props: ['transform']}).toJSX()).toEqual(
<rn-view transform='[{"translateX": 10.000000}]' />,
);
});
});
describe('props', () => {
describe('pointerEvents', () => {
it('auto does not propagate to the mounting layer, it is the default', () => {
@@ -31,8 +31,8 @@ describe('<Button>', () => {
.toJSX(),
).toEqual(
// Upper case on Android (also used by Fantom)
<rn-view backgroundColor="rgba(33, 150, 243, 255)">
<rn-paragraph foregroundColor="rgba(255, 255, 255, 255)">
<rn-view backgroundColor="rgba(33, 150, 243, 1)">
<rn-paragraph foregroundColor="rgba(255, 255, 255, 1)">
HELLO
</rn-paragraph>
</rn-view>,
@@ -53,8 +53,8 @@ describe('<Button>', () => {
.getRenderedOutput({props: ['foregroundColor', 'backgroundColor']})
.toJSX(),
).toEqual(
<rn-view backgroundColor="rgba(0, 0, 255, 255)">
<rn-paragraph foregroundColor="rgba(255, 255, 255, 255)">
<rn-view backgroundColor="rgba(0, 0, 255, 1)">
<rn-paragraph foregroundColor="rgba(255, 255, 255, 1)">
HELLO
</rn-paragraph>
</rn-view>,
@@ -99,8 +99,8 @@ describe('<Button>', () => {
.getRenderedOutput({props: ['foregroundColor', 'backgroundColor']})
.toJSX(),
).toEqual(
<rn-view backgroundColor="rgba(223, 223, 223, 255)">
<rn-paragraph foregroundColor="rgba(161, 161, 161, 255)">
<rn-view backgroundColor="rgba(223, 223, 223, 1)">
<rn-paragraph foregroundColor="rgba(161, 161, 161, 1)">
HELLO
</rn-paragraph>
</rn-view>,
@@ -181,8 +181,8 @@ describe('<Button>', () => {
.getRenderedOutput({props: ['foregroundColor', 'backgroundColor']})
.toJSX(),
).toEqual(
<rn-view backgroundColor="rgba(33, 150, 243, 255)">
<rn-paragraph foregroundColor="rgba(255, 255, 255, 255)">
<rn-view backgroundColor="rgba(33, 150, 243, 1)">
<rn-paragraph foregroundColor="rgba(255, 255, 255, 1)">
HELLO
</rn-paragraph>
</rn-view>,
@@ -223,8 +223,8 @@ describe('<Button>', () => {
.getRenderedOutput({props: ['foregroundColor', 'backgroundColor']})
.toJSX(),
).toEqual(
<rn-view backgroundColor="rgba(223, 223, 223, 255)">
<rn-paragraph foregroundColor="rgba(161, 161, 161, 255)">
<rn-view backgroundColor="rgba(223, 223, 223, 1)">
<rn-paragraph foregroundColor="rgba(161, 161, 161, 1)">
HELLO
</rn-paragraph>
</rn-view>,
+8 -2
View File
@@ -35,9 +35,9 @@ export interface ModalBaseProps {
*/
visible?: boolean | undefined;
/**
* The `onRequestClose` callback is called when the user taps the hardware back button on Android or the menu button on Apple TV.
* The `onRequestClose` callback is called when the user taps the hardware back button on Android, dismisses the sheet using a gesture on iOS (when `allowSwipeDismissal` is set to true) or the menu button on Apple TV.
*
* This is required on Apple TV and Android.
* This is required on iOS and Android.
*/
onRequestClose?: ((event: NativeSyntheticEvent<any>) => void) | undefined;
/**
@@ -89,6 +89,12 @@ export interface ModalPropsIOS {
onOrientationChange?:
| ((event: NativeSyntheticEvent<any>) => void)
| undefined;
/**
* Controls whether the modal can be dismissed by swiping down on iOS.
* This requires you to implement the `onRequestClose` prop to handle the dismissal.
*/
allowSwipeDismissal?: boolean | undefined;
}
export interface ModalPropsAndroid {
+19 -2
View File
@@ -86,9 +86,9 @@ export type ModalBaseProps = {
*/
visible?: ?boolean,
/**
* The `onRequestClose` callback is called when the user taps the hardware back button on Android or the menu button on Apple TV.
* The `onRequestClose` callback is called when the user taps the hardware back button on Android, dismisses the sheet using a gesture on iOS (when `allowSwipeDismissal` is set to true) or the menu button on Apple TV.
*
* This is required on Apple TV and Android.
* This is required on iOS and Android.
*/
// onRequestClose?: (event: NativeSyntheticEvent<any>) => void;
onRequestClose?: ?DirectEventHandler<null>,
@@ -147,6 +147,12 @@ export type ModalPropsIOS = {
// | ((event: NativeSyntheticEvent<any>) => void)
// | undefined;
onOrientationChange?: ?DirectEventHandler<OrientationChangeEvent>,
/**
* Controls whether the modal can be dismissed by swiping down on iOS.
* This requires you to implement the `onRequestClose` prop to handle the dismissal.
*/
allowSwipeDismissal?: ?boolean,
};
export type ModalPropsAndroid = {
@@ -192,6 +198,16 @@ function confirmProps(props: ModalProps) {
'Modal with translucent navigation bar and without translucent status bar is not supported.',
);
}
if (
Platform.OS === 'ios' &&
props.allowSwipeDismissal === true &&
!props.onRequestClose
) {
console.warn(
'Modal requires the onRequestClose prop when used with `allowSwipeDismissal`. This is necessary to prevent state corruption.',
);
}
}
}
@@ -327,6 +343,7 @@ class Modal extends React.Component<ModalProps, ModalState> {
onStartShouldSetResponder={this._shouldSetResponder}
supportedOrientations={this.props.supportedOrientations}
onOrientationChange={this.props.onOrientationChange}
allowSwipeDismissal={this.props.allowSwipeDismissal}
testID={this.props.testID}>
<VirtualizedListContextResetter>
<ScrollView.Context.Provider value={null}>
@@ -8,7 +8,7 @@
* @format
*/
import type {AttributeConfiguration} from '../../Renderer/shims/ReactNativeTypes';
import type {PartialAttributeConfiguration as AttributeConfiguration} from '../../Renderer/shims/ReactNativeTypes';
import flattenStyle from '../../StyleSheet/flattenStyle';
import deepDiffer from '../../Utilities/differ/deepDiffer';
@@ -61,7 +61,7 @@ function restoreDeletedValuesInNestedArray(
} else if (node && removedKeyCount > 0) {
const obj = node;
for (const propKey in removedKeys) {
// $FlowFixMe[incompatible-use] found when upgrading Flow
// $FlowFixMe[incompatible-use] removedKeys is always non-null
if (!removedKeys[propKey]) {
continue;
}
@@ -133,12 +133,12 @@ function diffNestedArrayProperty(
);
}
for (; i < nextArray.length; i++) {
// Add all remaining properties.
updatePayload = addNestedProperty(
updatePayload,
nextArray[i],
validAttributes,
);
// Add all remaining properties
const nextProp = nextArray[i];
if (!nextProp) {
continue;
}
updatePayload = addNestedProperty(updatePayload, nextProp, validAttributes);
}
return updatePayload;
}
@@ -183,9 +183,7 @@ function diffNestedProperty(
if (Array.isArray(prevProp)) {
return diffProperties(
updatePayload,
// $FlowFixMe - We know that this is always an object when the input is.
flattenStyle(prevProp),
// $FlowFixMe - We know that this isn't an array because of above flow.
nextProp,
validAttributes,
);
@@ -194,43 +192,11 @@ function diffNestedProperty(
return diffProperties(
updatePayload,
prevProp,
// $FlowFixMe - We know that this is always an object when the input is.
flattenStyle(nextProp),
validAttributes,
);
}
/**
* addNestedProperty takes a single set of props and valid attribute
* attribute configurations. It processes each prop and adds it to the
* updatePayload.
*/
function addNestedProperty(
updatePayload: null | Object,
nextProp: NestedNode,
validAttributes: AttributeConfiguration,
): $FlowFixMe {
if (!nextProp) {
return updatePayload;
}
if (!Array.isArray(nextProp)) {
// Add each property of the leaf.
return addProperties(updatePayload, nextProp, validAttributes);
}
for (let i = 0; i < nextProp.length; i++) {
// Add all the properties of the array.
updatePayload = addNestedProperty(
updatePayload,
nextProp[i],
validAttributes,
);
}
return updatePayload;
}
/**
* clearNestedProperty takes a single set of props and valid attributes. It
* adds a null sentinel to the updatePayload, for each prop key.
@@ -285,14 +251,19 @@ function diffProperties(
prevProp = prevProps[propKey];
nextProp = nextProps[propKey];
// functions are converted to booleans as markers that the associated
// events should be sent from native.
if (typeof nextProp === 'function') {
nextProp = (true: any);
// If nextProp is not a function, then don't bother changing prevProp
// since nextProp will win and go into the updatePayload regardless.
if (typeof prevProp === 'function') {
prevProp = (true: any);
const attributeConfigHasProcess =
typeof attributeConfig === 'object' &&
typeof attributeConfig.process === 'function';
if (!attributeConfigHasProcess) {
// functions are converted to booleans as markers that the associated
// events should be sent from native.
nextProp = (true: any);
// If nextProp is not a function, then don't bother changing prevProp
// since nextProp will win and go into the updatePayload regardless.
if (typeof prevProp === 'function') {
prevProp = (true: any);
}
}
}
@@ -442,16 +413,69 @@ function diffProperties(
return updatePayload;
}
/**
* addProperties adds all the valid props to the payload after being processed.
*/
function addProperties(
updatePayload: null | Object,
function addNestedProperty(
payload: null | Object,
props: Object,
validAttributes: AttributeConfiguration,
): null | Object {
// TODO: Fast path
return diffProperties(updatePayload, emptyObject, props, validAttributes);
// Flatten nested style props.
if (Array.isArray(props)) {
for (let i = 0; i < props.length; i++) {
payload = addNestedProperty(payload, props[i], validAttributes);
}
return payload;
}
for (const propKey in props) {
const prop = props[propKey];
const attributeConfig = ((validAttributes[
propKey
]: any): AttributeConfiguration);
if (attributeConfig == null) {
continue;
}
let newValue;
if (prop === undefined) {
// Discard the prop if it was previously defined.
if (payload && payload[propKey] !== undefined) {
newValue = null;
} else {
continue;
}
} else if (typeof attributeConfig === 'object') {
if (typeof attributeConfig.process === 'function') {
// An atomic prop with custom processing.
newValue = attributeConfig.process(prop);
} else if (typeof attributeConfig.diff === 'function') {
// An atomic prop with custom diffing. We don't need to do diffing when adding props.
newValue = prop;
}
} else {
if (typeof prop === 'function') {
// A function prop. It represents an event handler. Pass it to native as 'true'.
newValue = true;
} else {
// An atomic prop. Doesn't need to be flattened.
newValue = prop;
}
}
if (newValue !== undefined) {
if (!payload) {
payload = ({}: {[string]: $FlowFixMe});
}
payload[propKey] = newValue;
continue;
}
payload = addNestedProperty(payload, prop, attributeConfig);
}
return payload;
}
/**
@@ -463,7 +487,6 @@ function clearProperties(
prevProps: Object,
validAttributes: AttributeConfiguration,
): null | Object {
// TODO: Fast path
return diffProperties(updatePayload, prevProps, emptyObject, validAttributes);
}
@@ -471,11 +494,7 @@ export function create(
props: Object,
validAttributes: AttributeConfiguration,
): null | Object {
return addProperties(
null, // updatePayload
props,
validAttributes,
);
return addNestedProperty(null, props, validAttributes);
}
export function diff(
@@ -0,0 +1,495 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow
* @format
*/
'use strict';
import type {PartialAttributeConfiguration as AttributeConfiguration} from '../../../Renderer/shims/ReactNativeTypes';
const {create, diff} = require('../ReactNativeAttributePayload');
describe('ReactNativeAttributePayload.create', () => {
it('should work with simple example', () => {
expect(create({b: 2, c: 3}, {a: true, b: true})).toEqual({
b: 2,
});
});
it('should work with complex example', () => {
const validAttributes: AttributeConfiguration = {
style: {
position: true,
zIndex: true,
flexGrow: true,
flexShrink: true,
flexDirection: true,
overflow: true,
backgroundColor: true,
},
};
expect(
create(
{
style: [
{
flexGrow: 1,
flexShrink: 1,
flexDirection: 'row',
overflow: 'scroll',
},
[
{position: 'relative', zIndex: 2},
{flexGrow: 0},
{backgroundColor: 'red'},
],
],
},
validAttributes,
),
).toEqual({
flexGrow: 0,
flexShrink: 1,
flexDirection: 'row',
overflow: 'scroll',
position: 'relative',
zIndex: 2,
backgroundColor: 'red',
});
});
it('should nullify previously defined style prop that is subsequently set to null or undefined', () => {
expect(
create({style: [{a: 0}, {a: undefined}]}, {style: {a: true}}),
).toEqual({a: null});
expect(create({style: [{a: 0}, {a: null}]}, {style: {a: true}})).toEqual({
a: null,
});
});
it('should ignore non-style fields that are set to undefined', () => {
expect(create({}, {a: true})).toEqual(null);
expect(create({a: undefined}, {a: true})).toEqual(null);
expect(create({a: undefined, b: undefined}, {a: true, b: true})).toEqual(
null,
);
expect(
create({a: undefined, b: undefined, c: 1}, {a: true, b: true}),
).toEqual(null);
expect(
create({a: undefined, b: undefined, c: 1}, {a: true, b: true, c: true}),
).toEqual({c: 1});
expect(
create({a: 1, b: undefined, c: 2}, {a: true, b: true, c: true}),
).toEqual({a: 1, c: 2});
});
it('should ignore invalid fields', () => {
expect(create({b: 2}, {})).toEqual(null);
});
it('should not use the diff attribute', () => {
const diffA = jest.fn();
expect(create({a: [2]}, {a: {diff: diffA}})).toEqual({a: [2]});
expect(diffA).not.toBeCalled();
});
it('should use the process attribute', () => {
const processA = jest.fn(a => a + 1);
expect(create({a: 2}, {a: {process: processA}})).toEqual({a: 3});
expect(processA).toBeCalledWith(2);
});
it('should use the process attribute for functions as well', () => {
const process = (x: Object) => x;
const nextFunction = () => {};
expect(create({a: nextFunction}, {a: {process}})).toEqual({
a: nextFunction,
});
});
it('should work with undefined styles', () => {
expect(create({style: undefined}, {style: {b: true}})).toEqual(null);
expect(create({style: {a: '#ffffff', b: 1}}, {style: {b: true}})).toEqual({
b: 1,
});
});
it('should flatten nested styles and predefined styles', () => {
const validStyleAttribute: AttributeConfiguration = {
style: {foo: true, bar: true},
};
expect(create({style: [{foo: 1}, {bar: 2}]}, validStyleAttribute)).toEqual({
foo: 1,
bar: 2,
});
expect(create({}, validStyleAttribute)).toEqual(null);
const barStyle = {
bar: 3,
};
expect(
create({style: [[{foo: 1}, {foo: 2}], barStyle]}, validStyleAttribute),
).toEqual({foo: 2, bar: 3});
});
it('should not flatten nested props if attribute config is a primitive or only has diff/process', () => {
expect(create({a: {foo: 1, bar: 2}}, {a: true})).toEqual({
a: {foo: 1, bar: 2},
});
expect(create({a: [{foo: 1}, {bar: 2}]}, {a: true})).toEqual({
a: [{foo: 1}, {bar: 2}],
});
expect(create({a: {foo: 1, bar: 2}}, {a: {diff: a => a}})).toEqual({
a: {foo: 1, bar: 2},
});
expect(
create({a: [{foo: 1}, {bar: 2}]}, {a: {diff: a => a, process: a => a}}),
).toEqual({a: [{foo: 1}, {bar: 2}]});
});
it('handles attributes defined multiple times', () => {
const validAttributes: AttributeConfiguration = {
foo: true,
style: {foo: true},
};
expect(create({foo: 4, style: {foo: 2}}, validAttributes)).toEqual({
foo: 2,
});
expect(create({style: {foo: 2}}, validAttributes)).toEqual({
foo: 2,
});
expect(create({style: {foo: 2}, foo: 4}, validAttributes)).toEqual({
foo: 4,
});
expect(create({foo: 4, style: {foo: null}}, validAttributes)).toEqual({
foo: null, // this should ideally be null.
});
expect(
create({foo: 4, style: [{foo: null}, {foo: 5}]}, validAttributes),
).toEqual({
foo: 5,
});
});
// Function properties are just markers to native that events should be sent.
it('should convert functions to booleans', () => {
expect(
create(
{
a: function () {
return 9;
},
b: function () {
return 3;
},
},
{a: true, b: true},
),
).toEqual({a: true, b: true});
});
});
describe('ReactNativeAttributePayload.diff', () => {
it('should work with simple example', () => {
expect(diff({a: 1, c: 3}, {b: 2, c: 3}, {a: true, b: true})).toEqual({
a: null,
b: 2,
});
});
it('should skip fields that are equal', () => {
expect(
diff(
{a: 1, b: 'two', c: true, d: false, e: undefined, f: 0},
{a: 1, b: 'two', c: true, d: false, e: undefined, f: 0},
{a: true, b: true, c: true, d: true, e: true, f: true},
),
).toEqual(null);
});
it('should remove fields', () => {
expect(diff({a: 1}, {}, {a: true})).toEqual({a: null});
});
it('should remove fields that are set to undefined', () => {
expect(diff({a: 1}, {a: undefined}, {a: true})).toEqual({a: null});
});
it('should ignore invalid fields', () => {
expect(diff({a: 1}, {b: 2}, {})).toEqual(null);
});
it('should use the diff attribute', () => {
const diffA = jest.fn((a, b) => true);
const diffB = jest.fn((a, b) => false);
expect(
diff(
{a: [1], b: [3]},
{a: [2], b: [4]},
{a: {diff: diffA}, b: {diff: diffB}},
),
).toEqual({a: [2]});
expect(diffA).toBeCalledWith([1], [2]);
expect(diffB).toBeCalledWith([3], [4]);
});
it('should not use the diff attribute on addition/removal', () => {
const diffA = jest.fn();
const diffB = jest.fn();
expect(
diff({a: [1]}, {b: [2]}, {a: {diff: diffA}, b: {diff: diffB}}),
).toEqual({a: null, b: [2]});
expect(diffA).not.toBeCalled();
expect(diffB).not.toBeCalled();
});
it('should do deep diffs of Objects by default', () => {
expect(
diff(
{a: [1], b: {k: [3, 4]}, c: {k: [4, 4]}},
{a: [2], b: {k: [3, 4]}, c: {k: [4, 5]}},
{a: true, b: true, c: true},
),
).toEqual({a: [2], c: {k: [4, 5]}});
});
it('should work with undefined styles', () => {
expect(
diff(
{style: {a: '#ffffff', b: 1}},
{style: undefined},
{style: {b: true}},
),
).toEqual({b: null});
expect(
diff(
{style: undefined},
{style: {a: '#ffffff', b: 1}},
{style: {b: true}},
),
).toEqual({b: 1});
expect(
diff({style: undefined}, {style: undefined}, {style: {b: true}}),
).toEqual(null);
});
it('should work with empty styles', () => {
const validAttributes: AttributeConfiguration = {a: true, b: true};
expect(diff({a: 1, c: 3}, {}, validAttributes)).toEqual({a: null});
expect(diff({}, {a: 1, c: 3}, validAttributes)).toEqual({a: 1});
expect(diff({}, {}, validAttributes)).toEqual(null);
});
it('should flatten nested styles and predefined styles', () => {
const validStyleAttribute: AttributeConfiguration = {
style: {foo: true, bar: true},
};
expect(
diff({}, {style: [{foo: 1}, {bar: 2}]}, validStyleAttribute),
).toEqual({foo: 1, bar: 2});
expect(
diff({style: [{foo: 1}, {bar: 2}]}, {}, validStyleAttribute),
).toEqual({foo: null, bar: null});
const barStyle = {
bar: 3,
};
expect(
diff({}, {style: [[{foo: 1}, {foo: 2}], barStyle]}, validStyleAttribute),
).toEqual({foo: 2, bar: 3});
});
it('should reset a value to a previous if it is removed', () => {
const validStyleAttribute: AttributeConfiguration = {
style: {foo: true, bar: true},
};
expect(
diff(
{style: [{foo: 1}, {foo: 3}]},
{style: [{foo: 1}, {bar: 2}]},
validStyleAttribute,
),
).toEqual({foo: 1, bar: 2});
});
it('should not clear removed props if they are still in another slot', () => {
const validStyleAttribute: AttributeConfiguration = {
style: {foo: true, bar: true},
};
expect(
diff(
{style: [{}, {foo: 3, bar: 2}]},
{style: [{foo: 3}, {bar: 2}]},
validStyleAttribute,
),
).toEqual({foo: 3}); // this should ideally be null. heuristic tradeoff.
expect(
diff(
{style: [{}, {foo: 3, bar: 2}]},
{style: [{foo: 1, bar: 1}, {bar: 2}]},
validStyleAttribute,
),
).toEqual({bar: 2, foo: 1});
});
it('should clear a prop if a later style is explicit null/undefined', () => {
const validStyleAttribute: AttributeConfiguration = {
style: {foo: true, bar: true},
};
expect(
diff(
{style: [{}, {foo: 3, bar: 2}]},
{style: [{foo: 1}, {bar: 2, foo: null}]},
validStyleAttribute,
),
).toEqual({foo: null});
expect(
diff(
{style: [{foo: 3}, {foo: null, bar: 2}]},
{style: [{foo: null}, {bar: 2}]},
validStyleAttribute,
),
).toEqual({foo: null});
expect(
diff(
{style: [{foo: 1}, {foo: null}]},
{style: [{foo: 2}, {foo: null}]},
validStyleAttribute,
),
).toEqual({foo: null}); // this should ideally be null. heuristic.
// Test the same case with object equality because an early bailout doesn't
// work in this case.
const fooObj = {foo: 3};
expect(
diff(
{style: [{foo: 1}, fooObj]},
{style: [{foo: 2}, fooObj]},
validStyleAttribute,
),
).toEqual({foo: 3}); // this should ideally be null. heuristic.
expect(
diff(
{style: [{foo: 1}, {foo: 3}]},
{style: [{foo: 2}, {foo: undefined}]},
validStyleAttribute,
),
).toEqual({foo: null}); // this should ideally be null. heuristic.
});
it('handles attributes defined multiple times', () => {
const validAttributes: AttributeConfiguration = {
foo: true,
style: {foo: true},
};
expect(diff({}, {foo: 4, style: {foo: 2}}, validAttributes)).toEqual({
foo: 2,
});
expect(diff({foo: 4}, {style: {foo: 2}}, validAttributes)).toEqual({
foo: 2,
});
expect(diff({style: {foo: 2}}, {foo: 4}, validAttributes)).toEqual({
foo: 4,
});
});
// Function properties are just markers to native that events should be sent.
it('should convert functions to booleans', () => {
// Note that if the property changes from one function to another, we don't
// need to send an update.
expect(
diff(
{
a: function () {
return 1;
},
b: function () {
return 2;
},
c: 3,
},
{
b: function () {
return 9;
},
c: function () {
return 3;
},
},
{a: true, b: true, c: true},
),
).toEqual({a: null, c: true});
});
it('should skip changed functions', () => {
expect(
diff(
{
a: function () {
return 1;
},
},
{
a: function () {
return 9;
},
},
{a: true},
),
).toEqual(null);
});
it('should skip deeply-nested changed functions', () => {
expect(
diff(
{
wrapper: {
a: function () {
return 1;
},
},
},
{
wrapper: {
a: function () {
return 9;
},
},
},
{wrapper: true},
),
).toEqual(null);
});
it('should use the process function config when prop is a function', () => {
const process = jest.fn(a => a);
const nextFunction = function () {};
expect(
diff(
{
a: function () {},
},
{
a: nextFunction,
},
{a: {process}},
),
).toEqual({a: nextFunction});
expect(process).toBeCalled();
});
});
@@ -3515,7 +3515,7 @@ declare export default typeof ReactNativeViewAttributes;
`;
exports[`public API should not change unintentionally Libraries/Components/View/View.js 1`] = `
"declare export default component(
"declare export default component View(
ref?: React.RefSetter<React.ElementRef<typeof ViewNativeComponent>>,
...props: ViewProps
);
@@ -3685,7 +3685,6 @@ export type AccessibilityProps = $ReadOnly<{
accessibilityLabel?: ?Stringish,
accessibilityHint?: ?Stringish,
\\"aria-label\\"?: ?Stringish,
experimental_accessibilityOrder?: ?Array<string>,
accessibilityRole?: ?AccessibilityRole,
role?: ?Role,
accessibilityState?: ?AccessibilityState,
@@ -3833,6 +3832,7 @@ type ViewBaseProps = $ReadOnly<{
hitSlop?: ?EdgeInsetsOrSizeProp,
pointerEvents?: ?(\\"auto\\" | \\"box-none\\" | \\"box-only\\" | \\"none\\"),
removeClippedSubviews?: ?boolean,
experimental_accessibilityOrder?: ?Array<string>,
}>;
export type ViewProps = $ReadOnly<{
...DirectEventProps,
@@ -5689,6 +5689,7 @@ export type ModalPropsIOS = {
>,
onDismiss?: ?() => void,
onOrientationChange?: ?DirectEventHandler<OrientationChangeEvent>,
allowSwipeDismissal?: ?boolean,
};
export type ModalPropsAndroid = {
hardwareAccelerated?: ?boolean,
@@ -9348,6 +9349,11 @@ export type {
PublicRootInstance,
PublicTextInstance,
} from \\"./Libraries/ReactPrivate/ReactNativePrivateInterface\\";
export {
default as unstable_VirtualView,
VirtualViewMode,
} from \\"./src/private/components/virtualview/VirtualView\\";
export type { ModeChangeEvent } from \\"./src/private/components/virtualview/VirtualView\\";
"
`;
+8 -2
View File
@@ -336,7 +336,10 @@ let reactRuntime = RNTarget(
name: .reactRuntime,
path: "ReactCommon/react/runtime",
excludedPaths: ["tests", "iostests", "platform"],
dependencies: [.reactNativeDependencies, .jsi, .reactJsiExecutor, .reactCxxReact, .reactJsErrorHandler, .reactPerformanceTimeline, .reactUtils, .reactFeatureFlags, .reactJsInspector, .reactJsiTooling, .reactHermes, .reactRuntimeScheduler, .hermesPrebuilt]
dependencies: [.reactNativeDependencies, .jsi, .reactJsiExecutor, .reactCxxReact, .reactJsErrorHandler, .reactPerformanceTimeline, .reactUtils, .reactFeatureFlags, .reactJsInspector, .reactJsiTooling, .reactHermes, .reactRuntimeScheduler, .hermesPrebuilt],
defines: [
CXXSetting.define("HERMES_ENABLE_DEBUGGER", to: "1", .when(configuration: BuildConfiguration.debug))
]
)
/// React-runtimeApple.podspec
@@ -394,6 +397,7 @@ let reactFabric = RNTarget(
"components/textinput",
"components/textinput/platform/ios/",
"components/unimplementedview",
"components/virtualview",
"components/root/tests",
],
dependencies: [.reactNativeDependencies, .reactJsiExecutor, .rctTypesafety, .reactTurboModuleCore, .jsi, .logger, .reactDebug, .reactFeatureFlags, .reactUtils, .reactRuntimeScheduler, .reactCxxReact, .reactRendererDebug, .reactGraphics, .yoga],
@@ -412,6 +416,8 @@ let reactFabricComponents = RNTarget(
name: .reactFabricComponents,
path: "ReactCommon/react/renderer",
excludedPaths: [
"components/modal/platform/android",
"components/modal/platform/cxx",
"components/view/platform/android",
"components/view/platform/windows",
"components/view/platform/macos",
@@ -427,7 +433,7 @@ let reactFabricComponents = RNTarget(
"conponents/rncore", // this was the old folder where RN Core Components were generated. If you ran codegen in the past, you might have some files in it that might make the build fail.
],
dependencies: [.reactNativeDependencies, .reactCore, .reactJsiExecutor, .reactTurboModuleCore, .jsi, .logger, .reactDebug, .reactFeatureFlags, .reactUtils, .reactRuntimeScheduler, .reactCxxReact, .yoga, .reactRendererDebug, .reactGraphics, .reactFabric, .reactTurboModuleBridging],
sources: ["components/inputaccessory", "components/modal", "components/safeareaview", "components/text", "components/text/platform/cxx", "components/textinput", "components/textinput/platform/ios/", "components/unimplementedview", "textlayoutmanager", "textlayoutmanager/platform/ios"]
sources: ["components/inputaccessory", "components/modal", "components/safeareaview", "components/text", "components/text/platform/cxx", "components/textinput", "components/textinput/platform/ios/", "components/unimplementedview", "components/virtualview", "textlayoutmanager", "textlayoutmanager/platform/ios"]
)
/// React-FabricImage.podspec
@@ -1,31 +0,0 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#pragma once
#ifndef RCT_FIT_RM_OLD_RUNTIME
#include <jsireact/JSIExecutor.h>
namespace facebook::react {
class JSCExecutorFactory : public JSExecutorFactory {
public:
explicit JSCExecutorFactory(JSIExecutor::RuntimeInstaller runtimeInstaller)
: runtimeInstaller_(std::move(runtimeInstaller)) {}
std::unique_ptr<JSExecutor> createJSExecutor(
std::shared_ptr<ExecutorDelegate> delegate,
std::shared_ptr<MessageQueueThread> jsQueue) override;
private:
JSIExecutor::RuntimeInstaller runtimeInstaller_;
};
} // namespace facebook::react
#endif // RCT_FIT_RM_OLD_RUNTIME
@@ -1,28 +0,0 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#include "JSCExecutorFactory.h"
#ifndef RCT_FIT_RM_OLD_RUNTIME
#import <jsc/JSCRuntime.h>
#import <memory>
namespace facebook::react {
std::unique_ptr<JSExecutor> JSCExecutorFactory::createJSExecutor(
std::shared_ptr<ExecutorDelegate> delegate,
std::shared_ptr<MessageQueueThread> __unused jsQueue)
{
return std::make_unique<JSIExecutor>(
facebook::jsc::makeJSCRuntime(), delegate, JSIExecutor::defaultTimeoutInvoker, runtimeInstaller_);
}
} // namespace facebook::react
#endif // RCT_FIT_RM_OLD_RUNTIME
@@ -22,8 +22,6 @@
}
_touchHandler = [RCTSurfaceTouchHandler new];
self.modalInPresentation = YES;
return self;
}
@@ -124,6 +124,7 @@ static ModalHostViewEventEmitter::OnOrientationChange onOrientationChangeStruct(
_viewController = [RCTFabricModalHostViewController new];
_viewController.modalTransitionStyle = UIModalTransitionStyleCoverVertical;
_viewController.delegate = self;
_viewController.modalInPresentation = YES;
}
return _viewController;
}
@@ -239,6 +240,7 @@ static ModalHostViewEventEmitter::OnOrientationChange onOrientationChangeStruct(
- (void)updateProps:(const Props::Shared &)props oldProps:(const Props::Shared &)oldProps
{
const auto &oldViewProps = static_cast<const ModalHostViewProps &>(*_props);
const auto &newProps = static_cast<const ModalHostViewProps &>(*props);
#if !TARGET_OS_TV
@@ -251,6 +253,10 @@ static ModalHostViewEventEmitter::OnOrientationChange onOrientationChangeStruct(
self.viewController.modalPresentationStyle = presentationConfiguration(newProps);
if (oldViewProps.allowSwipeDismissal != newProps.allowSwipeDismissal) {
self.viewController.modalInPresentation = !newProps.allowSwipeDismissal;
}
_shouldPresent = newProps.visible;
[self ensurePresentedOnlyIfNeeded];
@@ -283,6 +289,16 @@ static ModalHostViewEventEmitter::OnOrientationChange onOrientationChangeStruct(
}
}
- (void)presentationControllerDidDismiss:(UIPresentationController *)presentationController
{
auto eventEmitter = [self modalEventEmitter];
const auto &props = static_cast<const ModalHostViewProps &>(*_props);
if (eventEmitter && props.allowSwipeDismissal) {
eventEmitter->onRequestClose({});
}
}
@end
#ifdef __cplusplus
@@ -1215,7 +1215,7 @@ static NSString *RCTRecursiveAccessibilityLabel(UIView *view)
result = [NSMutableString string];
}
if (result.length > 0) {
[result appendString:@" "];
[result appendString:@", "];
}
[result appendString:label];
}
@@ -1230,7 +1230,10 @@ static NSString *RCTRecursiveAccessibilityLabel(UIView *view)
return label;
}
return RCTRecursiveAccessibilityLabel(self.currentContainerView);
if (self.isAccessibilityElement) {
return RCTRecursiveAccessibilityLabel(self.currentContainerView);
}
return nil;
}
- (NSString *)accessibilityLabelForCoopting
@@ -0,0 +1,24 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#import <UIKit/UIKit.h>
#import <React/RCTViewComponentView.h>
NS_ASSUME_NONNULL_BEGIN
@interface RCTVirtualViewComponentView : RCTViewComponentView
+ (instancetype)new NS_UNAVAILABLE;
- (instancetype)init NS_UNAVAILABLE;
- (instancetype)initWithCoder:(NSCoder *)coder NS_UNAVAILABLE;
- (instancetype)initWithFrame:(CGRect)frame NS_DESIGNATED_INITIALIZER;
@end
NS_ASSUME_NONNULL_END
@@ -0,0 +1,295 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#import "RCTVirtualViewComponentView.h"
#import <React/RCTAssert.h>
#import <React/RCTConversions.h>
#import <React/RCTScrollViewComponentView.h>
#import <React/RCTScrollableProtocol.h>
#import <React/UIView+React.h>
#import <jsi/jsi.h>
#import <react/featureflags/ReactNativeFeatureFlags.h>
#import <react/renderer/components/FBReactNativeSpec/ComponentDescriptors.h>
#import <react/renderer/components/FBReactNativeSpec/EventEmitters.h>
#import <react/renderer/components/FBReactNativeSpec/Props.h>
#import <react/renderer/components/virtualview/VirtualViewComponentDescriptor.h>
#import <react/renderer/components/virtualview/VirtualViewShadowNode.h>
#import "RCTFabricComponentsPlugins.h"
using namespace facebook;
using namespace facebook::react;
typedef NS_ENUM(NSInteger, RCTVirtualViewMode) {
RCTVirtualViewModeVisible = 0,
RCTVirtualViewModePrerender = 1,
RCTVirtualViewModeHidden = 2,
};
/**
* Checks whether one CGRect overlaps with another CGRect.
*
* This is different from CGRectIntersectsRect because a CGRect representing
* a line or a point is considered to overlap with another CGRect if the line
* or point is within the rect bounds. However, two CGRects are not considered
* to overlap if they only share a boundary.
*/
static BOOL CGRectOverlaps(CGRect rect1, CGRect rect2)
{
CGFloat minY1 = CGRectGetMinY(rect1);
CGFloat maxY1 = CGRectGetMaxY(rect1);
CGFloat minY2 = CGRectGetMinY(rect2);
CGFloat maxY2 = CGRectGetMaxY(rect2);
if (minY1 >= maxY2 || minY2 >= maxY1) {
// No overlap on the y-axis.
return NO;
}
CGFloat minX1 = CGRectGetMinX(rect1);
CGFloat maxX1 = CGRectGetMaxX(rect1);
CGFloat minX2 = CGRectGetMinX(rect2);
CGFloat maxX2 = CGRectGetMaxX(rect2);
if (minX1 >= maxX2 || minX2 >= maxX1) {
// No overlap on the x-axis.
return NO;
}
return YES;
}
@interface RCTVirtualViewComponentView () <UIScrollViewDelegate>
@end
@implementation RCTVirtualViewComponentView {
RCTScrollViewComponentView *_lastParentScrollViewComponentView;
std::optional<enum RCTVirtualViewMode> _mode;
std::optional<CGRect> _targetRect;
}
- (instancetype)initWithFrame:(CGRect)frame
{
if (self = [super initWithFrame:frame]) {
_props = VirtualViewShadowNode::defaultSharedProps();
}
return self;
}
- (void)updateProps:(const Props::Shared &)props oldProps:(const Props::Shared &)oldProps
{
if (!_mode.has_value()) {
const auto &newViewProps = static_cast<const VirtualViewProps &>(*props);
_mode = newViewProps.initialHidden ? RCTVirtualViewModeHidden : RCTVirtualViewModeVisible;
}
[super updateProps:props oldProps:oldProps];
}
- (RCTScrollViewComponentView *)getParentScrollViewComponentView
{
UIView *view = self.superview;
while (view != nil) {
if ([view isKindOfClass:[RCTScrollViewComponentView class]]) {
return (RCTScrollViewComponentView *)view;
}
view = view.superview;
}
return nil;
}
- (void)prepareForRecycle
{
[super prepareForRecycle];
// No need to remove the scroll listener here since the view is always removed from window before being recycled and
// we do that in didMoveToWindow, which gets called when the view is removed from window.
RCTAssert(
_lastParentScrollViewComponentView == nil,
@"_lastParentScrollViewComponentView should already have been cleared in didMoveToWindow.");
_mode.reset();
_targetRect.reset();
}
// Handles case when sibling changes size.
// TODO(T202601695): This doesn't yet handle the case of elements in the ScrollView outside a VirtualColumn changing
// size.
- (void)updateLayoutMetrics:(const LayoutMetrics &)layoutMetrics
oldLayoutMetrics:(const LayoutMetrics &)oldLayoutMetrics
{
[super updateLayoutMetrics:layoutMetrics oldLayoutMetrics:_layoutMetrics];
[self dispatchOnModeChangeIfNeeded:YES];
}
- (void)didMoveToWindow
{
[super didMoveToWindow];
if (_lastParentScrollViewComponentView) {
[_lastParentScrollViewComponentView removeScrollListener:self];
_lastParentScrollViewComponentView = nil;
}
if (RCTScrollViewComponentView *parentScrollViewComponentView = [self getParentScrollViewComponentView]) {
if (self.window) {
// TODO(T202601695): We also want the ScrollView to emit layout changes from didLayoutSubviews so that any event
// that may affect visibily of this view notifies the listeners.
[parentScrollViewComponentView addScrollListener:self];
_lastParentScrollViewComponentView = parentScrollViewComponentView;
// We want to dispatch the event immediately when the view is added to the window before any scrolling occurs.
[self dispatchOnModeChangeIfNeeded:NO];
}
}
}
- (void)scrollViewDidScroll:(UIScrollView *)scrollView
{
[self dispatchOnModeChangeIfNeeded:NO];
}
- (void)dispatchOnModeChangeIfNeeded:(BOOL)checkForTargetRectChange
{
if (!_lastParentScrollViewComponentView) {
return;
}
UIScrollView *scrollView = _lastParentScrollViewComponentView.scrollView;
CGRect targetRect = [self convertRect:self.bounds toView:scrollView];
// While scrolling, the `targetRect` does not change, so we don't check for changed `targetRect` in that case.
if (checkForTargetRectChange) {
if (_targetRect.has_value() && CGRectEqualToRect(targetRect, _targetRect.value())) {
return;
}
_targetRect = targetRect;
}
enum RCTVirtualViewMode newMode;
CGRect thresholdRect = CGRectMake(
scrollView.contentOffset.x,
scrollView.contentOffset.y,
scrollView.frame.size.width,
scrollView.frame.size.height);
if (CGRectOverlaps(targetRect, thresholdRect)) {
newMode = RCTVirtualViewModeVisible;
} else {
auto prerender = false;
const CGFloat prerenderRatio = ReactNativeFeatureFlags::virtualViewPrerenderRatio();
if (prerenderRatio > 0) {
thresholdRect = CGRectInset(
thresholdRect, -thresholdRect.size.width * prerenderRatio, -thresholdRect.size.height * prerenderRatio);
prerender = CGRectOverlaps(targetRect, thresholdRect);
}
if (prerender) {
newMode = RCTVirtualViewModePrerender;
} else {
newMode = RCTVirtualViewModeHidden;
thresholdRect = CGRectZero;
}
}
if (_mode.has_value() && newMode == _mode.value()) {
return;
}
// NOTE: Make sure to keep these props in sync with dispatchSyncModeChange below where we have to explicitly copy all
// props.
VirtualViewEventEmitter::OnModeChange event = {
.mode = (int)newMode,
.targetRect =
{.x = targetRect.origin.x,
.y = targetRect.origin.y,
.width = targetRect.size.width,
.height = targetRect.size.height},
.thresholdRect =
{.x = thresholdRect.origin.x,
.y = thresholdRect.origin.y,
.width = thresholdRect.size.width,
.height = thresholdRect.size.height},
};
const std::optional<enum RCTVirtualViewMode> oldMode = _mode;
_mode = newMode;
switch (newMode) {
case RCTVirtualViewModeVisible:
[self dispatchSyncModeChange:event];
break;
case RCTVirtualViewModePrerender:
if (!oldMode.has_value() || oldMode != RCTVirtualViewModeVisible) {
[self dispatchAsyncModeChange:event];
}
break;
case RCTVirtualViewModeHidden:
[self dispatchAsyncModeChange:event];
break;
}
}
- (void)dispatchAsyncModeChange:(VirtualViewEventEmitter::OnModeChange &)event
{
if (!_eventEmitter) {
return;
}
std::shared_ptr<const VirtualViewEventEmitter> emitter =
std::static_pointer_cast<const VirtualViewEventEmitter>(_eventEmitter);
emitter->onModeChange(event);
}
- (void)dispatchSyncModeChange:(VirtualViewEventEmitter::OnModeChange &)event
{
if (!_eventEmitter) {
return;
}
std::shared_ptr<const VirtualViewEventEmitter> emitter =
std::static_pointer_cast<const VirtualViewEventEmitter>(_eventEmitter);
// TODO: Move this into a custom event emitter. We had to duplicate the codegen code here from onModeChange in order
// to dispatch synchronously and discrete.
emitter->experimental_flushSync([&emitter, &event]() {
emitter->dispatchEvent(
"modeChange",
[event](jsi::Runtime &runtime) {
auto payload = jsi::Object(runtime);
payload.setProperty(runtime, "mode", event.mode);
{
auto targetRect = jsi::Object(runtime);
targetRect.setProperty(runtime, "x", event.targetRect.x);
targetRect.setProperty(runtime, "y", event.targetRect.y);
targetRect.setProperty(runtime, "width", event.targetRect.width);
targetRect.setProperty(runtime, "height", event.targetRect.height);
payload.setProperty(runtime, "targetRect", targetRect);
}
{
auto thresholdRect = jsi::Object(runtime);
thresholdRect.setProperty(runtime, "x", event.thresholdRect.x);
thresholdRect.setProperty(runtime, "y", event.thresholdRect.y);
thresholdRect.setProperty(runtime, "width", event.thresholdRect.width);
thresholdRect.setProperty(runtime, "height", event.thresholdRect.height);
payload.setProperty(runtime, "thresholdRect", thresholdRect);
}
return payload;
},
RawEvent::Category::Discrete);
});
}
+ (ComponentDescriptorProvider)componentDescriptorProvider
{
return concreteComponentDescriptorProvider<VirtualViewComponentDescriptor>();
}
@end
Class<RCTComponentViewProtocol> VirtualViewCls(void)
{
return RCTVirtualViewComponentView.class;
}
@@ -16,6 +16,12 @@ NS_ASSUME_NONNULL_BEGIN
+ (std::vector<facebook::react::ProcessedColorStop>)getFixedColorStops:
(const std::vector<facebook::react::ColorStop> &)colorStops
gradientLineLength:(CGFloat)gradientLineLength;
// CAGradientLayer linear gradient squishes the non-square gradient to square gradient.
// This function fixes the "squished" effect.
// See https://stackoverflow.com/a/43176174 for more information.
+ (std::pair<CGPoint, CGPoint>)pointsForCAGradientLayerLinearGradient:(CGPoint)startPoint
endPoint:(CGPoint)endPoint
bounds:(CGSize)bounds;
@end
NS_ASSUME_NONNULL_END
@@ -9,10 +9,140 @@
#import <React/RCTAnimationUtils.h>
#import <React/RCTConversions.h>
#import <react/utils/FloatComparison.h>
#include <optional>
#import <vector>
using namespace facebook::react;
namespace {
struct Line;
struct LineSegment {
CGPoint p1;
CGPoint p2;
LineSegment(CGPoint p1, CGPoint p2) : p1(p1), p2(p2) {}
LineSegment(CGPoint p1, CGFloat m, CGFloat distance);
CGFloat getLength() const
{
CGFloat dx = p2.x - p1.x;
CGFloat dy = p2.y - p1.y;
return sqrt(dx * dx + dy * dy);
}
CGFloat getDistance() const
{
return p1.x <= p2.x ? getLength() : -getLength();
}
CGPoint getMidpoint() const
{
return CGPointMake((p1.x + p2.x) / 2, (p1.y + p2.y) / 2);
}
CGFloat getSlope() const
{
CGFloat dx = p2.x - p1.x;
if (floatEquality(dx, 0.0)) {
return std::numeric_limits<CGFloat>::infinity();
}
return (p2.y - p1.y) / dx;
}
CGFloat getPerpendicularSlope() const
{
CGFloat slope = getSlope();
if (std::isinf(slope)) {
return 0.0;
}
if (floatEquality(slope, 0.0)) {
return -std::numeric_limits<CGFloat>::infinity();
}
return -1 / slope;
}
Line toLine() const;
LineSegment perpendicularBisector() const
{
CGPoint midpoint = getMidpoint();
CGFloat perpSlope = getPerpendicularSlope();
CGFloat dist = getDistance();
return {LineSegment(midpoint, perpSlope, -dist / 2).p2, LineSegment(midpoint, perpSlope, dist / 2).p2};
}
LineSegment multiplied(CGSize multipliers) const
{
return {
CGPointMake(p1.x * multipliers.width, p1.y * multipliers.height),
CGPointMake(p2.x * multipliers.width, p2.y * multipliers.height)};
}
LineSegment divided(CGSize divisors) const
{
return multiplied(CGSizeMake(1 / divisors.width, 1 / divisors.height));
}
};
struct Line {
CGFloat m;
CGFloat b;
Line(CGFloat m, CGFloat b) : m(m), b(b) {}
Line(CGFloat m, CGPoint p) : m(m), b(p.y - m * p.x) {}
Line(CGPoint p1, CGPoint p2) : m(LineSegment(p1, p2).getSlope()), b(p1.y - m * p1.x) {}
CGFloat y(CGFloat x) const
{
return m * x + b;
}
CGPoint point(CGFloat x) const
{
return CGPointMake(x, y(x));
}
std::optional<CGPoint> intersection(const Line &other) const
{
CGFloat n = other.m;
CGFloat c = other.b;
if (floatEquality(m, n)) {
return std::nullopt;
}
CGFloat x = (c - b) / (m - n);
return point(x);
}
};
LineSegment::LineSegment(CGPoint p1, CGFloat m, CGFloat distance) : p1(p1)
{
Line line(m, p1);
CGPoint measuringPoint = line.point(p1.x + 1);
LineSegment measuringSegment(p1, measuringPoint);
CGFloat measuringDeltaH = measuringSegment.getDistance();
CGFloat deltaX = !floatEquality(measuringDeltaH, 0.0) ? distance / measuringDeltaH : 0.0;
p2 = line.point(p1.x + deltaX);
}
Line LineSegment::toLine() const
{
return {p1, p2};
}
CGSize calculateMultipliers(CGSize bounds)
{
if (bounds.height <= bounds.width) {
return CGSizeMake(1, bounds.width / bounds.height);
} else {
return CGSizeMake(bounds.height / bounds.width, 1);
}
}
} // namespace
static std::optional<Float> resolveColorStopPosition(ValueUnit position, CGFloat gradientLineLength)
{
if (position.unit == UnitType::Point) {
@@ -52,21 +182,21 @@ static std::vector<ProcessedColorStop> processColorTransitionHints(const std::ve
auto leftDist = offset - offsetLeft;
auto rightDist = offsetRight - offset;
auto totalDist = offsetRight - offsetLeft;
SharedColor leftSharedColor = colorStops[x - 1].color;
SharedColor rightSharedColor = colorStops[x + 1].color;
const SharedColor &leftSharedColor = colorStops[x - 1].color;
const SharedColor &rightSharedColor = colorStops[x + 1].color;
if (facebook::react::floatEquality(leftDist, rightDist)) {
if (floatEquality(leftDist, rightDist)) {
colorStops.erase(colorStops.begin() + x);
--indexOffset;
continue;
}
if (facebook::react::floatEquality(leftDist, .0f)) {
if (floatEquality(leftDist, 0.0)) {
colorStops[x].color = rightSharedColor;
continue;
}
if (facebook::react::floatEquality(rightDist, .0f)) {
if (floatEquality(rightDist, 0.0)) {
colorStops[x].color = leftSharedColor;
continue;
}
@@ -120,7 +250,7 @@ static std::vector<ProcessedColorStop> processColorTransitionHints(const std::ve
auto green = (interpolatedColor >> 8) & 0xFF;
auto blue = interpolatedColor & 0xFF;
newStop.color = facebook::react::colorFromRGBA(red, green, blue, alpha);
newStop.color = colorFromRGBA(red, green, blue, alpha);
}
// Replace the color hint with new color stops
@@ -201,4 +331,47 @@ static std::vector<ProcessedColorStop> processColorTransitionHints(const std::ve
}
return processColorTransitionHints(fixedColorStops);
}
// CAGradientLayer linear gradient squishes the non-square gradient to square gradient.
// This function fixes the "squished" effect.
// See https://stackoverflow.com/a/43176174 for more information.
+ (std::pair<CGPoint, CGPoint>)pointsForCAGradientLayerLinearGradient:(CGPoint)startPoint
endPoint:(CGPoint)endPoint
bounds:(CGSize)bounds
{
if (floatEquality(startPoint.x, endPoint.x) || floatEquality(startPoint.y, endPoint.y)) {
// Apple's implementation of horizontal and vertical gradients works just fine
return {startPoint, endPoint};
}
LineSegment startEnd(startPoint, endPoint);
LineSegment ab = startEnd.multiplied({bounds.width, bounds.height});
const CGPoint a = ab.p1;
const CGPoint b = ab.p2;
LineSegment cd = ab.perpendicularBisector();
CGSize multipliers = calculateMultipliers(bounds);
LineSegment lineSegmentCD = cd.multiplied(multipliers);
LineSegment lineSegmentEF = lineSegmentCD.perpendicularBisector();
LineSegment ef = lineSegmentEF.divided(multipliers);
Line efLine = ef.toLine();
Line aParallelLine(cd.getSlope(), a);
Line bParallelLine(cd.getSlope(), b);
std::optional<CGPoint> g_opt = efLine.intersection(aParallelLine);
std::optional<CGPoint> h_opt = efLine.intersection(bParallelLine);
if (g_opt && h_opt) {
LineSegment gh(*g_opt, *h_opt);
LineSegment result = gh.divided({bounds.width, bounds.height});
return {result.p1, result.p2};
}
return {startPoint, endPoint};
}
@end
@@ -19,54 +19,52 @@ using namespace facebook::react;
+ (CALayer *)gradientLayerWithSize:(CGSize)size gradient:(const LinearGradient &)gradient
{
UIGraphicsImageRenderer *renderer = [[UIGraphicsImageRenderer alloc] initWithSize:size];
const auto &direction = gradient.direction;
UIImage *gradientImage = [renderer imageWithActions:^(UIGraphicsImageRendererContext *_Nonnull rendererContext) {
CGPoint startPoint;
CGPoint endPoint;
CAGradientLayer *gradientLayer = [CAGradientLayer layer];
CGPoint startPoint;
CGPoint endPoint;
if (direction.type == GradientDirectionType::Angle) {
CGFloat angle = std::get<Float>(direction.value);
std::tie(startPoint, endPoint) = getPointsFromAngle(angle, size);
} else if (direction.type == GradientDirectionType::Keyword) {
auto keyword = std::get<GradientKeyword>(direction.value);
CGFloat angle = getAngleForKeyword(keyword, size);
std::tie(startPoint, endPoint) = getPointsFromAngle(angle, size);
} else {
// Default to top-to-bottom gradient
startPoint = CGPointMake(0.0, 0.0);
endPoint = CGPointMake(0.0, size.height);
}
if (direction.type == GradientDirectionType::Angle) {
CGFloat angle = std::get<Float>(direction.value);
std::tie(startPoint, endPoint) = getPointsFromAngle(angle, size);
} else if (direction.type == GradientDirectionType::Keyword) {
auto keyword = std::get<GradientKeyword>(direction.value);
CGFloat angle = getAngleForKeyword(keyword, size);
std::tie(startPoint, endPoint) = getPointsFromAngle(angle, size);
} else {
// Default to top-to-bottom gradient
CGFloat centerX = size.width / 2;
startPoint = CGPointMake(centerX, 0.0);
endPoint = CGPointMake(centerX, size.height);
}
CGFloat dx = endPoint.x - startPoint.x;
CGFloat dy = endPoint.y - startPoint.y;
CGFloat gradientLineLength = sqrt(dx * dx + dy * dy);
const auto colorStops = [RCTGradientUtils getFixedColorStops:gradient.colorStops
gradientLineLength:gradientLineLength];
CGFloat dx = endPoint.x - startPoint.x;
CGFloat dy = endPoint.y - startPoint.y;
CGFloat gradientLineLength = sqrt(dx * dx + dy * dy);
const auto colorStops = [RCTGradientUtils getFixedColorStops:gradient.colorStops
gradientLineLength:gradientLineLength];
NSMutableArray<id> *colors = [NSMutableArray array];
NSMutableArray<NSNumber *> *locations = [NSMutableArray array];
CGPoint relativeStartPoint = CGPointMake(startPoint.x / size.width, startPoint.y / size.height);
CGPoint relativeEndPoint = CGPointMake(endPoint.x / size.width, endPoint.y / size.height);
CGContextRef context = rendererContext.CGContext;
NSMutableArray *colors = [NSMutableArray array];
CGFloat locations[colorStops.size()];
CGPoint fixedStartPoint;
CGPoint fixedEndPoint;
for (size_t i = 0; i < colorStops.size(); ++i) {
const auto &colorStop = colorStops[i];
CGColorRef cgColor = RCTCreateCGColorRefFromSharedColor(colorStop.color);
[colors addObject:(__bridge id)cgColor];
locations[i] = std::max(std::min(colorStop.position.value(), 1.0), 0.0);
}
std::tie(fixedStartPoint, fixedEndPoint) = [RCTGradientUtils pointsForCAGradientLayerLinearGradient:relativeStartPoint
endPoint:relativeEndPoint
bounds:size];
CGGradientRef cgGradient = CGGradientCreateWithColors(NULL, (__bridge CFArrayRef)colors, locations);
gradientLayer.startPoint = fixedStartPoint;
gradientLayer.endPoint = fixedEndPoint;
CGContextDrawLinearGradient(context, cgGradient, startPoint, endPoint, 0);
for (const auto &colorStop : colorStops) {
[colors addObject:(id)RCTUIColorFromSharedColor(colorStop.color).CGColor];
[locations addObject:@(std::max(std::min(colorStop.position.value(), 1.0), 0.0))];
}
for (id color in colors) {
CGColorRelease((__bridge CGColorRef)color);
}
CGGradientRelease(cgGradient);
}];
CALayer *gradientLayer = [CALayer layer];
gradientLayer.contents = (__bridge id)gradientImage.CGImage;
gradientLayer.colors = colors;
gradientLayer.locations = locations;
return gradientLayer;
}
@@ -1,20 +0,0 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#import "RCTJscInstanceFactory.h"
#import <ReactCommon/RCTJscInstance.h>
using namespace facebook::react;
extern "C" {
JSRuntimeFactoryRef jsrt_create_jsc_factory(void)
{
return reinterpret_cast<JSRuntimeFactoryRef>(new RCTJscInstance());
}
} // extern "C"
@@ -24,6 +24,7 @@
@property (nonatomic, copy) RCTDirectEventBlock onShow;
@property (nonatomic, assign) BOOL visible;
@property (nonatomic, assign) BOOL allowSwipeDismissal;
// Android only
@property (nonatomic, assign) BOOL statusBarTranslucent;
@@ -35,6 +35,7 @@ RCT_NOT_IMPLEMENTED(-(instancetype)initWithCoder : coder)
if ((self = [super initWithFrame:CGRectZero])) {
_bridge = bridge;
_modalViewController = [RCTModalHostViewController new];
_modalViewController.modalInPresentation = YES;
UIView *containerView = [UIView new];
containerView.autoresizingMask = UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleWidth;
_modalViewController.view = containerView;
@@ -50,6 +51,14 @@ RCT_NOT_IMPLEMENTED(-(instancetype)initWithCoder : coder)
return self;
}
- (void)setAllowSwipeDismissal:(BOOL)allowSwipeDismissal
{
if (_allowSwipeDismissal != allowSwipeDismissal) {
_allowSwipeDismissal = allowSwipeDismissal;
_modalViewController.modalInPresentation = !allowSwipeDismissal;
}
}
- (void)notifyForBoundsChange:(CGRect)newBounds
{
if (_reactSubview && _isPresented) {
@@ -70,6 +79,13 @@ RCT_NOT_IMPLEMENTED(-(instancetype)initWithCoder : coder)
}
}
- (void)presentationControllerDidDismiss:(UIPresentationController *)presentationController
{
if (_onRequestClose != nil && _allowSwipeDismissal) {
_onRequestClose(nil);
}
}
- (void)notifyForOrientationChange
{
if (!_onOrientationChange) {
@@ -22,8 +22,6 @@
return nil;
}
self.modalInPresentation = YES;
_preferredStatusBarStyle = [RCTUIStatusBarManager() statusBarStyle];
_preferredStatusBarHidden = [RCTUIStatusBarManager() isStatusBarHidden];
@@ -119,6 +119,7 @@ RCT_EXPORT_VIEW_PROPERTY(supportedOrientations, NSArray)
RCT_EXPORT_VIEW_PROPERTY(onOrientationChange, RCTDirectEventBlock)
RCT_EXPORT_VIEW_PROPERTY(visible, BOOL)
RCT_EXPORT_VIEW_PROPERTY(onRequestClose, RCTDirectEventBlock)
RCT_EXPORT_VIEW_PROPERTY(allowSwipeDismissal, BOOL)
// Fabric only
RCT_EXPORT_VIEW_PROPERTY(onDismiss, RCTDirectEventBlock)
+4 -1
View File
@@ -240,7 +240,10 @@ RCT_NOT_IMPLEMENTED(-(instancetype)initWithCoder : unused)
if (label) {
return label;
}
return RCTRecursiveAccessibilityLabel(self);
if (self.isAccessibilityElement) {
return RCTRecursiveAccessibilityLabel(self);
}
return nil;
}
- (NSArray<UIAccessibilityCustomAction *> *)accessibilityCustomActions
@@ -148,29 +148,29 @@ public class com/facebook/react/ReactDelegate {
public fun <init> (Landroid/app/Activity;Lcom/facebook/react/ReactNativeHost;Ljava/lang/String;Landroid/os/Bundle;)V
public fun <init> (Landroid/app/Activity;Lcom/facebook/react/ReactNativeHost;Ljava/lang/String;Landroid/os/Bundle;Z)V
protected fun createRootView ()Lcom/facebook/react/ReactRootView;
public fun getCurrentReactContext ()Lcom/facebook/react/bridge/ReactContext;
public fun getReactHost ()Lcom/facebook/react/ReactHost;
public fun getReactInstanceManager ()Lcom/facebook/react/ReactInstanceManager;
public fun getReactRootView ()Lcom/facebook/react/ReactRootView;
protected fun isFabricEnabled ()Z
public fun loadApp ()V
public fun loadApp (Ljava/lang/String;)V
public fun onActivityResult (IILandroid/content/Intent;Z)V
public fun onBackPressed ()Z
public fun onConfigurationChanged (Landroid/content/res/Configuration;)V
public fun onHostDestroy ()V
public fun onHostPause ()V
public fun onHostResume ()V
public fun onKeyDown (ILandroid/view/KeyEvent;)Z
public fun onKeyLongPress (I)Z
public fun onNewIntent (Landroid/content/Intent;)Z
public fun onUserLeaveHint ()V
public fun onWindowFocusChanged (Z)V
public fun reload ()V
public fun setReactRootView (Lcom/facebook/react/ReactRootView;)V
public fun setReactSurface (Lcom/facebook/react/interfaces/fabric/ReactSurface;)V
public fun shouldShowDevMenuOrReload (ILandroid/view/KeyEvent;)Z
public fun unloadApp ()V
public final fun getCurrentReactContext ()Lcom/facebook/react/bridge/ReactContext;
public final fun getReactHost ()Lcom/facebook/react/ReactHost;
public final fun getReactInstanceManager ()Lcom/facebook/react/ReactInstanceManager;
public final fun getReactRootView ()Lcom/facebook/react/ReactRootView;
protected final fun isFabricEnabled ()Z
public final fun loadApp ()V
public final fun loadApp (Ljava/lang/String;)V
public final fun onActivityResult (IILandroid/content/Intent;Z)V
public final fun onBackPressed ()Z
public final fun onConfigurationChanged (Landroid/content/res/Configuration;)V
public final fun onHostDestroy ()V
public final fun onHostPause ()V
public final fun onHostResume ()V
public final fun onKeyDown (ILandroid/view/KeyEvent;)Z
public final fun onKeyLongPress (I)Z
public final fun onNewIntent (Landroid/content/Intent;)Z
public final fun onUserLeaveHint ()V
public final fun onWindowFocusChanged (Z)V
public final fun reload ()V
public final fun setReactRootView (Lcom/facebook/react/ReactRootView;)V
public final fun setReactSurface (Lcom/facebook/react/interfaces/fabric/ReactSurface;)V
public final fun shouldShowDevMenuOrReload (ILandroid/view/KeyEvent;)Z
public final fun unloadApp ()V
}
public class com/facebook/react/ReactFragment : androidx/fragment/app/Fragment, com/facebook/react/modules/core/PermissionAwareActivity {
@@ -259,6 +259,7 @@ public class com/facebook/react/ReactInstanceManager {
public fun createViewManager (Ljava/lang/String;)Lcom/facebook/react/uimanager/ViewManager;
public fun destroy ()V
public fun detachRootView (Lcom/facebook/react/uimanager/ReactRoot;)V
public fun getCurrentReactContext ()Lcom/facebook/react/bridge/ReactContext;
public fun getDevSupportManager ()Lcom/facebook/react/devsupport/interfaces/DevSupportManager;
public fun getJsExecutorName ()Ljava/lang/String;
public fun getLifecycleState ()Lcom/facebook/react/common/LifecycleState;
@@ -600,12 +601,14 @@ public abstract interface class com/facebook/react/bridge/CatalystInstance : com
public abstract fun getSourceURL ()Ljava/lang/String;
public abstract fun hasNativeModule (Ljava/lang/Class;)Z
public abstract fun hasRunJSBundle ()Z
public abstract fun initialize ()V
public abstract fun invokeCallback (ILcom/facebook/react/bridge/NativeArrayInterface;)V
public abstract fun isDestroyed ()Z
public abstract fun registerSegment (ILjava/lang/String;)V
public abstract fun removeBridgeIdleDebugListener (Lcom/facebook/react/bridge/NotThreadSafeBridgeIdleDebugListener;)V
public abstract fun runJSBundle ()V
public abstract fun setFabricUIManager (Lcom/facebook/react/bridge/UIManager;)V
public abstract fun setGlobalVariable (Ljava/lang/String;Ljava/lang/String;)V
public abstract fun setTurboModuleRegistry (Lcom/facebook/react/internal/turbomodule/core/interfaces/TurboModuleRegistry;)V
}
@@ -2372,24 +2375,6 @@ public final class com/facebook/react/fabric/FabricUIManagerProviderImpl : com/f
public fun createUIManager (Lcom/facebook/react/bridge/ReactApplicationContext;)Lcom/facebook/react/bridge/UIManager;
}
public class com/facebook/react/fabric/mounting/MountItemDispatcher {
public fun <init> (Lcom/facebook/react/fabric/mounting/MountingManager;Lcom/facebook/react/fabric/mounting/MountItemDispatcher$ItemDispatchListener;)V
public fun addMountItem (Lcom/facebook/react/fabric/mounting/mountitems/MountItem;)V
public fun addPreAllocateMountItem (Lcom/facebook/react/fabric/mounting/mountitems/MountItem;)V
public fun addViewCommandMountItem (Lcom/facebook/react/fabric/mounting/mountitems/DispatchCommandMountItem;)V
public fun dispatchMountItems (Ljava/util/Queue;)V
public fun dispatchPreMountItems (J)V
public fun getBatchedExecutionTime ()J
public fun getRunStartTime ()J
public fun tryDispatchMountItems ()V
}
public abstract interface class com/facebook/react/fabric/mounting/MountItemDispatcher$ItemDispatchListener {
public abstract fun didDispatchMountItems ()V
public abstract fun didMountItems (Ljava/util/List;)V
public abstract fun willMountItems (Ljava/util/List;)V
}
public class com/facebook/react/fabric/mounting/SurfaceMountingManager {
public static final field TAG Ljava/lang/String;
public fun <init> (ILcom/facebook/react/touch/JSResponderHandler;Lcom/facebook/react/uimanager/ViewManagerRegistry;Lcom/facebook/react/uimanager/RootViewManager;Lcom/facebook/react/fabric/mounting/MountingManager$MountItemExecutor;Lcom/facebook/react/uimanager/ThemedReactContext;)V
@@ -2448,19 +2433,6 @@ public abstract interface class com/facebook/react/interfaces/fabric/ReactSurfac
public abstract fun stop ()Lcom/facebook/react/interfaces/TaskInterface;
}
public final class com/facebook/react/jscexecutor/JSCExecutor : com/facebook/react/bridge/JavaScriptExecutor {
public fun getName ()Ljava/lang/String;
public static final fun loadLibrary ()V
}
public final class com/facebook/react/jscexecutor/JSCExecutorFactory : com/facebook/react/bridge/JavaScriptExecutorFactory {
public fun <init> (Ljava/lang/String;Ljava/lang/String;)V
public fun create ()Lcom/facebook/react/bridge/JavaScriptExecutor;
public fun startSamplingProfiler ()V
public fun stopSamplingProfiler (Ljava/lang/String;)V
public fun toString ()Ljava/lang/String;
}
public final class com/facebook/react/jstasks/HeadlessJsTaskConfig {
public fun <init> (Lcom/facebook/react/jstasks/HeadlessJsTaskConfig;)V
public fun <init> (Ljava/lang/String;Lcom/facebook/react/bridge/WritableMap;)V
@@ -3135,10 +3107,6 @@ public class com/facebook/react/runtime/CoreReactPackage$$ReactModuleInfoProvide
public fun getReactModuleInfos ()Ljava/util/Map;
}
public final class com/facebook/react/runtime/JSCInstance : com/facebook/react/runtime/JSRuntimeFactory {
public fun <init> ()V
}
public abstract class com/facebook/react/runtime/JSRuntimeFactory {
public fun <init> (Lcom/facebook/jni/HybridData;)V
}
@@ -3253,10 +3221,6 @@ public final class com/facebook/react/soloader/OpenSourceMergedSoMapping : com/f
public final fun libhermes_executor_so ()I
public final fun libhermesinstancejni_so ()I
public final fun libhermestooling_so ()I
public final fun libjscexecutor_so ()I
public final fun libjscinstance_so ()I
public final fun libjscruntime_so ()I
public final fun libjsctooling_so ()I
public final fun libjsijniprofiler_so ()I
public final fun libjsinspector_so ()I
public final fun libmapbufferjni_so ()I
@@ -3336,6 +3300,7 @@ public abstract class com/facebook/react/uimanager/BaseViewManager : com/faceboo
public fun getExportedCustomBubblingEventTypeConstants ()Ljava/util/Map;
public fun getExportedCustomDirectEventTypeConstants ()Ljava/util/Map;
protected fun onAfterUpdateTransaction (Landroid/view/View;)V
public fun onDropViewInstance (Landroid/view/View;)V
public fun onLayoutChange (Landroid/view/View;IIIIIIII)V
protected fun prepareToRecycleView (Lcom/facebook/react/uimanager/ThemedReactContext;Landroid/view/View;)Landroid/view/View;
public fun setAccessibilityActions (Landroid/view/View;Lcom/facebook/react/bridge/ReadableArray;)V
@@ -4925,32 +4890,6 @@ public final class com/facebook/react/uimanager/events/NativeGestureUtil {
public static final fun notifyNativeGestureStarted (Landroid/view/View;Landroid/view/MotionEvent;)V
}
public class com/facebook/react/uimanager/events/PointerEvent : com/facebook/react/uimanager/events/Event {
public fun dispatch (Lcom/facebook/react/uimanager/events/RCTEventEmitter;)V
public fun dispatchModern (Lcom/facebook/react/uimanager/events/RCTModernEventEmitter;)V
public fun getCoalescingKey ()S
public fun getEventAnimationDriverMatchSpec ()Lcom/facebook/react/uimanager/events/Event$EventAnimationDriverMatchSpec;
public fun getEventName ()Ljava/lang/String;
public static fun obtain (Ljava/lang/String;ILcom/facebook/react/uimanager/events/PointerEvent$PointerEventState;Landroid/view/MotionEvent;)Lcom/facebook/react/uimanager/events/PointerEvent;
public static fun obtain (Ljava/lang/String;ILcom/facebook/react/uimanager/events/PointerEvent$PointerEventState;Landroid/view/MotionEvent;S)Lcom/facebook/react/uimanager/events/PointerEvent;
public fun onDispose ()V
}
public class com/facebook/react/uimanager/events/PointerEvent$PointerEventState {
public fun <init> (IIIILjava/util/Map;Ljava/util/Map;Ljava/util/Map;Ljava/util/Map;Ljava/util/Set;)V
public fun getActivePointerId ()I
public final fun getEventCoordinatesByPointerId ()Ljava/util/Map;
public final fun getHitPathByPointerId ()Ljava/util/Map;
public final fun getHitPathForActivePointer ()Ljava/util/List;
public fun getHoveringPointerIds ()Ljava/util/Set;
public fun getLastButtonState ()I
public final fun getOffsetByPointerId ()Ljava/util/Map;
public fun getPrimaryPointerId ()I
public final fun getScreenCoordinatesByPointerId ()Ljava/util/Map;
public fun getSurfaceId ()I
public fun supportsHover (I)Z
}
public abstract interface class com/facebook/react/uimanager/events/RCTEventEmitter : com/facebook/react/bridge/JavaScriptModule {
public abstract fun receiveEvent (ILjava/lang/String;Lcom/facebook/react/bridge/WritableMap;)V
public abstract fun receiveTouches (Ljava/lang/String;Lcom/facebook/react/bridge/WritableArray;Lcom/facebook/react/bridge/WritableArray;)V
@@ -5327,6 +5266,7 @@ public class com/facebook/react/viewmanagers/ModalHostViewManagerDelegate : com/
}
public abstract interface class com/facebook/react/viewmanagers/ModalHostViewManagerInterface : com/facebook/react/uimanager/ViewManagerWithGeneratedInterface {
public abstract fun setAllowSwipeDismissal (Landroid/view/View;Z)V
public abstract fun setAnimated (Landroid/view/View;Z)V
public abstract fun setAnimationType (Landroid/view/View;Ljava/lang/String;)V
public abstract fun setHardwareAccelerated (Landroid/view/View;Z)V
@@ -5356,6 +5296,15 @@ public abstract interface class com/facebook/react/viewmanagers/UnimplementedNat
public abstract fun setName (Landroid/view/View;Ljava/lang/String;)V
}
public class com/facebook/react/viewmanagers/VirtualViewManagerDelegate : com/facebook/react/uimanager/BaseViewManagerDelegate {
public fun <init> (Lcom/facebook/react/uimanager/BaseViewManager;)V
public fun setProperty (Landroid/view/View;Ljava/lang/String;Ljava/lang/Object;)V
}
public abstract interface class com/facebook/react/viewmanagers/VirtualViewManagerInterface : com/facebook/react/uimanager/ViewManagerWithGeneratedInterface {
public abstract fun setInitialHidden (Landroid/view/View;Z)V
}
public final class com/facebook/react/views/drawer/ReactDrawerLayout : androidx/drawerlayout/widget/DrawerLayout {
public fun <init> (Lcom/facebook/react/bridge/ReactContext;)V
public fun onInterceptTouchEvent (Landroid/view/MotionEvent;)Z
@@ -6183,16 +6132,17 @@ public final class com/facebook/react/views/text/ReactFontManager$Companion {
public final fun getInstance ()Lcom/facebook/react/views/text/ReactFontManager;
}
public class com/facebook/react/views/text/ReactTextShadowNode : com/facebook/react/views/text/ReactBaseTextShadowNode {
public final class com/facebook/react/views/text/ReactTextShadowNode : com/facebook/react/views/text/ReactBaseTextShadowNode {
public fun <init> ()V
public fun <init> (Lcom/facebook/react/views/text/ReactTextViewManagerCallback;)V
public synthetic fun <init> (Lcom/facebook/react/views/text/ReactTextViewManagerCallback;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
public fun calculateLayoutOnChildren ()Ljava/lang/Iterable;
public fun hoistNativeChildren ()Z
public fun isVirtualAnchor ()Z
public fun markUpdated ()V
public fun onBeforeLayout (Lcom/facebook/react/uimanager/NativeViewHierarchyOptimizer;)V
public fun onCollectExtraUpdates (Lcom/facebook/react/uimanager/UIViewOperationQueue;)V
public fun setShouldNotifyOnTextLayout (Z)V
public final fun setShouldNotifyOnTextLayout (Z)V
}
public final class com/facebook/react/views/text/ReactTextUpdate {
@@ -87,8 +87,6 @@ val preparePrefab by
// hermes_executor
// This prefab targets is used by Expo & Reanimated
Pair("../ReactCommon/hermes/inspector-modern/", "hermes/inspector-modern/"),
// jscexecutor
Pair("../ReactCommon/jsc/", "jsc/"),
// fabricjni
Pair("src/main/jni/react/fabric", "react/fabric/"),
// glog
@@ -236,10 +234,6 @@ val preparePrefab by
"hermestooling",
// hermes_executor
Pair("../ReactCommon/hermes/inspector-modern/", "hermes/inspector-modern/")),
PrefabPreprocessingEntry(
"jsctooling",
// jsc
Pair("../ReactCommon/jsc/", "jsc/")),
))
outputDir.set(prefabHeadersDir)
}
@@ -534,7 +528,6 @@ android {
"reactnative",
"jsi",
"hermestooling",
"jsctooling",
)
}
}
@@ -583,10 +576,9 @@ android {
resources.excludes.add("META-INF/LICENSE")
// We intentionally don't want to bundle any JS Runtime inside the Android AAR
// we produce. The reason behind this is that we want to allow users to pick the
// JS engine by specifying a dependency on either `hermes-engine` or `android-jsc`
// JS engine by specifying a dependency on either `hermes-engine` or other engines
// that will include the necessary .so files to load.
jniLibs.excludes.add("**/libhermes.so")
jniLibs.excludes.add("**/libjsc.so")
}
buildFeatures {
@@ -599,7 +591,6 @@ android {
create("jsi") { headers = File(prefabHeadersDir, "jsi").absolutePath }
create("reactnative") { headers = File(prefabHeadersDir, "reactnative").absolutePath }
create("hermestooling") { headers = File(prefabHeadersDir, "hermestooling").absolutePath }
create("jsctooling") { headers = File(prefabHeadersDir, "jsctooling").absolutePath }
}
publishing {
@@ -644,10 +635,9 @@ dependencies {
compileOnly(libs.javax.annotation.api)
api(libs.javax.inject)
// It's up to the consumer to decide if hermes/jsc should be included or not.
// Therefore hermes-engine and jsc are compileOnly dependencies.
// It's up to the consumer to decide if hermes or other engines should be included or not.
// Therefore hermes-engine is a compileOnly dependencies.
compileOnly(project(":packages:react-native:ReactAndroid:hermes-engine"))
compileOnly(libs.jsc.android)
testImplementation(libs.junit)
testImplementation(libs.assertj)
@@ -186,6 +186,24 @@ val prepareHeadersForPrefab by
into(prefabHeadersDir)
}
val buildHermesLib by
tasks.registering(CustomExecTask::class) {
dependsOn(buildHermesC)
workingDir(hermesDir)
inputs.files(hermesBuildOutputFileTree)
commandLine(
cmakeBinaryPath,
"--build",
hermesBuildDir.toString(),
"--target",
"libhermes",
"-j",
ndkBuildJobs,
)
standardOutputFile.set(project.file("$buildDir/build-hermes-lib.log"))
errorOutputFile.set(project.file("$buildDir/build-hermes-lib.error.log"))
}
fun windowsAwareCommandLine(vararg commands: String): List<String> {
val result =
if (Os.isFamily(Os.FAMILY_WINDOWS)) {
@@ -33,13 +33,4 @@ internal object ReactDebugOverlayTags {
"UI Manager",
"UI Manager View Operations (requires restart\nwarning: this is spammy)",
Color.CYAN)
@JvmField
val FABRIC_UI_MANAGER: DebugOverlayTag =
DebugOverlayTag("FabricUIManager", "Fabric UI Manager View Operations", Color.CYAN)
@JvmField
val FABRIC_RECONCILER: DebugOverlayTag =
DebugOverlayTag("FabricReconciler", "Reconciler for Fabric", Color.CYAN)
@JvmField
val RELAY: DebugOverlayTag =
DebugOverlayTag("Relay", "including prefetching", Color.rgb(0xFF, 0x99, 0x00))
}
@@ -1,460 +0,0 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
package com.facebook.react;
import android.app.Activity;
import android.content.Intent;
import android.content.res.Configuration;
import android.os.Bundle;
import android.view.KeyEvent;
import androidx.annotation.Nullable;
import com.facebook.infer.annotation.Assertions;
import com.facebook.infer.annotation.Nullsafe;
import com.facebook.react.bridge.ReactContext;
import com.facebook.react.bridge.UiThreadUtil;
import com.facebook.react.common.annotations.DeprecatedInNewArchitecture;
import com.facebook.react.devsupport.DoubleTapReloadRecognizer;
import com.facebook.react.devsupport.ReleaseDevSupportManager;
import com.facebook.react.devsupport.interfaces.DevSupportManager;
import com.facebook.react.interfaces.fabric.ReactSurface;
import com.facebook.react.internal.featureflags.ReactNativeNewArchitectureFeatureFlags;
import com.facebook.react.modules.core.DefaultHardwareBackBtnHandler;
import java.util.Objects;
/**
* A delegate for handling React Application support. This delegate is unaware whether it is used in
* an {@link Activity} or a {@link android.app.Fragment}.
*/
@Nullsafe(Nullsafe.Mode.LOCAL)
public class ReactDelegate {
private final Activity mActivity;
@Nullable private ReactRootView mReactRootView;
@Nullable private final String mMainComponentName;
@Nullable private Bundle mLaunchOptions;
@Nullable private DoubleTapReloadRecognizer mDoubleTapReloadRecognizer;
@Nullable private ReactNativeHost mReactNativeHost;
@Nullable private ReactHost mReactHost;
@Nullable private ReactSurface mReactSurface;
private boolean mFabricEnabled = ReactNativeNewArchitectureFeatureFlags.enableFabricRenderer();
/**
* Do not use this constructor as it's not accounting for New Architecture at all. You should use
* {@link ReactDelegate#ReactDelegate(Activity, ReactNativeHost, String, Bundle, boolean)} as it's
* the constructor used for New Architecture.
*
* @deprecated Use one of the other constructors instead to account for New Architecture.
*/
@Deprecated(since = "0.75.0")
public ReactDelegate(
Activity activity,
@Nullable ReactNativeHost reactNativeHost,
@Nullable String appKey,
@Nullable Bundle launchOptions) {
mActivity = activity;
mMainComponentName = appKey;
mLaunchOptions = launchOptions;
mDoubleTapReloadRecognizer = new DoubleTapReloadRecognizer();
mReactNativeHost = reactNativeHost;
}
public ReactDelegate(
Activity activity,
@Nullable ReactHost reactHost,
@Nullable String appKey,
@Nullable Bundle launchOptions) {
mActivity = activity;
mMainComponentName = appKey;
mLaunchOptions = launchOptions;
mDoubleTapReloadRecognizer = new DoubleTapReloadRecognizer();
mReactHost = reactHost;
}
@Deprecated(since = "0.81.0")
public ReactDelegate(
Activity activity,
@Nullable ReactNativeHost reactNativeHost,
@Nullable String appKey,
@Nullable Bundle launchOptions,
boolean fabricEnabled) {
mFabricEnabled = fabricEnabled;
mActivity = activity;
mMainComponentName = appKey;
mLaunchOptions = launchOptions;
mDoubleTapReloadRecognizer = new DoubleTapReloadRecognizer();
mReactNativeHost = reactNativeHost;
}
@Nullable
private DevSupportManager getDevSupportManager() {
if (ReactNativeNewArchitectureFeatureFlags.enableBridgelessArchitecture()
&& mReactHost != null
&& mReactHost.getDevSupportManager() != null) {
return mReactHost.getDevSupportManager();
} else if (getReactNativeHost() != null
&& getReactNativeHost().hasInstance()
&& getReactNativeHost().getReactInstanceManager() != null) {
return getReactNativeHost().getReactInstanceManager().getDevSupportManager();
} else {
return null;
}
}
public void onHostResume() {
if (!(mActivity instanceof DefaultHardwareBackBtnHandler)) {
throw new ClassCastException(
"Host Activity does not implement DefaultHardwareBackBtnHandler");
}
if (ReactNativeNewArchitectureFeatureFlags.enableBridgelessArchitecture()
&& mReactHost != null) {
mReactHost.onHostResume(mActivity, (DefaultHardwareBackBtnHandler) mActivity);
} else {
ReactNativeHost reactNativeHost = getReactNativeHost();
if (reactNativeHost != null && reactNativeHost.hasInstance()) {
reactNativeHost
.getReactInstanceManager()
.onHostResume(mActivity, (DefaultHardwareBackBtnHandler) mActivity);
}
}
}
public void onUserLeaveHint() {
if (ReactNativeNewArchitectureFeatureFlags.enableBridgelessArchitecture()
&& mReactHost != null) {
mReactHost.onHostLeaveHint(mActivity);
} else {
ReactNativeHost reactNativeHost = getReactNativeHost();
if (reactNativeHost != null && reactNativeHost.hasInstance()) {
reactNativeHost.getReactInstanceManager().onUserLeaveHint(mActivity);
}
}
}
public void onHostPause() {
if (ReactNativeNewArchitectureFeatureFlags.enableBridgelessArchitecture()
&& mReactHost != null) {
mReactHost.onHostPause(mActivity);
} else {
ReactNativeHost reactNativeHost = getReactNativeHost();
if (reactNativeHost != null && reactNativeHost.hasInstance()) {
reactNativeHost.getReactInstanceManager().onHostPause(mActivity);
}
}
}
public void onHostDestroy() {
unloadApp();
if (ReactNativeNewArchitectureFeatureFlags.enableBridgelessArchitecture()
&& mReactHost != null) {
mReactHost.onHostDestroy(mActivity);
} else {
ReactNativeHost reactNativeHost = getReactNativeHost();
if (reactNativeHost != null && reactNativeHost.hasInstance()) {
reactNativeHost.getReactInstanceManager().onHostDestroy(mActivity);
}
}
}
public boolean onBackPressed() {
if (ReactNativeNewArchitectureFeatureFlags.enableBridgelessArchitecture()
&& mReactHost != null) {
mReactHost.onBackPressed();
return true;
} else {
ReactNativeHost reactNativeHost = getReactNativeHost();
if (reactNativeHost != null && reactNativeHost.hasInstance()) {
reactNativeHost.getReactInstanceManager().onBackPressed();
return true;
}
}
return false;
}
public boolean onNewIntent(Intent intent) {
if (ReactNativeNewArchitectureFeatureFlags.enableBridgelessArchitecture()
&& mReactHost != null) {
mReactHost.onNewIntent(intent);
return true;
} else {
ReactNativeHost reactNativeHost = getReactNativeHost();
if (reactNativeHost != null && reactNativeHost.hasInstance()) {
reactNativeHost.getReactInstanceManager().onNewIntent(intent);
return true;
}
}
return false;
}
public void onActivityResult(
int requestCode,
int resultCode,
@Nullable Intent data,
boolean shouldForwardToReactInstance) {
if (ReactNativeNewArchitectureFeatureFlags.enableBridgelessArchitecture()
&& mReactHost != null) {
mReactHost.onActivityResult(mActivity, requestCode, resultCode, data);
} else {
ReactNativeHost reactNativeHost = getReactNativeHost();
if (reactNativeHost != null
&& reactNativeHost.hasInstance()
&& shouldForwardToReactInstance) {
reactNativeHost
.getReactInstanceManager()
.onActivityResult(mActivity, requestCode, resultCode, data);
}
}
}
public void onWindowFocusChanged(boolean hasFocus) {
if (ReactNativeNewArchitectureFeatureFlags.enableBridgelessArchitecture()
&& mReactHost != null) {
mReactHost.onWindowFocusChange(hasFocus);
} else {
if (getReactNativeHost() != null && getReactNativeHost().hasInstance()) {
getReactNativeHost().getReactInstanceManager().onWindowFocusChange(hasFocus);
}
}
}
public void onConfigurationChanged(Configuration newConfig) {
if (ReactNativeNewArchitectureFeatureFlags.enableBridgelessArchitecture()
&& mReactHost != null) {
mReactHost.onConfigurationChanged(Assertions.assertNotNull(mActivity));
} else {
if (getReactNativeHost() != null && getReactNativeHost().hasInstance()) {
getReactInstanceManager()
.onConfigurationChanged(Assertions.assertNotNull(mActivity), newConfig);
}
}
}
public boolean onKeyDown(int keyCode, KeyEvent event) {
if (keyCode == KeyEvent.KEYCODE_MEDIA_FAST_FORWARD
&& ((ReactNativeNewArchitectureFeatureFlags.enableBridgelessArchitecture()
&& mReactHost != null
&& mReactHost.getDevSupportManager() != null)
|| (getReactNativeHost() != null
&& getReactNativeHost().hasInstance()
&& getReactNativeHost().getUseDeveloperSupport()))) {
event.startTracking();
return true;
}
return false;
}
public boolean onKeyLongPress(int keyCode) {
if (keyCode == KeyEvent.KEYCODE_MEDIA_FAST_FORWARD) {
if (ReactNativeNewArchitectureFeatureFlags.enableBridgelessArchitecture()
&& mReactHost != null) {
DevSupportManager devSupportManager = mReactHost.getDevSupportManager();
// onKeyLongPress is a Dev API and not supported in RELEASE mode.
if (devSupportManager != null && !(devSupportManager instanceof ReleaseDevSupportManager)) {
devSupportManager.showDevOptionsDialog();
return true;
}
} else {
ReactNativeHost reactNativeHost = getReactNativeHost();
if (reactNativeHost != null
&& reactNativeHost.hasInstance()
&& reactNativeHost.getUseDeveloperSupport()) {
reactNativeHost.getReactInstanceManager().showDevOptionsDialog();
return true;
}
}
}
return false;
}
public void reload() {
DevSupportManager devSupportManager = getDevSupportManager();
if (devSupportManager == null) {
return;
}
// Reload in RELEASE mode
if (devSupportManager instanceof ReleaseDevSupportManager) {
// Do not reload the bundle from JS as there is no bundler running in release mode.
if (ReactNativeNewArchitectureFeatureFlags.enableBridgelessArchitecture()) {
if (mReactHost != null) {
mReactHost.reload("ReactDelegate.reload()");
}
} else {
UiThreadUtil.runOnUiThread(
() -> {
if (mReactNativeHost != null
&& mReactNativeHost.hasInstance()
&& mReactNativeHost.getReactInstanceManager() != null) {
mReactNativeHost.getReactInstanceManager().recreateReactContextInBackground();
}
});
}
return;
}
// Reload in DEBUG mode
devSupportManager.handleReloadJS();
}
/** Start the React surface with the app key supplied in the {@link ReactDelegate} constructor. */
public void loadApp() {
if (mMainComponentName == null) {
throw new IllegalStateException("Cannot loadApp without a main component name.");
} else {
loadApp(mMainComponentName);
}
}
/**
* Start the React surface for the given app key.
*
* @param appKey The ID of the app to load into the surface.
*/
public void loadApp(String appKey) {
// With Bridgeless enabled, create and start the surface
if (ReactNativeNewArchitectureFeatureFlags.enableBridgelessArchitecture()) {
if (mReactSurface == null && mReactHost != null) {
mReactSurface = mReactHost.createSurface(mActivity, appKey, mLaunchOptions);
}
Objects.requireNonNull(mReactSurface).start();
} else {
if (mReactRootView != null) {
throw new IllegalStateException("Cannot loadApp while app is already running.");
}
mReactRootView = createRootView();
if (getReactNativeHost() != null) {
mReactRootView.startReactApplication(
getReactNativeHost().getReactInstanceManager(), appKey, mLaunchOptions);
}
}
}
/** Stop the React surface started with {@link ReactDelegate#loadApp()}. */
public void unloadApp() {
if (ReactNativeNewArchitectureFeatureFlags.enableBridgelessArchitecture()) {
if (mReactSurface != null) {
mReactSurface.stop();
mReactSurface = null;
}
} else {
if (mReactRootView != null) {
mReactRootView.unmountReactApplication();
mReactRootView = null;
}
}
}
public void setReactSurface(ReactSurface reactSurface) {
mReactSurface = reactSurface;
}
public void setReactRootView(ReactRootView reactRootView) {
mReactRootView = reactRootView;
}
@Nullable
public ReactRootView getReactRootView() {
if (ReactNativeNewArchitectureFeatureFlags.enableBridgelessArchitecture()) {
if (mReactSurface != null) {
return (ReactRootView) mReactSurface.getView();
} else {
return null;
}
} else {
return mReactRootView;
}
}
// Not used in bridgeless
protected ReactRootView createRootView() {
ReactRootView reactRootView = new ReactRootView(mActivity);
reactRootView.setIsFabric(isFabricEnabled());
return reactRootView;
}
/**
* Handles delegating the {@link Activity#onKeyUp(int, KeyEvent)} method to determine whether the
* application should show the developer menu or should reload the React Application.
*
* @return true if we consume the event and either shoed the develop menu or reloaded the
* application.
*/
public boolean shouldShowDevMenuOrReload(int keyCode, KeyEvent event) {
DevSupportManager devSupportManager = getDevSupportManager();
// shouldShowDevMenuOrReload is a Dev API and not supported in RELEASE mode.
if (devSupportManager == null || devSupportManager instanceof ReleaseDevSupportManager) {
return false;
}
if (keyCode == KeyEvent.KEYCODE_MENU) {
devSupportManager.showDevOptionsDialog();
return true;
}
boolean didDoubleTapR =
Objects.requireNonNull(mDoubleTapReloadRecognizer)
.didDoubleTapR(keyCode, mActivity.getCurrentFocus());
if (didDoubleTapR) {
devSupportManager.handleReloadJS();
return true;
}
return false;
}
/** Get the {@link ReactNativeHost} used by this app. */
@DeprecatedInNewArchitecture(message = "Use getReactHost()")
private @Nullable ReactNativeHost getReactNativeHost() {
return mReactNativeHost;
}
@DeprecatedInNewArchitecture(message = "Use getReactHost()")
public ReactInstanceManager getReactInstanceManager() {
if (getReactNativeHost() == null) {
throw new IllegalStateException("Cannot get ReactInstanceManager without a ReactNativeHost.");
}
return getReactNativeHost().getReactInstanceManager();
}
public @Nullable ReactHost getReactHost() {
return mReactHost;
}
/**
* Get the current {@link ReactContext} from ReactHost or ReactInstanceManager
*
* <p>Do not store a reference to this, if the React instance is reloaded or destroyed, this
* context will no longer be valid.
*/
public @Nullable ReactContext getCurrentReactContext() {
if (ReactNativeNewArchitectureFeatureFlags.enableBridgelessArchitecture()) {
if (mReactHost != null) {
return mReactHost.getCurrentReactContext();
} else {
return null;
}
} else {
return getReactInstanceManager().getCurrentReactContext();
}
}
/**
* Override this method if you wish to selectively toggle Fabric for a specific surface. This will
* also control if Concurrent Root (React 18) should be enabled or not.
*
* @return true if Fabric is enabled for this Activity, false otherwise.
*/
protected boolean isFabricEnabled() {
return mFabricEnabled;
}
}
@@ -0,0 +1,409 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
package com.facebook.react
import android.app.Activity
import android.content.Intent
import android.content.res.Configuration
import android.os.Bundle
import android.view.KeyEvent
import com.facebook.react.bridge.ReactContext
import com.facebook.react.bridge.UiThreadUtil.runOnUiThread
import com.facebook.react.common.annotations.DeprecatedInNewArchitecture
import com.facebook.react.devsupport.DoubleTapReloadRecognizer
import com.facebook.react.devsupport.ReleaseDevSupportManager
import com.facebook.react.devsupport.interfaces.DevSupportManager
import com.facebook.react.interfaces.fabric.ReactSurface
import com.facebook.react.internal.featureflags.ReactNativeNewArchitectureFeatureFlags
import com.facebook.react.modules.core.DefaultHardwareBackBtnHandler
/**
* A delegate for handling React Application support. This delegate is unaware whether it is used in
* an [Activity] or a [android.app.Fragment].
*/
public open class ReactDelegate {
private val activity: Activity
private var internalReactRootView: ReactRootView? = null
private val mainComponentName: String?
private var launchOptions: Bundle?
private var doubleTapReloadRecognizer: DoubleTapReloadRecognizer?
private var reactNativeHost: ReactNativeHost? = null
public var reactHost: ReactHost? = null
private set
private var reactSurface: ReactSurface? = null
/**
* Override this method if you wish to selectively toggle Fabric for a specific surface. This will
* also control if Concurrent Root (React 18) should be enabled or not.
*
* @return true if Fabric is enabled for this Activity, false otherwise.
*/
protected var isFabricEnabled: Boolean =
ReactNativeNewArchitectureFeatureFlags.enableFabricRenderer()
private set
/**
* Do not use this constructor as it's not accounting for New Architecture at all. You should use
* [ReactDelegate(Activity, ReactNativeHost, String, Bundle, boolean)] as it's the constructor
* used for New Architecture.
*/
@Deprecated(
"Use one of the other constructors instead to account for New Architecture. Deprecated since 0.75.0")
public constructor(
activity: Activity,
reactNativeHost: ReactNativeHost?,
appKey: String?,
launchOptions: Bundle?
) {
this.activity = activity
mainComponentName = appKey
this.launchOptions = launchOptions
doubleTapReloadRecognizer = DoubleTapReloadRecognizer()
this.reactNativeHost = reactNativeHost
}
public constructor(
activity: Activity,
reactHost: ReactHost?,
appKey: String?,
launchOptions: Bundle?
) {
this.activity = activity
mainComponentName = appKey
this.launchOptions = launchOptions
doubleTapReloadRecognizer = DoubleTapReloadRecognizer()
this.reactHost = reactHost
}
@Deprecated("Deprecated since 0.81.0, use one of the other constructors instead.")
public constructor(
activity: Activity,
reactNativeHost: ReactNativeHost?,
appKey: String?,
launchOptions: Bundle?,
fabricEnabled: Boolean
) {
isFabricEnabled = fabricEnabled
this.activity = activity
mainComponentName = appKey
this.launchOptions = launchOptions
doubleTapReloadRecognizer = DoubleTapReloadRecognizer()
this.reactNativeHost = reactNativeHost
}
private val devSupportManager: DevSupportManager?
get() =
if (ReactNativeNewArchitectureFeatureFlags.enableBridgelessArchitecture() &&
reactHost?.devSupportManager != null) {
reactHost?.devSupportManager
} else if (reactNativeHost?.hasInstance() == true &&
reactNativeHost?.reactInstanceManager != null) {
reactNativeHost?.reactInstanceManager?.devSupportManager
} else {
null
}
public fun onHostResume() {
if (activity !is DefaultHardwareBackBtnHandler) {
throw ClassCastException("Host Activity does not implement DefaultHardwareBackBtnHandler")
}
if (ReactNativeNewArchitectureFeatureFlags.enableBridgelessArchitecture() &&
reactHost != null) {
reactHost?.onHostResume(activity, activity as DefaultHardwareBackBtnHandler)
} else {
if (reactNativeHost?.hasInstance() == true) {
reactNativeHost
?.reactInstanceManager
?.onHostResume(activity, activity as DefaultHardwareBackBtnHandler)
}
}
}
public fun onUserLeaveHint() {
if (ReactNativeNewArchitectureFeatureFlags.enableBridgelessArchitecture() &&
reactHost != null) {
reactHost?.onHostLeaveHint(activity)
} else {
if (reactNativeHost?.hasInstance() == true) {
reactNativeHost?.reactInstanceManager?.onUserLeaveHint(activity)
}
}
}
public fun onHostPause() {
if (ReactNativeNewArchitectureFeatureFlags.enableBridgelessArchitecture() &&
reactHost != null) {
reactHost?.onHostPause(activity)
} else {
if (reactNativeHost?.hasInstance() == true) {
reactNativeHost?.reactInstanceManager?.onHostPause(activity)
}
}
}
public fun onHostDestroy() {
unloadApp()
if (ReactNativeNewArchitectureFeatureFlags.enableBridgelessArchitecture() &&
reactHost != null) {
reactHost?.onHostDestroy(activity)
} else {
if (reactNativeHost?.hasInstance() == true) {
reactNativeHost?.reactInstanceManager?.onHostDestroy(activity)
}
}
}
public fun onBackPressed(): Boolean {
if (ReactNativeNewArchitectureFeatureFlags.enableBridgelessArchitecture() &&
reactHost != null) {
reactHost?.onBackPressed()
return true
} else {
if (reactNativeHost?.hasInstance() == true) {
reactNativeHost?.reactInstanceManager?.onBackPressed()
return true
}
}
return false
}
public fun onNewIntent(intent: Intent): Boolean {
if (ReactNativeNewArchitectureFeatureFlags.enableBridgelessArchitecture() &&
reactHost != null) {
reactHost?.onNewIntent(intent)
return true
} else {
if (reactNativeHost?.hasInstance() == true) {
reactNativeHost?.reactInstanceManager?.onNewIntent(intent)
return true
}
}
return false
}
public fun onActivityResult(
requestCode: Int,
resultCode: Int,
data: Intent?,
shouldForwardToReactInstance: Boolean
) {
if (ReactNativeNewArchitectureFeatureFlags.enableBridgelessArchitecture() &&
reactHost != null &&
shouldForwardToReactInstance) {
reactHost?.onActivityResult(activity, requestCode, resultCode, data)
} else {
if (reactNativeHost?.hasInstance() == true && shouldForwardToReactInstance) {
reactNativeHost
?.reactInstanceManager
?.onActivityResult(activity, requestCode, resultCode, data)
}
}
}
public fun onWindowFocusChanged(hasFocus: Boolean) {
if (ReactNativeNewArchitectureFeatureFlags.enableBridgelessArchitecture() &&
reactHost != null) {
reactHost?.onWindowFocusChange(hasFocus)
} else {
if (reactNativeHost?.hasInstance() == true) {
reactNativeHost?.reactInstanceManager?.onWindowFocusChange(hasFocus)
}
}
}
public fun onConfigurationChanged(newConfig: Configuration?) {
if (ReactNativeNewArchitectureFeatureFlags.enableBridgelessArchitecture() &&
reactHost != null) {
reactHost?.onConfigurationChanged(checkNotNull(activity))
} else {
if (reactNativeHost?.hasInstance() == true) {
getReactInstanceManager().onConfigurationChanged(checkNotNull(activity), newConfig)
}
}
}
public fun onKeyDown(keyCode: Int, event: KeyEvent): Boolean {
if (keyCode == KeyEvent.KEYCODE_MEDIA_FAST_FORWARD &&
((ReactNativeNewArchitectureFeatureFlags.enableBridgelessArchitecture() &&
reactHost?.devSupportManager != null) ||
(reactNativeHost?.hasInstance() == true &&
reactNativeHost?.useDeveloperSupport == true))) {
event.startTracking()
return true
}
return false
}
public fun onKeyLongPress(keyCode: Int): Boolean {
if (keyCode == KeyEvent.KEYCODE_MEDIA_FAST_FORWARD) {
if (ReactNativeNewArchitectureFeatureFlags.enableBridgelessArchitecture() &&
reactHost != null) {
val devSupportManager = reactHost?.devSupportManager
// onKeyLongPress is a Dev API and not supported in RELEASE mode.
if (devSupportManager != null && devSupportManager !is ReleaseDevSupportManager) {
devSupportManager.showDevOptionsDialog()
return true
}
} else {
if (reactNativeHost?.hasInstance() == true &&
reactNativeHost?.useDeveloperSupport == true) {
reactNativeHost?.reactInstanceManager?.showDevOptionsDialog()
return true
}
}
}
return false
}
public fun reload() {
val devSupportManager = devSupportManager ?: return
// Reload in RELEASE mode
if (devSupportManager is ReleaseDevSupportManager) {
// Do not reload the bundle from JS as there is no bundler running in release mode.
if (ReactNativeNewArchitectureFeatureFlags.enableBridgelessArchitecture()) {
reactHost?.reload("ReactDelegate.reload()")
} else {
runOnUiThread {
if (reactNativeHost?.hasInstance() == true &&
reactNativeHost?.reactInstanceManager != null) {
reactNativeHost?.reactInstanceManager?.recreateReactContextInBackground()
}
}
}
return
}
// Reload in DEBUG mode
devSupportManager.handleReloadJS()
}
/** Start the React surface with the app key supplied in the [ReactDelegate] constructor. */
public fun loadApp() {
val name = requireNotNull(mainComponentName) { "Cannot loadApp without a main component name." }
loadApp(name)
}
/**
* Start the React surface for the given app key.
*
* @param appKey The ID of the app to load into the surface.
*/
public fun loadApp(appKey: String) {
// With Bridgeless enabled, create and start the surface
if (ReactNativeNewArchitectureFeatureFlags.enableBridgelessArchitecture()) {
val reactHost = reactHost
if (reactSurface == null && reactHost != null) {
reactSurface = reactHost.createSurface(activity, appKey, launchOptions)
}
reactSurface?.start()
} else {
check(internalReactRootView == null) { "Cannot loadApp while app is already running." }
internalReactRootView = createRootView()
if (reactNativeHost != null) {
internalReactRootView?.startReactApplication(
reactNativeHost?.reactInstanceManager, appKey, launchOptions)
}
}
}
/** Stop the React surface started with [ReactDelegate.loadApp]. */
public fun unloadApp() {
if (ReactNativeNewArchitectureFeatureFlags.enableBridgelessArchitecture()) {
reactSurface?.stop()
reactSurface = null
} else {
if (internalReactRootView != null) {
internalReactRootView?.unmountReactApplication()
internalReactRootView = null
}
}
}
public fun setReactSurface(reactSurface: ReactSurface?) {
this.reactSurface = reactSurface
}
public var reactRootView: ReactRootView?
get() {
return if (ReactNativeNewArchitectureFeatureFlags.enableBridgelessArchitecture()) {
if (reactSurface != null) {
reactSurface?.view as ReactRootView?
} else {
null
}
} else {
internalReactRootView
}
}
set(reactRootView) {
internalReactRootView = reactRootView
}
// Not used in bridgeless
protected open fun createRootView(): ReactRootView? {
val reactRootView = ReactRootView(activity)
reactRootView.setIsFabric(isFabricEnabled)
return reactRootView
}
/**
* Handles delegating the [Activity.onKeyUp] method to determine whether the application should
* show the developer menu or should reload the React Application.
*
* @return true if we consume the event and either show the develop menu or reloaded the
* application.
*/
public fun shouldShowDevMenuOrReload(keyCode: Int, event: KeyEvent?): Boolean {
val devSupportManager = devSupportManager
// shouldShowDevMenuOrReload is a Dev API and not supported in RELEASE mode.
if (devSupportManager == null || devSupportManager is ReleaseDevSupportManager) {
return false
}
if (keyCode == KeyEvent.KEYCODE_MENU) {
devSupportManager.showDevOptionsDialog()
return true
}
val didDoubleTapR = doubleTapReloadRecognizer?.didDoubleTapR(keyCode, activity.currentFocus)
if (didDoubleTapR == true) {
devSupportManager.handleReloadJS()
return true
}
return false
}
@DeprecatedInNewArchitecture(message = "Use reactHost")
public fun getReactInstanceManager(): ReactInstanceManager {
val nonNullReactNativeHost =
checkNotNull(reactNativeHost) {
"Cannot get ReactInstanceManager without a ReactNativeHost."
}
return nonNullReactNativeHost.reactInstanceManager
}
/**
* Get the current [ReactContext] from [ReactHost] or [ReactInstanceManager]
*
* Do not store a reference to this, if the React instance is reloaded or destroyed, this context
* will no longer be valid.
*/
public val currentReactContext: ReactContext?
get() {
return if (ReactNativeNewArchitectureFeatureFlags.enableBridgelessArchitecture()) {
if (reactHost != null) {
reactHost?.currentReactContext
} else {
null
}
} else {
getReactInstanceManager().currentReactContext
}
}
}
@@ -74,7 +74,6 @@ import com.facebook.react.bridge.queue.ReactQueueConfigurationSpec;
import com.facebook.react.common.LifecycleState;
import com.facebook.react.common.ReactConstants;
import com.facebook.react.common.SurfaceDelegateFactory;
import com.facebook.react.common.annotations.VisibleForTesting;
import com.facebook.react.common.annotations.internal.LegacyArchitecture;
import com.facebook.react.common.annotations.internal.LegacyArchitectureLogLevel;
import com.facebook.react.common.annotations.internal.LegacyArchitectureLogger;
@@ -1079,6 +1078,10 @@ public class ReactInstanceManager {
if (reactPackage instanceof ViewManagerOnDemandReactPackage) {
Collection<String> names =
((ViewManagerOnDemandReactPackage) reactPackage).getViewManagerNames(context);
// When converting this class to Kotlin, you need to retain this null check
// or wrap around a try/catch otherwise this will cause a crash for OSS libraries
// that are not migrated to Kotlin yet and are returning null for
// `getViewManagerNames`
if (names != null) {
uniqueNames.addAll(names);
}
@@ -1115,7 +1118,6 @@ public class ReactInstanceManager {
/**
* @return current ReactApplicationContext
*/
@VisibleForTesting
public @Nullable ReactContext getCurrentReactContext() {
synchronized (mReactContextLock) {
return mCurrentReactContext;
@@ -84,7 +84,7 @@ public class NativeAnimatedModule(reactContext: ReactApplicationContext?) :
NativeAnimatedModuleSpec(reactContext), LifecycleEventListener, UIManagerListener {
// For `queueAndExecuteBatchedOperations`
private enum class BatchExecutionOpCodes(val value: Int) {
private enum class BatchExecutionOpCodes(value: Int) {
OP_CODE_CREATE_ANIMATED_NODE(1),
OP_CODE_UPDATE_ANIMATED_NODE_CONFIG(2),
OP_CODE_GET_VALUE(3),
@@ -9,7 +9,6 @@ package com.facebook.react.bridge
import com.facebook.proguard.annotations.DoNotStrip
import com.facebook.react.bridge.queue.ReactQueueConfiguration
import com.facebook.react.common.annotations.VisibleForTesting
import com.facebook.react.common.annotations.internal.LegacyArchitecture
import com.facebook.react.internal.turbomodule.core.interfaces.TurboModuleRegistry
import com.facebook.react.turbomodule.core.interfaces.CallInvokerHolder
@@ -53,7 +52,7 @@ public interface CatalystInstance : MemoryPressureListener, JSInstance, JSBundle
public val isDestroyed: Boolean
/** Initialize all the native modules */
@VisibleForTesting public fun initialize()
public fun initialize()
public val reactQueueConfiguration: ReactQueueConfiguration
@@ -90,7 +89,7 @@ public interface CatalystInstance : MemoryPressureListener, JSInstance, JSBundle
/** This method registers the file path of an additional JS segment by its ID. */
public fun registerSegment(segmentId: Int, path: String)
@VisibleForTesting public fun setGlobalVariable(propName: String, jsonValue: String)
public fun setGlobalVariable(propName: String, jsonValue: String)
/**
* Do not use this anymore. Use [runtimeExecutor] instead. Get the C pointer (as a long) to the
@@ -55,11 +55,6 @@ internal object ReactSoftExceptionLogger {
listeners.remove(listener)
}
@JvmStatic
fun clearListeners(): Unit {
listeners.clear()
}
@JvmStatic
fun logSoftExceptionVerbose(@CategoryMode category: String, cause: Throwable): Unit {
logSoftException("${category}|${cause.javaClass.simpleName}:${cause.message}", cause)
@@ -36,9 +36,9 @@ private constructor(
}
}
public companion object {
companion object {
@JvmStatic
public fun create(
fun create(
spec: ReactQueueConfigurationSpec,
exceptionHandler: QueueThreadExceptionHandler
): ReactQueueConfigurationImpl {
@@ -17,7 +17,6 @@ import com.facebook.react.common.annotations.UnstableReactNativeAPI
import com.facebook.react.common.build.ReactBuildConfig
import com.facebook.react.fabric.ComponentFactory
import com.facebook.react.runtime.BindingsInstaller
import com.facebook.react.runtime.JSCInstance
import com.facebook.react.runtime.JSRuntimeFactory
import com.facebook.react.runtime.ReactHostImpl
import com.facebook.react.runtime.cxxreactpackage.CxxReactPackage
@@ -208,7 +207,7 @@ public object DefaultReactHost {
jsMainModulePath,
jsBundleAssetPath,
jsBundleFilePath,
if (isHermesEnabled) HermesInstance() else JSCInstance(),
HermesInstance(),
useDevSupport,
cxxReactPackageProviders,
exceptionHandler,
@@ -264,7 +263,7 @@ public object DefaultReactHost {
jsMainModulePath,
jsBundleAssetPath,
jsBundleFilePath,
if (isHermesEnabled) HermesInstance() else JSCInstance(),
HermesInstance(),
useDevSupport,
cxxReactPackageProviders,
)
@@ -17,7 +17,6 @@ import com.facebook.react.bridge.UIManagerProvider
import com.facebook.react.common.annotations.UnstableReactNativeAPI
import com.facebook.react.fabric.ComponentFactory
import com.facebook.react.fabric.FabricUIManagerProviderImpl
import com.facebook.react.runtime.JSCInstance
import com.facebook.react.runtime.JSRuntimeFactory
import com.facebook.react.runtime.hermes.HermesInstance
import com.facebook.react.uimanager.ViewManagerRegistry
@@ -93,6 +92,9 @@ protected constructor(
* If true, the app will load the Hermes engine, and fail if not found. If false, the app will
* load the JSC engine, and fail if not found.
*/
@Deprecated(
"Setting isHermesEnabled inside `ReactNativeHost` is deprecated and this field will be ignored. If this field is set to true, you can safely remove it. If this field is set to false, please follow the setup on https://github.com/react-native-community/javascriptcore to continue using JSC",
ReplaceWith(""))
protected open val isHermesEnabled: Boolean
get() = true
@@ -106,8 +108,7 @@ protected constructor(
context: Context,
jsRuntimeFactory: JSRuntimeFactory? = null
): ReactHost {
val concreteJSRuntimeFactory =
jsRuntimeFactory ?: if (isHermesEnabled) HermesInstance() else JSCInstance()
val concreteJSRuntimeFactory = jsRuntimeFactory ?: HermesInstance()
return DefaultReactHost.getDefaultReactHost(
context,
packages,
@@ -107,9 +107,6 @@ public class DefaultDevLoadingViewImplementation(
}
}
private val context: Context?
get() = reactInstanceDevHelper.currentActivity
public companion object {
private var isEnabled = true
@@ -1317,7 +1317,7 @@ public class FabricUIManager
@UiThread
@ThreadConfined(UI)
@Override
public void willMountItems(@Nullable List<MountItem> mountItems) {
public void willMountItems(@Nullable List<? extends MountItem> mountItems) {
for (UIManagerListener listener : mListeners) {
listener.willMountItems(FabricUIManager.this);
}
@@ -1326,7 +1326,7 @@ public class FabricUIManager
@UiThread
@ThreadConfined(UI)
@Override
public void didMountItems(@Nullable List<MountItem> mountItems) {
public void didMountItems(@Nullable List<? extends MountItem> mountItems) {
for (UIManagerListener listener : mListeners) {
listener.didMountItems(FabricUIManager.this);
}
@@ -1,410 +0,0 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
package com.facebook.react.fabric.mounting;
import static com.facebook.infer.annotation.ThreadConfined.UI;
import static com.facebook.react.fabric.FabricUIManager.IS_DEVELOPMENT_ENVIRONMENT;
import android.os.SystemClock;
import android.view.View;
import androidx.annotation.Nullable;
import androidx.annotation.UiThread;
import com.facebook.common.logging.FLog;
import com.facebook.infer.annotation.Assertions;
import com.facebook.infer.annotation.Nullsafe;
import com.facebook.infer.annotation.ThreadConfined;
import com.facebook.react.bridge.ReactIgnorableMountingException;
import com.facebook.react.bridge.ReactNoCrashSoftException;
import com.facebook.react.bridge.ReactSoftExceptionLogger;
import com.facebook.react.bridge.RetryableMountingLayerException;
import com.facebook.react.fabric.mounting.mountitems.DispatchCommandMountItem;
import com.facebook.react.fabric.mounting.mountitems.MountItem;
import com.facebook.react.internal.featureflags.ReactNativeFeatureFlags;
import com.facebook.systrace.Systrace;
import java.util.ArrayList;
import java.util.List;
import java.util.Queue;
import java.util.concurrent.ConcurrentLinkedQueue;
@Nullsafe(Nullsafe.Mode.LOCAL)
public class MountItemDispatcher {
private static final String TAG = "MountItemDispatcher";
private static final long FRAME_TIME_NS = 1_000_000_000 / 60;
private final MountingManager mMountingManager;
private final ItemDispatchListener mItemDispatchListener;
private final ConcurrentLinkedQueue<DispatchCommandMountItem> mViewCommandMountItems =
new ConcurrentLinkedQueue<>();
private final ConcurrentLinkedQueue<MountItem> mMountItems = new ConcurrentLinkedQueue<>();
private final ConcurrentLinkedQueue<MountItem> mPreMountItems = new ConcurrentLinkedQueue<>();
private boolean mInDispatch = false;
private long mBatchedExecutionTime = 0L;
private long mRunStartTime = 0L;
private long mLastFrameTimeNanos = 0L;
public MountItemDispatcher(MountingManager mountingManager, ItemDispatchListener listener) {
mMountingManager = mountingManager;
mItemDispatchListener = listener;
}
public void addViewCommandMountItem(DispatchCommandMountItem mountItem) {
mViewCommandMountItems.add(mountItem);
}
public void addMountItem(MountItem mountItem) {
mMountItems.add(mountItem);
}
public void addPreAllocateMountItem(MountItem mountItem) {
// We do this check only for PreAllocateViewMountItem - and not DispatchMountItem or regular
// MountItem - because PreAllocateViewMountItems are not batched, and is relatively more
// expensive
// both to queue, to drain, and to execute.
if (!mMountingManager.surfaceIsStopped(mountItem.getSurfaceId())) {
mPreMountItems.add(mountItem);
} else if (IS_DEVELOPMENT_ENVIRONMENT) {
FLog.e(
TAG,
"Not queueing PreAllocateMountItem: surfaceId stopped: [%d] - %s",
mountItem.getSurfaceId(),
mountItem.toString());
}
}
/**
* Try to dispatch MountItems. In case of the exception, we will retry 10 times before giving up.
*/
@UiThread
@ThreadConfined(UI)
public void tryDispatchMountItems() {
// If we're already dispatching, don't reenter.
// Reentrance can potentially happen a lot on Android in Fabric because
// `updateState` from the
// mounting layer causes mount items to be dispatched synchronously. We want to 1) make sure
// we don't reenter in those cases, but 2) still execute those queued instructions
// synchronously.
// This is a pretty blunt tool, but we might not have better options since we really don't want
// to execute anything out-of-order.
if (mInDispatch) {
return;
}
mInDispatch = true;
try {
dispatchMountItems();
} finally {
// Clean up after running dispatchMountItems - even if an exception was thrown
mInDispatch = false;
}
// We call didDispatchMountItems regardless of whether we actually dispatched anything, since
// NativeAnimatedModule relies on this for executing any animations that may have been
// scheduled
mItemDispatchListener.didDispatchMountItems();
}
@UiThread
@ThreadConfined(UI)
public void dispatchMountItems(Queue<MountItem> mountItems) {
while (!mountItems.isEmpty()) {
MountItem item = mountItems.poll();
Assertions.assertNotNull(item);
try {
item.execute(mMountingManager);
} catch (RetryableMountingLayerException e) {
if (item instanceof DispatchCommandMountItem) {
// Only DispatchCommandMountItem supports retries
DispatchCommandMountItem mountItem = (DispatchCommandMountItem) item;
// Retrying exactly once
if (mountItem.getRetries() == 0) {
mountItem.incrementRetries();
// In case we haven't retried executing this item yet, execute in the next batch of
// items
addViewCommandMountItem(mountItem);
}
} else {
printMountItem(
item, "dispatchExternalMountItems: mounting failed with " + e.getMessage());
}
}
}
}
/*
* Executes view commands, pre mount items and mount items in the respective order:
* 1. View commands.
* 2. Pre mount items.
* 3. Regular mount items.
*
* Does nothing if `viewCommandMountItemsToDispatch` and `mountItemsToDispatch` are empty.
* Nothing should call this directly except for `tryDispatchMountItems`.
*/
@UiThread
@ThreadConfined(UI)
private void dispatchMountItems() {
mBatchedExecutionTime = 0;
mRunStartTime = SystemClock.uptimeMillis();
List<DispatchCommandMountItem> viewCommandMountItemsToDispatch =
getAndResetViewCommandMountItems();
List<MountItem> mountItemsToDispatch = getAndResetMountItems();
if (mountItemsToDispatch == null && viewCommandMountItemsToDispatch == null) {
return;
}
mItemDispatchListener.willMountItems(mountItemsToDispatch);
// As an optimization, execute all ViewCommands first
// This should be:
// 1) Performant: ViewCommands are often a replacement for SetNativeProps, which we've always
// wanted to be as "synchronous" as possible.
// 2) Safer: ViewCommands are inherently disconnected from the tree commit/diff/mount process.
// JS imperatively queues these commands.
// If JS has queued a command, it's reasonable to assume that the more time passes, the more
// likely it is that the view disappears.
// Thus, by executing ViewCommands early, we should actually avoid a category of
// errors/glitches.
if (viewCommandMountItemsToDispatch != null) {
Systrace.beginSection(
Systrace.TRACE_TAG_REACT, "MountItemDispatcher::mountViews viewCommandMountItems");
for (DispatchCommandMountItem command : viewCommandMountItemsToDispatch) {
if (ReactNativeFeatureFlags.enableFabricLogs()) {
printMountItem(command, "dispatchMountItems: Executing viewCommandMountItem");
}
try {
executeOrEnqueue(command);
} catch (RetryableMountingLayerException e) {
// If the exception is marked as Retryable, we retry the viewcommand exactly once, after
// the current batch of mount items has finished executing.
if (command.getRetries() == 0) {
command.incrementRetries();
addViewCommandMountItem(command);
} else {
// It's very common for commands to be executed on views that no longer exist - for
// example, a blur event on TextInput being fired because of a navigation event away
// from the current screen. If the exception is marked as Retryable, we log a soft
// exception but never crash in debug.
// It's not clear that logging this is even useful, because these events are very
// common, mundane, and there's not much we can do about them currently.
ReactSoftExceptionLogger.logSoftException(
TAG,
new ReactNoCrashSoftException(
"Caught exception executing ViewCommand: " + command.toString(), e));
}
} catch (Throwable e) {
// Non-Retryable exceptions are logged as soft exceptions in prod, but crash in Debug.
ReactSoftExceptionLogger.logSoftException(
TAG,
new RuntimeException(
"Caught exception executing ViewCommand: " + command.toString(), e));
}
}
Systrace.endSection(Systrace.TRACE_TAG_REACT);
}
// If there are MountItems to dispatch, we make sure all the "pre mount items" are executed
// first
List<MountItem> preMountItemsToDispatch = getAndResetPreMountItems();
if (preMountItemsToDispatch != null) {
Systrace.beginSection(
Systrace.TRACE_TAG_REACT, "MountItemDispatcher::mountViews preMountItems");
for (MountItem preMountItem : preMountItemsToDispatch) {
if (ReactNativeFeatureFlags.enableFabricLogs()) {
printMountItem(preMountItem, "dispatchMountItems: Executing preMountItem");
}
executeOrEnqueue(preMountItem);
}
Systrace.endSection(Systrace.TRACE_TAG_REACT);
}
if (mountItemsToDispatch != null) {
Systrace.beginSection(
Systrace.TRACE_TAG_REACT, "MountItemDispatcher::mountViews mountItems to execute");
long batchedExecutionStartTime = SystemClock.uptimeMillis();
for (MountItem mountItem : mountItemsToDispatch) {
if (ReactNativeFeatureFlags.enableFabricLogs()) {
printMountItem(mountItem, "dispatchMountItems: Executing mountItem");
}
try {
executeOrEnqueue(mountItem);
} catch (Throwable e) {
// If there's an exception, we want to log diagnostics in prod and rethrow.
FLog.e(TAG, "dispatchMountItems: caught exception, displaying mount state", e);
for (MountItem m : mountItemsToDispatch) {
if (m == mountItem) {
// We want to mark the mount item that caused exception
FLog.e(TAG, "dispatchMountItems: mountItem: next mountItem triggered exception!");
}
printMountItem(m, "dispatchMountItems: mountItem");
}
if (mountItem.getSurfaceId() != View.NO_ID) {
SurfaceMountingManager surfaceManager =
mMountingManager.getSurfaceManager(mountItem.getSurfaceId());
if (surfaceManager != null) {
surfaceManager.printSurfaceState();
}
}
if (ReactIgnorableMountingException.isIgnorable(e)) {
ReactSoftExceptionLogger.logSoftException(TAG, e);
} else {
throw e;
}
}
}
mBatchedExecutionTime += SystemClock.uptimeMillis() - batchedExecutionStartTime;
Systrace.endSection(Systrace.TRACE_TAG_REACT);
}
mItemDispatchListener.didMountItems(mountItemsToDispatch);
}
/*
* Executes pre mount items. Pre mount items are operations that can be executed before the mount items come. For example view preallocation.
* This is a performance optimisation to do as much work ahead of time as possible.
*
* `tryDispatchMountItems` will also execute pre mount items, but only if there are mount items to be executed.
*/
@UiThread
@ThreadConfined(UI)
public void dispatchPreMountItems(long frameTimeNanos) {
mLastFrameTimeNanos = frameTimeNanos;
if (mPreMountItems.isEmpty()) {
// Avoid starting systrace if there are no pre mount items.
return;
}
long deadline = mLastFrameTimeNanos + FRAME_TIME_NS / 2;
dispatchPreMountItemsImpl(deadline);
}
private void dispatchPreMountItemsImpl(long deadline) {
Systrace.beginSection(Systrace.TRACE_TAG_REACT, "MountItemDispatcher::premountViews");
// dispatchPreMountItems cannot be reentrant, but we want to prevent dispatchMountItems from
// reentering during dispatchPreMountItems
mInDispatch = true;
try {
while (true) {
if (System.nanoTime() > deadline) {
break;
}
MountItem preMountItemToDispatch = mPreMountItems.poll();
// If list is empty, `poll` will return null, or var will never be set
if (preMountItemToDispatch == null) {
break;
}
if (ReactNativeFeatureFlags.enableFabricLogs()) {
printMountItem(preMountItemToDispatch, "dispatchPreMountItems");
}
executeOrEnqueue(preMountItemToDispatch);
}
} finally {
mInDispatch = false;
}
Systrace.endSection(Systrace.TRACE_TAG_REACT);
}
private void executeOrEnqueue(MountItem item) {
if (mMountingManager.isWaitingForViewAttach(item.getSurfaceId())) {
if (ReactNativeFeatureFlags.enableFabricLogs()) {
FLog.e(
TAG,
"executeOrEnqueue: Item execution delayed, surface %s is not ready yet",
item.getSurfaceId());
}
SurfaceMountingManager surfaceMountingManager =
mMountingManager.getSurfaceManagerEnforced(
item.getSurfaceId(), "MountItemDispatcher::executeOrEnqueue");
surfaceMountingManager.scheduleMountItemOnViewAttach(item);
} else {
item.execute(mMountingManager);
}
}
private static <E> @Nullable List<E> drainConcurrentItemQueue(ConcurrentLinkedQueue<E> queue) {
if (queue.isEmpty()) {
return null;
}
List<E> result = new ArrayList<>();
do {
E item = queue.poll();
if (item != null) {
result.add(item);
}
} while (!queue.isEmpty());
if (result.size() == 0) {
return null;
}
return result;
}
@UiThread
@ThreadConfined(UI)
private @Nullable List<DispatchCommandMountItem> getAndResetViewCommandMountItems() {
return drainConcurrentItemQueue(mViewCommandMountItems);
}
@UiThread
@ThreadConfined(UI)
private @Nullable List<MountItem> getAndResetMountItems() {
return drainConcurrentItemQueue(mMountItems);
}
@UiThread
@ThreadConfined(UI)
private @Nullable List<MountItem> getAndResetPreMountItems() {
return drainConcurrentItemQueue(mPreMountItems);
}
public long getBatchedExecutionTime() {
return mBatchedExecutionTime;
}
public long getRunStartTime() {
return mRunStartTime;
}
private static void printMountItem(MountItem mountItem, String prefix) {
// If a MountItem description is split across multiple lines, it's because it's a
// compound MountItem. Log each line separately.
String[] mountItemLines = mountItem.toString().split("\n");
for (String m : mountItemLines) {
FLog.e(TAG, prefix + ": " + m);
}
}
public interface ItemDispatchListener {
void willMountItems(@Nullable List<MountItem> mountItems);
void didMountItems(@Nullable List<MountItem> mountItems);
void didDispatchMountItems();
}
}
@@ -0,0 +1,365 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
package com.facebook.react.fabric.mounting
import android.os.SystemClock
import android.view.View
import androidx.annotation.UiThread
import com.facebook.common.logging.FLog
import com.facebook.infer.annotation.ThreadConfined
import com.facebook.infer.annotation.ThreadConfined.UI
import com.facebook.react.bridge.ReactIgnorableMountingException
import com.facebook.react.bridge.ReactNoCrashSoftException
import com.facebook.react.bridge.ReactSoftExceptionLogger
import com.facebook.react.bridge.RetryableMountingLayerException
import com.facebook.react.fabric.FabricUIManager
import com.facebook.react.fabric.mounting.mountitems.DispatchCommandMountItem
import com.facebook.react.fabric.mounting.mountitems.MountItem
import com.facebook.react.internal.featureflags.ReactNativeFeatureFlags
import com.facebook.systrace.Systrace
import java.util.Queue
import java.util.concurrent.ConcurrentLinkedQueue
internal class MountItemDispatcher(
private val mountingManager: MountingManager,
private val itemDispatchListener: ItemDispatchListener
) {
private val viewCommandMountItems: Queue<DispatchCommandMountItem> = ConcurrentLinkedQueue()
private val mountItems: Queue<MountItem> = ConcurrentLinkedQueue()
private val preMountItems: Queue<MountItem> = ConcurrentLinkedQueue()
private var inDispatch: Boolean = false
var batchedExecutionTime: Long = 0L
private set
var runStartTime: Long = 0L
private set
private var lastFrameTimeNanos: Long = 0L
fun addViewCommandMountItem(mountItem: DispatchCommandMountItem) {
viewCommandMountItems.add(mountItem)
}
fun addMountItem(mountItem: MountItem) {
mountItems.add(mountItem)
}
fun addPreAllocateMountItem(mountItem: MountItem) {
// We do this check only for PreAllocateViewMountItem - and not DispatchMountItem or regular
// MountItem - because PreAllocateViewMountItems are not batched, and is relatively more
// expensive
// both to queue, to drain, and to execute.
if (!mountingManager.surfaceIsStopped(mountItem.getSurfaceId())) {
preMountItems.add(mountItem)
} else if (FabricUIManager.IS_DEVELOPMENT_ENVIRONMENT) {
FLog.e(
TAG,
"Not queueing PreAllocateMountItem: surfaceId stopped: [%d] - %s",
mountItem.getSurfaceId(),
mountItem.toString())
}
}
/**
* Try to dispatch MountItems. In case of the exception, we will retry 10 times before giving up.
*/
@UiThread
@ThreadConfined(UI)
fun tryDispatchMountItems() {
// If we're already dispatching, don't reenter.
// Reentrance can potentially happen a lot on Android in Fabric because `updateState` from the
// mounting layer causes mount items to be dispatched synchronously. We want to 1) make sure we
// don't reenter in those cases, but 2) still execute those queued instructions synchronously.
// This is a pretty blunt tool, but we might not have better options since we really don't want
// to execute anything out-of-order.
if (inDispatch) {
return
}
inDispatch = true
try {
dispatchMountItems()
} finally {
// Clean up after running dispatchMountItems - even if an exception was thrown
inDispatch = false
}
// We call didDispatchMountItems regardless of whether we actually dispatched anything, since
// NativeAnimatedModule relies on this for executing any animations that may have been
// scheduled
itemDispatchListener.didDispatchMountItems()
}
@UiThread
@ThreadConfined(UI)
fun dispatchMountItems(mountItems: Queue<MountItem?>) {
while (!mountItems.isEmpty()) {
val item = requireNotNull(mountItems.poll()) { "MountItem should not be null" }
try {
item.execute(mountingManager)
} catch (e: RetryableMountingLayerException) {
if (item is DispatchCommandMountItem) {
// Only DispatchCommandMountItem supports retries
val mountItem: DispatchCommandMountItem = item as DispatchCommandMountItem
// Retrying exactly once
if (mountItem.getRetries() == 0) {
mountItem.incrementRetries()
// In case we haven't retried executing this item yet, execute in the next batch of
// items
addViewCommandMountItem(mountItem)
}
} else {
printMountItem(item, "dispatchExternalMountItems: mounting failed with ${e.message}")
}
}
}
}
/*
* Executes view commands, pre mount items and mount items in the respective order:
* 1. View commands.
* 2. Pre mount items.
* 3. Regular mount items.
*
* Does nothing if `viewCommandMountItemsToDispatch` and `mountItemsToDispatch` are empty.
* Nothing should call this directly except for `tryDispatchMountItems`.
*/
@UiThread
@ThreadConfined(UI)
private fun dispatchMountItems() {
batchedExecutionTime = 0
runStartTime = SystemClock.uptimeMillis()
val viewCommandMountItemsToDispatch = getAndResetViewCommandMountItems()
val mountItemsToDispatch = getAndResetMountItems()
if (mountItemsToDispatch == null && viewCommandMountItemsToDispatch == null) {
return
}
itemDispatchListener.willMountItems(mountItemsToDispatch)
// As an optimization, execute all ViewCommands first
// This should be:
// 1) Performant: ViewCommands are often a replacement for SetNativeProps, which we've always
// wanted to be as "synchronous" as possible.
// 2) Safer: ViewCommands are inherently disconnected from the tree commit/diff/mount process.
// JS imperatively queues these commands.
// If JS has queued a command, it's reasonable to assume that the more time passes, the more
// likely it is that the view disappears.
// Thus, by executing ViewCommands early, we should actually avoid a category of
// errors/glitches.
viewCommandMountItemsToDispatch?.let { commands ->
Systrace.beginSection(
Systrace.TRACE_TAG_REACT, "MountItemDispatcher::mountViews viewCommandMountItems")
for (command in commands) {
if (ReactNativeFeatureFlags.enableFabricLogs()) {
printMountItem(command, "dispatchMountItems: Executing viewCommandMountItem")
}
try {
executeOrEnqueue(command)
} catch (e: RetryableMountingLayerException) {
// If the exception is marked as Retryable, we retry the viewcommand exactly once, after
// the current batch of mount items has finished executing.
if (command.getRetries() == 0) {
command.incrementRetries()
addViewCommandMountItem(command)
} else {
// It's very common for commands to be executed on views that no longer exist - for
// example, a blur event on TextInput being fired because of a navigation event away
// from the current screen. If the exception is marked as Retryable, we log a soft
// exception but never crash in debug.
// It's not clear that logging this is even useful, because these events are very
// common, mundane, and there's not much we can do about them currently.
ReactSoftExceptionLogger.logSoftException(
TAG,
ReactNoCrashSoftException("Caught exception executing ViewCommand: $command", e))
}
} catch (e: Throwable) {
// Non-retryable exceptions are logged as soft exceptions in prod, but crash in Debug.
ReactSoftExceptionLogger.logSoftException(
TAG, RuntimeException("Caught exception executing ViewCommand: $command", e))
}
}
Systrace.endSection(Systrace.TRACE_TAG_REACT)
}
// If there are MountItems to dispatch, we make sure all the "pre mount items" are executed
// first
getAndResetPreMountItems()?.let { preMountItems ->
Systrace.beginSection(
Systrace.TRACE_TAG_REACT, "MountItemDispatcher::mountViews preMountItems")
for (preMountItem in preMountItems) {
if (ReactNativeFeatureFlags.enableFabricLogs()) {
printMountItem(preMountItem, "dispatchMountItems: Executing preMountItem")
}
executeOrEnqueue(preMountItem)
}
Systrace.endSection(Systrace.TRACE_TAG_REACT)
}
mountItemsToDispatch?.let { items ->
Systrace.beginSection(
Systrace.TRACE_TAG_REACT, "MountItemDispatcher::mountViews mountItems to execute")
val batchedExecutionStartTime = SystemClock.uptimeMillis()
for (mountItem in items) {
if (ReactNativeFeatureFlags.enableFabricLogs()) {
printMountItem(mountItem, "dispatchMountItems: Executing mountItem")
}
try {
executeOrEnqueue(mountItem)
} catch (e: Throwable) {
// If there's an exception, we want to log diagnostics in prod and rethrow.
FLog.e(TAG, "dispatchMountItems: caught exception, displaying mount state", e)
for (m in items) {
if (m === mountItem) {
// We want to mark the mount item that caused exception
FLog.e(TAG, "dispatchMountItems: mountItem: next mountItem triggered exception!")
}
printMountItem(m, "dispatchMountItems: mountItem")
}
if (mountItem.getSurfaceId() != View.NO_ID) {
mountingManager.getSurfaceManager(mountItem.getSurfaceId())?.printSurfaceState()
}
if (ReactIgnorableMountingException.isIgnorable(e)) {
ReactSoftExceptionLogger.logSoftException(TAG, e)
} else {
throw e
}
}
}
batchedExecutionTime += SystemClock.uptimeMillis() - batchedExecutionStartTime
Systrace.endSection(Systrace.TRACE_TAG_REACT)
}
itemDispatchListener.didMountItems(mountItemsToDispatch)
}
/*
* Executes pre-mount items. Pre-mount items are operations that can be executed before the mount
* items come, for example view pre-allocation. This is a performance optimisation to do as much
* work ahead of time as possible.
*
* `tryDispatchMountItems` will also execute pre mount items, but only if there are mount items to be executed.
*/
@UiThread
@ThreadConfined(UI)
fun dispatchPreMountItems(frameTimeNanos: Long) {
lastFrameTimeNanos = frameTimeNanos
if (preMountItems.isEmpty()) {
// Avoid starting systrace if there are no pre mount items.
return
}
val deadline = lastFrameTimeNanos + FRAME_TIME_NS / 2
dispatchPreMountItemsImpl(deadline)
}
private fun dispatchPreMountItemsImpl(deadline: Long) {
Systrace.beginSection(Systrace.TRACE_TAG_REACT, "MountItemDispatcher::premountViews")
// dispatchPreMountItems cannot be reentrant, but we want to prevent dispatchMountItems from
// reentering during dispatchPreMountItems
inDispatch = true
try {
while (true) {
if (System.nanoTime() > deadline) {
break
}
// If list is empty, `poll` will return null, or var will never be set
val preMountItemToDispatch = preMountItems.poll() ?: break
if (ReactNativeFeatureFlags.enableFabricLogs()) {
printMountItem(preMountItemToDispatch, "dispatchPreMountItems")
}
executeOrEnqueue(preMountItemToDispatch)
}
} finally {
inDispatch = false
}
Systrace.endSection(Systrace.TRACE_TAG_REACT)
}
private fun executeOrEnqueue(item: MountItem) {
if (mountingManager.isWaitingForViewAttach(item.getSurfaceId())) {
if (ReactNativeFeatureFlags.enableFabricLogs()) {
FLog.e(
TAG,
"executeOrEnqueue: Item execution delayed, surface %s is not ready yet",
item.getSurfaceId())
}
val surfaceMountingManager: SurfaceMountingManager =
mountingManager.getSurfaceManagerEnforced(
item.getSurfaceId(), "MountItemDispatcher::executeOrEnqueue")
surfaceMountingManager.scheduleMountItemOnViewAttach(item)
} else {
item.execute(mountingManager)
}
}
@ThreadConfined(UI)
@UiThread
private fun getAndResetViewCommandMountItems(): List<DispatchCommandMountItem>? =
drainConcurrentItemQueue(viewCommandMountItems)
@ThreadConfined(UI)
@UiThread
private fun getAndResetMountItems(): List<MountItem>? = drainConcurrentItemQueue(mountItems)
@ThreadConfined(UI)
@UiThread
private fun getAndResetPreMountItems(): List<MountItem>? = drainConcurrentItemQueue(preMountItems)
interface ItemDispatchListener {
fun willMountItems(mountItems: List<MountItem>?)
fun didMountItems(mountItems: List<MountItem>?)
fun didDispatchMountItems()
}
private companion object {
private const val TAG: String = "MountItemDispatcher"
private const val FRAME_TIME_NS: Long = (1000000000 / 60).toLong()
private fun <E> drainConcurrentItemQueue(queue: Queue<E>): List<E>? {
if (queue.isEmpty()) {
return null
}
return buildList {
do {
queue.poll()?.let { add(it) }
} while (queue.isNotEmpty())
}
.takeIf { it.isNotEmpty() }
}
private fun printMountItem(mountItem: MountItem, prefix: String) {
// If a MountItem description is split across multiple lines, it's because it's a
// compound MountItem. Log each line separately.
val mountItemLines = mountItem.toString().split("\n").dropLastWhile { it.isEmpty() }
for (line in mountItemLines) {
FLog.e(TAG, "$prefix: $line")
}
}
}
}
@@ -4,7 +4,7 @@
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @generated SignedSource<<4c40e490481c50d2f19830366f50aad5>>
* @generated SignedSource<<dee97293bfeab942c9cd0ba8622a5f86>>
*/
/**
@@ -48,6 +48,12 @@ public object ReactNativeFeatureFlags {
@JvmStatic
public fun cxxNativeAnimatedEnabled(): Boolean = accessor.cxxNativeAnimatedEnabled()
/**
* Removes JS sync at end of native animation
*/
@JvmStatic
public fun cxxNativeAnimatedRemoveJsSync(): Boolean = accessor.cxxNativeAnimatedRemoveJsSync()
/**
* Disable sync dispatch on the main queue on iOS
*/
@@ -150,12 +156,6 @@ public object ReactNativeFeatureFlags {
@JvmStatic
public fun enableIOSViewClipToPaddingBox(): Boolean = accessor.enableIOSViewClipToPaddingBox()
/**
* Integrates IntersectionObserver in the Event Loop in the new architecture, to dispatch the initial notifications for observations in the "Update the rendering" step.
*/
@JvmStatic
public fun enableIntersectionObserverEventLoopIntegration(): Boolean = accessor.enableIntersectionObserverEventLoopIntegration()
/**
* When enabled, LayoutAnimations API will animate state changes on Android.
*/
@@ -4,7 +4,7 @@
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @generated SignedSource<<8cb9eb082005be480c39272ffc7dde03>>
* @generated SignedSource<<c46464d2d10ac0c8a6b7869ae5b3cd2b>>
*/
/**
@@ -23,6 +23,7 @@ internal class ReactNativeFeatureFlagsCxxAccessor : ReactNativeFeatureFlagsAcces
private var commonTestFlagCache: Boolean? = null
private var animatedShouldSignalBatchCache: Boolean? = null
private var cxxNativeAnimatedEnabledCache: Boolean? = null
private var cxxNativeAnimatedRemoveJsSyncCache: Boolean? = null
private var disableMainQueueSyncDispatchIOSCache: Boolean? = null
private var disableMountItemReorderingAndroidCache: Boolean? = null
private var disableTextLayoutManagerCacheAndroidCache: Boolean? = null
@@ -40,7 +41,6 @@ internal class ReactNativeFeatureFlagsCxxAccessor : ReactNativeFeatureFlagsAcces
private var enableFontScaleChangesUpdatingLayoutCache: Boolean? = null
private var enableIOSTextBaselineOffsetPerLineCache: Boolean? = null
private var enableIOSViewClipToPaddingBoxCache: Boolean? = null
private var enableIntersectionObserverEventLoopIntegrationCache: Boolean? = null
private var enableLayoutAnimationsOnAndroidCache: Boolean? = null
private var enableLayoutAnimationsOnIOSCache: Boolean? = null
private var enableMainQueueCoordinatorOnIOSCache: Boolean? = null
@@ -100,6 +100,15 @@ internal class ReactNativeFeatureFlagsCxxAccessor : ReactNativeFeatureFlagsAcces
return cached
}
override fun cxxNativeAnimatedRemoveJsSync(): Boolean {
var cached = cxxNativeAnimatedRemoveJsSyncCache
if (cached == null) {
cached = ReactNativeFeatureFlagsCxxInterop.cxxNativeAnimatedRemoveJsSync()
cxxNativeAnimatedRemoveJsSyncCache = cached
}
return cached
}
override fun disableMainQueueSyncDispatchIOS(): Boolean {
var cached = disableMainQueueSyncDispatchIOSCache
if (cached == null) {
@@ -253,15 +262,6 @@ internal class ReactNativeFeatureFlagsCxxAccessor : ReactNativeFeatureFlagsAcces
return cached
}
override fun enableIntersectionObserverEventLoopIntegration(): Boolean {
var cached = enableIntersectionObserverEventLoopIntegrationCache
if (cached == null) {
cached = ReactNativeFeatureFlagsCxxInterop.enableIntersectionObserverEventLoopIntegration()
enableIntersectionObserverEventLoopIntegrationCache = cached
}
return cached
}
override fun enableLayoutAnimationsOnAndroid(): Boolean {
var cached = enableLayoutAnimationsOnAndroidCache
if (cached == null) {
@@ -4,7 +4,7 @@
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @generated SignedSource<<b6ddd7c2e829c427e7d6b07bc588cd8c>>
* @generated SignedSource<<62c548a9bfbf777a40b1b9e9d52b1de2>>
*/
/**
@@ -34,6 +34,8 @@ public object ReactNativeFeatureFlagsCxxInterop {
@DoNotStrip @JvmStatic public external fun cxxNativeAnimatedEnabled(): Boolean
@DoNotStrip @JvmStatic public external fun cxxNativeAnimatedRemoveJsSync(): Boolean
@DoNotStrip @JvmStatic public external fun disableMainQueueSyncDispatchIOS(): Boolean
@DoNotStrip @JvmStatic public external fun disableMountItemReorderingAndroid(): Boolean
@@ -68,8 +70,6 @@ public object ReactNativeFeatureFlagsCxxInterop {
@DoNotStrip @JvmStatic public external fun enableIOSViewClipToPaddingBox(): Boolean
@DoNotStrip @JvmStatic public external fun enableIntersectionObserverEventLoopIntegration(): Boolean
@DoNotStrip @JvmStatic public external fun enableLayoutAnimationsOnAndroid(): Boolean
@DoNotStrip @JvmStatic public external fun enableLayoutAnimationsOnIOS(): Boolean
@@ -4,7 +4,7 @@
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @generated SignedSource<<76edde93369be682e837189f82871dfe>>
* @generated SignedSource<<de8775978e15479830fb215c6f15b144>>
*/
/**
@@ -29,6 +29,8 @@ public open class ReactNativeFeatureFlagsDefaults : ReactNativeFeatureFlagsProvi
override fun cxxNativeAnimatedEnabled(): Boolean = false
override fun cxxNativeAnimatedRemoveJsSync(): Boolean = false
override fun disableMainQueueSyncDispatchIOS(): Boolean = false
override fun disableMountItemReorderingAndroid(): Boolean = false
@@ -63,8 +65,6 @@ public open class ReactNativeFeatureFlagsDefaults : ReactNativeFeatureFlagsProvi
override fun enableIOSViewClipToPaddingBox(): Boolean = false
override fun enableIntersectionObserverEventLoopIntegration(): Boolean = true
override fun enableLayoutAnimationsOnAndroid(): Boolean = false
override fun enableLayoutAnimationsOnIOS(): Boolean = true
@@ -4,7 +4,7 @@
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @generated SignedSource<<e718763435de15f788b1f510e4799a67>>
* @generated SignedSource<<220b0cff47d2eadddf8a36646852d619>>
*/
/**
@@ -27,6 +27,7 @@ internal class ReactNativeFeatureFlagsLocalAccessor : ReactNativeFeatureFlagsAcc
private var commonTestFlagCache: Boolean? = null
private var animatedShouldSignalBatchCache: Boolean? = null
private var cxxNativeAnimatedEnabledCache: Boolean? = null
private var cxxNativeAnimatedRemoveJsSyncCache: Boolean? = null
private var disableMainQueueSyncDispatchIOSCache: Boolean? = null
private var disableMountItemReorderingAndroidCache: Boolean? = null
private var disableTextLayoutManagerCacheAndroidCache: Boolean? = null
@@ -44,7 +45,6 @@ internal class ReactNativeFeatureFlagsLocalAccessor : ReactNativeFeatureFlagsAcc
private var enableFontScaleChangesUpdatingLayoutCache: Boolean? = null
private var enableIOSTextBaselineOffsetPerLineCache: Boolean? = null
private var enableIOSViewClipToPaddingBoxCache: Boolean? = null
private var enableIntersectionObserverEventLoopIntegrationCache: Boolean? = null
private var enableLayoutAnimationsOnAndroidCache: Boolean? = null
private var enableLayoutAnimationsOnIOSCache: Boolean? = null
private var enableMainQueueCoordinatorOnIOSCache: Boolean? = null
@@ -107,6 +107,16 @@ internal class ReactNativeFeatureFlagsLocalAccessor : ReactNativeFeatureFlagsAcc
return cached
}
override fun cxxNativeAnimatedRemoveJsSync(): Boolean {
var cached = cxxNativeAnimatedRemoveJsSyncCache
if (cached == null) {
cached = currentProvider.cxxNativeAnimatedRemoveJsSync()
accessedFeatureFlags.add("cxxNativeAnimatedRemoveJsSync")
cxxNativeAnimatedRemoveJsSyncCache = cached
}
return cached
}
override fun disableMainQueueSyncDispatchIOS(): Boolean {
var cached = disableMainQueueSyncDispatchIOSCache
if (cached == null) {
@@ -277,16 +287,6 @@ internal class ReactNativeFeatureFlagsLocalAccessor : ReactNativeFeatureFlagsAcc
return cached
}
override fun enableIntersectionObserverEventLoopIntegration(): Boolean {
var cached = enableIntersectionObserverEventLoopIntegrationCache
if (cached == null) {
cached = currentProvider.enableIntersectionObserverEventLoopIntegration()
accessedFeatureFlags.add("enableIntersectionObserverEventLoopIntegration")
enableIntersectionObserverEventLoopIntegrationCache = cached
}
return cached
}
override fun enableLayoutAnimationsOnAndroid(): Boolean {
var cached = enableLayoutAnimationsOnAndroidCache
if (cached == null) {
@@ -4,7 +4,7 @@
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @generated SignedSource<<892db232156970e8241d1f1ddde3a745>>
* @generated SignedSource<<d2ab95a5f7bf0dfc52428a90bee2fc10>>
*/
/**
@@ -29,6 +29,8 @@ public interface ReactNativeFeatureFlagsProvider {
@DoNotStrip public fun cxxNativeAnimatedEnabled(): Boolean
@DoNotStrip public fun cxxNativeAnimatedRemoveJsSync(): Boolean
@DoNotStrip public fun disableMainQueueSyncDispatchIOS(): Boolean
@DoNotStrip public fun disableMountItemReorderingAndroid(): Boolean
@@ -63,8 +65,6 @@ public interface ReactNativeFeatureFlagsProvider {
@DoNotStrip public fun enableIOSViewClipToPaddingBox(): Boolean
@DoNotStrip public fun enableIntersectionObserverEventLoopIntegration(): Boolean
@DoNotStrip public fun enableLayoutAnimationsOnAndroid(): Boolean
@DoNotStrip public fun enableLayoutAnimationsOnIOS(): Boolean
@@ -233,8 +233,8 @@ internal object TurboModuleInteropUtils {
i += 1
}
if (returnClass == java.lang.Boolean::class.javaPrimitiveType ||
returnClass == java.lang.Boolean::class.java) {
if (returnClass == Boolean::class.javaPrimitiveType ||
returnClass == Boolean::class.javaObjectType) {
return "BooleanKind"
}
@@ -1,41 +0,0 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
package com.facebook.react.jscexecutor
import com.facebook.jni.HybridData
import com.facebook.proguard.annotations.DoNotStrip
import com.facebook.react.bridge.JavaScriptExecutor
import com.facebook.react.bridge.ReadableNativeMap
import com.facebook.react.common.annotations.internal.LegacyArchitecture
import com.facebook.react.common.annotations.internal.LegacyArchitectureLogger
import com.facebook.soloader.SoLoader
@DoNotStrip
@LegacyArchitecture
public class JSCExecutor internal constructor(jscConfig: ReadableNativeMap) :
JavaScriptExecutor(initHybrid(jscConfig)) {
override fun getName(): String {
return "JSCExecutor"
}
internal companion object {
init {
loadLibrary()
LegacyArchitectureLogger.assertLegacyArchitecture("JSCExecutor")
}
@JvmStatic
@Throws(UnsatisfiedLinkError::class)
fun loadLibrary() {
SoLoader.loadLibrary("jscexecutor")
}
@JvmStatic private external fun initHybrid(jscConfig: ReadableNativeMap): HybridData
}
}
@@ -1,46 +0,0 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
package com.facebook.react.jscexecutor
import com.facebook.react.bridge.JavaScriptExecutor
import com.facebook.react.bridge.JavaScriptExecutorFactory
import com.facebook.react.bridge.WritableNativeMap
import com.facebook.react.common.annotations.internal.LegacyArchitecture
import com.facebook.react.common.annotations.internal.LegacyArchitectureLogLevel
import com.facebook.react.common.annotations.internal.LegacyArchitectureLogger
@LegacyArchitecture(logLevel = LegacyArchitectureLogLevel.ERROR)
public class JSCExecutorFactory(private val appName: String, private val deviceName: String) :
JavaScriptExecutorFactory {
init {
LegacyArchitectureLogger.assertLegacyArchitecture(
"JSCExecutorFactory", LegacyArchitectureLogLevel.ERROR)
}
@Throws(Exception::class)
override fun create(): JavaScriptExecutor {
val jscConfig =
WritableNativeMap().apply {
putString("OwnerIdentity", "ReactNative")
putString("AppIdentity", appName)
putString("DeviceIdentity", deviceName)
}
return JSCExecutor(jscConfig)
}
override fun startSamplingProfiler() {
throw UnsupportedOperationException("Starting sampling profiler not supported on ${toString()}")
}
override fun stopSamplingProfiler(filename: String) {
throw UnsupportedOperationException("Stopping sampling profiler not supported on ${toString()}")
}
override fun toString(): String = "JSIExecutor+JSCRuntime"
}
@@ -8,13 +8,13 @@
package com.facebook.react.modules.appearance
import android.content.Context
import android.content.res.Configuration
import androidx.appcompat.app.AppCompatDelegate
import com.facebook.fbreact.specs.NativeAppearanceSpec
import com.facebook.react.bridge.ReactApplicationContext
import com.facebook.react.bridge.UiThreadUtil
import com.facebook.react.bridge.buildReadableMap
import com.facebook.react.module.annotations.ReactModule
import com.facebook.react.views.common.UiModeUtils
/** Module that exposes the user's preferred color scheme. */
@ReactModule(name = NativeAppearanceSpec.NAME)
@@ -41,13 +41,7 @@ constructor(
return overrideColorScheme.getScheme()
}
val currentNightMode =
context.resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK
return when (currentNightMode) {
Configuration.UI_MODE_NIGHT_NO -> "light"
Configuration.UI_MODE_NIGHT_YES -> "dark"
else -> "light"
}
return if (UiModeUtils.isDarkMode(context)) "dark" else "light"
}
public override fun getColorScheme(): String {
@@ -13,8 +13,6 @@ import android.os.Build
import android.view.View
import android.view.WindowInsetsController
import android.view.WindowManager
import androidx.core.view.ViewCompat
import androidx.core.view.WindowInsetsCompat
import com.facebook.common.logging.FLog
import com.facebook.fbreact.specs.NativeStatusBarManagerAndroidSpec
import com.facebook.react.bridge.GuardedRunnable
@@ -23,6 +21,7 @@ import com.facebook.react.bridge.ReactApplicationContext
import com.facebook.react.bridge.UiThreadUtil
import com.facebook.react.common.ReactConstants
import com.facebook.react.module.annotations.ReactModule
import com.facebook.react.uimanager.DisplayMetricsHolder.getStatusBarHeightPx
import com.facebook.react.uimanager.PixelUtil
import com.facebook.react.views.view.setStatusBarTranslucency
import com.facebook.react.views.view.setStatusBarVisibility
@@ -34,32 +33,17 @@ internal class StatusBarModule(reactContext: ReactApplicationContext?) :
@Suppress("DEPRECATION")
override fun getTypedExportedConstants(): Map<String, Any> {
val currentActivity = reactApplicationContext.currentActivity
val statusBarColor =
reactApplicationContext.getCurrentActivity()?.window?.statusBarColor?.let { color ->
currentActivity?.window?.statusBarColor?.let { color ->
String.format("#%06X", 0xFFFFFF and color)
} ?: "black"
return mapOf(
HEIGHT_KEY to PixelUtil.toDIPFromPixel(getStatusBarHeightPx()),
HEIGHT_KEY to PixelUtil.toDIPFromPixel(getStatusBarHeightPx(currentActivity).toFloat()),
DEFAULT_BACKGROUND_COLOR_KEY to statusBarColor,
)
}
private fun getStatusBarHeightPx(): Float {
val windowInsets =
reactApplicationContext
.getCurrentActivity()
?.window
?.decorView
?.let(ViewCompat::getRootWindowInsets) ?: return 0f
return windowInsets
.getInsets(
WindowInsetsCompat.Type.statusBars() or
WindowInsetsCompat.Type.navigationBars() or
WindowInsetsCompat.Type.displayCutout())
.top
.toFloat()
}
@Suppress("DEPRECATION")
override fun setColor(colorDouble: Double, animated: Boolean) {
val color = colorDouble.toInt()
@@ -1,24 +0,0 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
package com.facebook.react.runtime
import com.facebook.jni.HybridData
import com.facebook.jni.annotations.DoNotStrip
import com.facebook.jni.annotations.DoNotStripAny
import com.facebook.soloader.SoLoader
@DoNotStripAny
public class JSCInstance : JSRuntimeFactory(initHybrid()) {
private companion object {
init {
SoLoader.loadLibrary("jscinstance")
}
@DoNotStrip @JvmStatic private external fun initHybrid(): HybridData
}
}
@@ -8,9 +8,14 @@
package com.facebook.react.runtime
import com.facebook.jni.HybridData
import com.facebook.proguard.annotations.DoNotStrip
import com.facebook.soloader.SoLoader
public abstract class JSRuntimeFactory(private val mHybridData: HybridData) {
public abstract class JSRuntimeFactory(
@Suppress("unused", "NoHungarianNotation", "NotAccessedPrivateField")
@DoNotStrip
private val mHybridData: HybridData
) {
private companion object {
init {
SoLoader.loadLibrary("rninstance")
@@ -67,6 +67,7 @@ import com.facebook.react.uimanager.ViewManager
import com.facebook.react.uimanager.ViewManagerRegistry
import com.facebook.react.uimanager.ViewManagerResolver
import com.facebook.react.uimanager.events.EventDispatcher
import com.facebook.react.util.RNLog
import com.facebook.soloader.SoLoader
import com.facebook.systrace.Systrace
import com.facebook.systrace.SystraceMessage
@@ -542,7 +543,18 @@ internal class ReactInstance(
for (reactPackage in reactPackages) {
if (reactPackage is ViewManagerOnDemandReactPackage) {
val names = reactPackage.getViewManagerNames(context)
uniqueNames.addAll(names)
// We need to null check here because some Java implementation of the
// `ViewManagerOnDemandReactPackage` interface could still return null even
// if the method is marked as returning a non-nullable collection in Kotlin.
// See https://github.com/facebook/react-native/issues/52014
@Suppress("SENSELESS_COMPARISON")
if (names == null) {
RNLog.w(
context,
"The ReactPackage called: `${reactPackage.javaClass.simpleName}` is returning null for getViewManagerNames(). This is violating the signature of the method. That method should be updated to return an empty collection.")
} else {
uniqueNames.addAll(names)
}
}
}
return uniqueNames
@@ -188,7 +188,6 @@ internal constructor(
internal val eventDispatcher: EventDispatcher?
get() = reactHost?.eventDispatcher
@get:VisibleForTesting
internal val isAttached: Boolean
get() = reactHost != null
@@ -42,11 +42,6 @@ public object OpenSourceMergedSoMapping : ExternalSoMapping {
"jsijniprofiler" -> {
"hermestooling"
}
"jscexecutor",
"jscruntime",
"jscinstance" -> {
"jsctooling"
}
else -> input
}
@@ -56,10 +51,6 @@ public object OpenSourceMergedSoMapping : ExternalSoMapping {
"hermes_executor" -> libhermes_executor_so()
"hermesinstancejni" -> libhermesinstancejni_so()
"hermestooling" -> libhermestooling_so()
"jscexecutor" -> libjscexecutor_so()
"jscinstance" -> libjscinstance_so()
"jscruntime" -> libjscruntime_so()
"jsctooling" -> libjsctooling_so()
"jsijniprofiler" -> libjsijniprofiler_so()
"jsinspector" -> libjsinspector_so()
"mapbufferjni" -> libmapbufferjni_so()
@@ -85,14 +76,6 @@ public object OpenSourceMergedSoMapping : ExternalSoMapping {
public external fun libhermestooling_so(): Int
public external fun libjscexecutor_so(): Int
public external fun libjscinstance_so(): Int
public external fun libjscruntime_so(): Int
public external fun libjsctooling_so(): Int
public external fun libjsijniprofiler_so(): Int
public external fun libjsinspector_so(): Int
@@ -173,30 +173,19 @@ public abstract class BaseViewManager<T extends View, C extends LayoutShadowNode
protected void addEventEmitters(@NonNull ThemedReactContext reactContext, @NonNull T view) {
super.addEventEmitters(reactContext, view);
@Nullable OnFocusChangeListener originalFocusChangeListener = view.getOnFocusChangeListener();
view.setOnFocusChangeListener(
(v, hasFocus) -> {
if (originalFocusChangeListener != null) {
originalFocusChangeListener.onFocusChange(v, hasFocus);
}
int surfaceId = UIManagerHelper.getSurfaceId(v.getContext());
if (surfaceId == View.NO_ID) {
return;
}
if (view.getContext() instanceof ThemedReactContext) {
ThemedReactContext themedReactContext = (ThemedReactContext) v.getContext();
@Nullable
EventDispatcher eventDispatcher =
UIManagerHelper.getEventDispatcherForReactTag(themedReactContext, view.getId());
if (eventDispatcher != null) {
if (hasFocus) {
eventDispatcher.dispatchEvent(new FocusEvent(surfaceId, view.getId()));
} else {
eventDispatcher.dispatchEvent(new BlurEvent(surfaceId, view.getId()));
}
}
}
});
BaseVMFocusChangeListener focusChangeListener =
new BaseVMFocusChangeListener(view.getOnFocusChangeListener());
focusChangeListener.attach(view);
}
@Override
public void onDropViewInstance(@NonNull T view) {
super.onDropViewInstance(view);
@Nullable OnFocusChangeListener focusChangeListener = view.getOnFocusChangeListener();
if (focusChangeListener instanceof BaseVMFocusChangeListener) {
((BaseVMFocusChangeListener) focusChangeListener).detach(view);
}
}
// Currently, layout listener is only attached when transform or transformOrigin is set.
@@ -1052,4 +1041,49 @@ public abstract class BaseViewManager<T extends View, C extends LayoutShadowNode
}
// Please add new props to BaseViewManagerDelegate as well!
/**
* A helper class to keep track of the original focus change listener if one is set. This is
* especially helpful for views that are recycled so we can retain and restore the original
* listener upon recycling (onDropViewInstance).
*/
private class BaseVMFocusChangeListener<V extends View> implements OnFocusChangeListener {
private @Nullable OnFocusChangeListener mOriginalFocusChangeListener;
public BaseVMFocusChangeListener(@Nullable OnFocusChangeListener originalFocusChangeListener) {
mOriginalFocusChangeListener = originalFocusChangeListener;
}
public void attach(T view) {
view.setOnFocusChangeListener(this);
}
public void detach(T view) {
view.setOnFocusChangeListener(mOriginalFocusChangeListener);
}
@Override
public void onFocusChange(View view, boolean hasFocus) {
if (mOriginalFocusChangeListener != null) {
mOriginalFocusChangeListener.onFocusChange(view, hasFocus);
}
int surfaceId = UIManagerHelper.getSurfaceId(view.getContext());
if (surfaceId == View.NO_ID) {
return;
}
if (view.getContext() instanceof ThemedReactContext) {
ThemedReactContext themedReactContext = (ThemedReactContext) view.getContext();
@Nullable
EventDispatcher eventDispatcher =
UIManagerHelper.getEventDispatcherForReactTag(themedReactContext, view.getId());
if (eventDispatcher != null) {
if (hasFocus) {
eventDispatcher.dispatchEvent(new FocusEvent(surfaceId, view.getId()));
} else {
eventDispatcher.dispatchEvent(new BlurEvent(surfaceId, view.getId()));
}
}
}
}
}
}
@@ -7,9 +7,12 @@
package com.facebook.react.uimanager
import android.app.Activity
import android.content.Context
import android.util.DisplayMetrics
import android.view.WindowManager
import androidx.core.view.ViewCompat
import androidx.core.view.WindowInsetsCompat
import com.facebook.react.bridge.WritableMap
import com.facebook.react.bridge.WritableNativeMap
@@ -98,4 +101,14 @@ public object DisplayMetricsHolder {
putDouble("fontScale", fontScale)
putDouble("densityDpi", displayMetrics.densityDpi.toDouble())
}
internal fun getStatusBarHeightPx(activity: Activity?): Int {
val windowInsets = activity?.window?.decorView?.let(ViewCompat::getRootWindowInsets) ?: return 0
return windowInsets
.getInsets(
WindowInsetsCompat.Type.statusBars() or
WindowInsetsCompat.Type.navigationBars() or
WindowInsetsCompat.Type.displayCutout())
.top
}
}
@@ -27,12 +27,4 @@ internal object LayoutDirectionUtil {
YogaDirection.RTL -> View.LAYOUT_DIRECTION_RTL
else -> View.LAYOUT_DIRECTION_INHERIT
}
@JvmStatic
fun toYogaFromAndroid(direction: Int): YogaDirection =
when (direction) {
View.LAYOUT_DIRECTION_LTR -> YogaDirection.LTR
View.LAYOUT_DIRECTION_RTL -> YogaDirection.RTL
else -> YogaDirection.INHERIT
}
}
@@ -17,7 +17,6 @@ import android.view.View;
import android.view.ViewGroup;
import android.view.accessibility.AccessibilityEvent;
import android.widget.EditText;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.core.view.ViewCompat;
@@ -501,10 +500,6 @@ public class ReactAccessibilityDelegate extends ExploreByTouchHelper {
node.setContentDescription(mView.getContentDescription());
}
if (mView instanceof TextView && ((TextView) mView).getText() != null) {
node.setText(((TextView) mView).getText());
}
populateAccessibilityNodeInfo(mView, node);
node.setBoundsInParent(new Rect(0, 0, mView.getWidth(), mView.getHeight()));
} else {

Some files were not shown because too many files have changed in this diff Show More