Compare commits

...
Author SHA1 Message Date
Christian Falch a97e38029d Added missing define to target .reactRuntime
The target needs the HERMES_ENABLE_DEBUGGER flag in debug just like .reactHermes does.

This commit fixes this by adding the define.
2025-06-17 14:28:37 +02:00
Riccardo Cipolleschi abc8fe1c92 Fix nightly download of Hermes and react-native-dependencies (#52033)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/52033

This change fixes the download of the artefacts for the nightlies of Hermes and React Native Dependencies after we changed the publishing logic for Maven

## Changelog:
[Internal] -

Reviewed By: cortinico

Differential Revision: D76723289

fbshipit-source-id: 6b0ea6a6c35125e6fb03cecc6be893bd02abdad8
2025-06-16 03:32:46 -07:00
Riccardo Cipolleschi a6ea626255 Remove the option to use JSC from core (#51946)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/51946

This change simplified the setp disallowing to use JSC from core.
As a side effect, it simplified the setup by always falling back to hermes if the users decides not to use the third party JSC

## Changelog:
[iOS][Removed] - remove the option to use JSC from core

Reviewed By: cortinico

Differential Revision: D76342625

fbshipit-source-id: c925ab4fab1e171e289a1c5f75890c92da1b3f08
2025-06-16 02:36:45 -07:00
Janic Duplessis 1da608f6f1 Fix RNTester hermesc build issue on iOS (#51989)
Summary:
I am not sure exactly why, but I've been getting this error when running RNTester on iOS, when it tries to build hermesc from source. We're clearing the env using `env -i` which seems to cause the issue. If I add PATH to the env we set then it builds fine.

```
++ hermesc_dir_path=/Users/janicduplessis/Developer/react-native/packages/rn-tester/Pods/hermes-engine/build_host_hermesc
++ shift
++ jsi_path=/Users/janicduplessis/Developer/react-native/packages/rn-tester/Pods/../../react-native/ReactCommon/jsi
+++ xcode-select -p
++ SDKROOT=/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk
++ env -i SDKROOT=/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk /opt/homebrew/bin/cmake -S /Users/janicduplessis/Developer/react-native/packages/rn-tester/Pods/hermes-engine -B /Users/janicduplessis/Developer/react-native/packages/rn-tester/Pods/hermes-engine/build_host_hermesc -DJSI_DIR=/Users/janicduplessis/Developer/react-native/packages/rn-tester/Pods/../../react-native/ReactCommon/jsi
CMake Error: CMake was unable to find a build program corresponding to "Unix Makefiles".  CMAKE_MAKE_PROGRAM is not set.  You probably need to select a different build tool.
CMake Error: CMAKE_C_COMPILER not set, after EnableLanguage
CMake Error: CMAKE_CXX_COMPILER not set, after EnableLanguage
-- Configuring incomplete, errors occurred!
Command PhaseScriptExecution failed with a nonzero exit code
```

## Changelog:

[INTERNAL] [FIXED] - Fix RNTester hermesc build issue on iOS

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

Test Plan: Build RN tester locally

Reviewed By: cortinico

Differential Revision: D76606335

Pulled By: cipolleschi

fbshipit-source-id: f442b77aefb3afacd6d9fb1f3d515b8d63c526ba
2025-06-16 02:05:30 -07:00
Tim Yung 98f5a4e118 Fantom: Enable hermesParser in Metro Transform (#52021)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/52021

Enables the `hermesParser` option in Fantom tests.

Notably, this configures parsing with `hermes-parser` to use `reactRuntimeTarget: '19'`.

Changelog:
[Internal]

Reviewed By: robhogan

Differential Revision: D76641340

fbshipit-source-id: a2dcdbe8cab838481dd37c251d03d1e6fffdf346
2025-06-14 06:41:00 -07:00
Tim Yung 92af97591b RN: Align ReportFullyDrawnView Type Exports (#52020)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/52020

Aligns the type exports of `ReportFullyDrawnView` across platforms, so that they are resilient to any changes made to `View` itself.

Changelog:
[Internal]

Reviewed By: lunaleaps

Differential Revision: D76638685

fbshipit-source-id: 612b2bcd76e70751aec691a24f31beca453cea35
2025-06-13 19:26:26 -07:00
Tim Yung 30d8a153e6 RN: Upgrade to eslint-plugin-react-hooks@6.1.0-canary (#52016)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/52016

Upgrade the React Native monorep to use `eslint-plugin-react-hooks@6.1.0-canary`, which includes support for Flow's Component Syntax.

This does not affect production users of `eslint-config-react-native`.

Changelog:
[Internal]

Reviewed By: NickGerleman

Differential Revision: D76627448

fbshipit-source-id: 19e95e5d7f1bcd4fb6bead4e94d268d0c36a4817
2025-06-13 19:26:26 -07:00
Ramanpreet Nara 05a61e8161 Introduce main queue coordinator (#51425)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/51425

# Problem

React native's new architecture will allow components to do sync render/events. That means they'll makes synchronous dispatches from main thread to the js thread, to capture the runtime so that they can execute js on the main thread.

But, the js thread already as a bunch of synchronous calls to the main thread. So, if any of those js -> ui sync calls happen concurrently with a synchronous render, the application will deadlock.

This diff is an attempt to mitigate all those deadlocks.

## Context
How js execution from the main thread works:

* Main thread puts a block on the js thread, to capture the js runtime. Main thread is put to sleep.
* Js thread executes "runtime capture block". The runtime is captured for the main thread. The js thread is put to sleep.
* Main thread wakes up, noticing that the runtime is captured. It executes its js code with the captured runtime. Then, it releases the runtime, which wakes up the js thread. Both the main and js thread move on to other tasks.

How synchronous js -> main thread calls work:
* Js thread puts a ui block on the main queue.
* Js thread goes to sleep until that ui block executes on the main thread.

## Deadlock #1
**Main thread**: execute js now:
  * Main thread puts a block on the js queue, to capture the runtime.
 * Main thread then then goes to sleep, waiting for runtime to be captured

**JS thread**: execute ui code synchronously:
* Js thread schedules a block on the ui thread
* Js thread then goes to sleep, waiting for that block to execute.

**Result:** The application deadlocks

| {F1978009555} |  {F1978009612} |

![image](https://github.com/user-attachments/assets/325a62f4-d5b7-492d-a114-efb738556239)

## Deadlock #2
**JS thread**: execute ui code synchronously:
* Js thread schedules a block on the ui thread
* Js thread then goes to sleep waiting for that block to execute.

**Main thread**: execute js now:
* Main thread puts a block on the js queue, to capture the runtime.
* Main thread then then goes to sleep, waiting for runtime to be captured

**Result:** The application deadlocks

|  {F1978009690}  | {F1978009701} |

![image](https://github.com/user-attachments/assets/13a6ea17-a55d-453d-9291-d1c8007ecffa)

# Changes
This diff attempts to fix those deadlocks. How:
* In "execute ui code synchronously" (js thread):
   * Before going to sleep, the js thread schedules the ui work on the main queue, **and** it  posts the ui work to "execute js now".
* In "execute js now" (main thread):
   * This diff makes "execute js now" stateful: it keeps a "pending ui block."
   * Before capturing the runtime, the "execute js now" executes "pending ui work", if it exists.
   * While sleeping waiting for runtime capture, "execute js now" can wake up, and execute "pending ui work." It goes back to sleep afterwards, waiting for runtime capture.

## Mitigation: Deadlock #1
**Main thread**: execute js now:
* Main thread puts a block on the js queue, to capture the runtime.
* Main thread then then goes to sleep, waiting for runtime capture

**JS Thread**: execute ui code synchronously:
* Js thread puts its ui block on the ui queue.
* ***New***: Js thread also posts that ui block to "execute js now". Main thread was sleeping waiting for runtime to be captured. It now wakes up.
* Js thread goes to sleep.

The main thread wakes up in "execute js now":
* Main thread sees that a "pending ui block" is posted. It executes the "pending ui block." The block, also scheduled on the main thread, noops henceforth.
* Main thread goes back to sleep, waiting for runtime capture.
* The js thread wakes up, moves on to the next task.

**Result:** The runtime is captured by the main thread.

| {F1978010383} | {F1978010363} |  {F1978010371} |  {F1978010379} |

![image](https://github.com/user-attachments/assets/f53cb10c-7801-46be-934a-96af7d5f5fab)

## Mitigation: Deadlock #2
**JS Thread**: execute ui code synchronously:
* Js thread puts its ui block on the ui queue.
* ***New***: Js thread also posts that ui block to "execute js now". Main thread was sleeping waiting for runtime to be captured. It now wakes up.
* Js thread goes to sleep.

**Main thread**: execute js now
* Main thread sees that a "pending ui block" is posted. It executes the "pending ui block" immediately. The block, also scheduled on the main thread, noops henceforth.
* Js thread wakes up and moves onto the next task.

**Result:** Main thread captures the runtime.

|  {F1978010525}  |  {F1978010533} |  {F1978010542} |

![image](https://github.com/user-attachments/assets/9e0ca5ef-fab6-4a26-bcca-d79d36624d5d)

Changelog: [Internal]

Reviewed By: javache

Differential Revision: D74769326

fbshipit-source-id: 854b83ce4e482a4030dc711834ea6c5613091537
2025-06-13 16:32:03 -07:00
Nicola Corti feec8d0148 Hide JS FPS on performance overlay as not accurate (#52000)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/52000

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

The current JS FPS value is incorrect because the frame skipping logic hasn't been reimplemented in Fabric.
As we're looking into moving this into the performance panel, I've discussed with huntie
and agreed we'll just remove the value for now to don't show inaccurate informations.

Changelog:
[Android] [Changed] - Hide JS FPS on performance overlay as not accurate

Reviewed By: huntie

Differential Revision: D76590909

fbshipit-source-id: 90b0d9c84f9aefa9197243ebb57f4e86107d6c01
2025-06-13 14:08:01 -07:00
Andrew Datsenko 05521adbc8 Add react/featureflags (#52003)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/52003

Changelog: [Internal]
This is a react common dep check that we can build and run tester.

Reviewed By: christophpurrer

Differential Revision: D76531041

fbshipit-source-id: 0a43fdb91aa61f7e6461ff8a94ea6e2732b55dbb
2025-06-13 13:36:39 -07:00
Nicola Corti cf6569bc18 Cleanup and internalize FpsDebugFrameCallback (#51982)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/51982

This class should not be accessed externally. I'm making it internal.
On top of this, it was not fully reimplemented on NewArch so is not working consistently.

This is gonna break one library which is unmaintained and not properly udpated to work with NewArch
https://github.com/hannojg/react-native-performance-stats

Changelog:
[Android] [Breaking] - Cleanup and internalize FpsDebugFrameCallback

Reviewed By: huntie

Differential Revision: D76531175

fbshipit-source-id: 25598eb7c1ecf476b69bb6a2f2f8088a57b9fbc2
2025-06-13 12:23:05 -07:00
Mateo Guzmán c64f698e5f Migrate JavaMethodWrapper to Kotlin (#51930)
Summary:
Migrate com.facebook.react.bridge.JavaMethodWrapper to Kotlin.

## Changelog:

[INTERNAL] - Migrate com.facebook.react.bridge.JavaMethodWrapper to Kotlin

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

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

Rollback Plan:

Reviewed By: cortinico

Differential Revision: D76377903

Pulled By: alanleedev

fbshipit-source-id: 4df257639992304a6ff3ed9abf499d8ed0b6aac7
2025-06-13 11:40:56 -07:00
Mathieu Acthernoene b5be57cb76 Fix RNTester system bars background when edge-to-edge is enforced (#51929)
Summary:
This PR fixes RNTester system bars background color to match the app one (not solid black).

## Changelog:

- [Internal] [Changed] - Fix RNTester app system bars color when edge-to-edge is enforced

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

Test Plan:
https://github.com/user-attachments/assets/8be0b721-6514-408f-81cd-2106ae7a17c4

Rollback Plan:

Reviewed By: javache

Differential Revision: D76352950

Pulled By: alanleedev

fbshipit-source-id: 474a81564570764a597aa995a0677617263338be
2025-06-13 11:05:35 -07:00
Mateo Guzmán b0530f0abf Migrate ModuleHolder to Kotlin (#51997)
Summary:
Migrate com.facebook.react.bridge.ModuleHolder to Kotlin.

## Changelog:

[INTERNAL] - Migrate com.facebook.react.bridge.ModuleHolder to Kotlin

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

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

Reviewed By: javache

Differential Revision: D76591704

Pulled By: cortinico

fbshipit-source-id: adbf1375ae9999881ce75b7d73d8e0bb3a8a73f8
2025-06-13 10:46:44 -07:00
Arushi Kesarwani b417b0c2d5 Extract out FBReactNativeSpec's core components including Unimplemented from auto-generated registry (#51941)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/51941

Changelog:
[Android][Fixed] - Extract out FBReactNativeSpec's core components including Unimplemented from auto-generated registry

Extracting out `FBReactNativeSpec`'s core components including `UnimplementedNativeView` from auto-generated registry. Using this `libraryName` to skip merging those modules

Reviewed By: RSNara

Differential Revision: D76371796

fbshipit-source-id: 4cfee0fe80a661f159a5f17e0d4abc60f601ea74
2025-06-13 10:18:50 -07:00
Nicola Corti 7ec2839955 Update Nightly URL for newly published versions on central.sonatype.com (#52004)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/52004

This is necessary because the snapshots are now going to be published on a different repository:
central.sonatype.com.

Changelog:
[Internal] [Changed] -

Reviewed By: cipolleschi

Differential Revision: D76596802

fbshipit-source-id: 424fb1134e41502d53b76209fba325c895c79ba8
2025-06-13 10:09:47 -07:00
Mateo Guzmán f880bfd1ab Kotlin: clean up redundant visibility modifiers (1/2) (#51960)
Summary:
Static code analysis shows that there are several redundant visibility modifiers across the codebase. These are most likely remnants after making different classes internal.

## Changelog:

[INTERNAL] - Kotlin: clean up redundant visibility modifiers

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

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

Reviewed By: javache

Differential Revision: D76503015

Pulled By: cortinico

fbshipit-source-id: e60e7aa141fc35ca2fd76335fbee791c86589e4e
2025-06-13 08:42:42 -07:00
Christoph Purrer fed27e71f8 Use std::format (#51992)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/51992

changelog: [internal]

Reviewed By: javache

Differential Revision: D76486572

fbshipit-source-id: e0577c067d350c993cffbcb6efd5a240faeca5f6
2025-06-13 08:25:50 -07:00
Riccardo Cipolleschi 0fb0bd1ae7 Add headers to XCFramework (#52010)
Summary:
We found out that the XCFramework that is generated in CI is missing the headers.
This is happening because we run the setup script, the responsible to prepare the folder structure with the heaeders in the right place, only in the job that builds the slices. However, the headers are copied by the job that composes the XCFramework.

This change stores the header folder as an artifact in the build job and retrieves it in the compose job, so that the files are available to the XCFramework

## Changelog:
[Internal] -

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

Test Plan:
Check the generated artefact in CI
<img width="292" alt="Screenshot 2025-06-13 at 15 32 02" src="https://github.com/user-attachments/assets/437333da-5848-4657-a9b3-e87fc79c69b2" />

Reviewed By: cortinico

Differential Revision: D76599834

Pulled By: cipolleschi

fbshipit-source-id: 44d74b5f8df545a825ecfe3df2e1898effe41261
2025-06-13 08:12:30 -07:00
Nicola Corti 6b9d931a88 Move React Native publishing URLs to Central Portal (#51693)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/51693

This moves React Native to use the Central Portal URLs rather than the legacy OSSRH ones.
See https://github.com/gradle-nexus/publish-plugin for more context.

Changelog:
[Internal] [Changed] -

Reviewed By: cipolleschi

Differential Revision: D75673984

fbshipit-source-id: 1de6746809eed72f232eac0c3fb4d809c2046620
2025-06-13 03:30:14 -07:00
David Vacca e61daa831d Introduce parameter to customize libraryGenerators used in the codegen (#51991)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/51991

This diff introduces a new parameter to customize libraryGenerators used in the codegen, since I'm adding a default object, this diff shoulnd't change any behavior

changelog: [internal] internal

Reviewed By: christophpurrer

Differential Revision: D76472495

fbshipit-source-id: 50b9095c7c554e368f65e4c0b5539be0cca51a51
2025-06-12 22:22:20 -07:00
David Vacca a8386aa878 Prevent exporting internal objects of codegen (#51990)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/51990

In this diff I'm limiting visibility of internal objects of codegen, these objects are being exported but they are unused, let's avoid exporting them

changelog: [internal] internal

Reviewed By: christophpurrer

Differential Revision: D76470809

fbshipit-source-id: 0e168558d2d3211ab5a3a3de05e2495d7c1ae4f5
2025-06-12 22:22:20 -07:00
David Vacca e8b55a4456 Add flowTypes for codegen LIBRARY_GENERATORS (#51987)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/51987

In this diff I'm adding flowTypes for codegen LIBRARY_GENERATORS

changelog: [internal] internal

Reviewed By: huntie

Differential Revision: D76470808

fbshipit-source-id: 8e2bddeda1f9175fd25fee04f8fdd3cb7c7faa49
2025-06-12 22:22:20 -07:00
Alexander Klotz 70962ef3ed Added support for multiple widths with dashed and dotted borders on iOS (#51770)
Summary:
This change allows for dashed and dotted borders to have different widths for each of the sides on iOS. This issue was described in https://github.com/facebook/react-native/issues/51658. This allows for better dashed lines and moves the implementation of borders closer to how it is handled on web/android.
Resolves https://github.com/facebook/react-native/issues/51658
(related https://github.com/facebook/react-native/issues/39088)

## Changelog:

[IOS] [ADDED] - Add support for different borderWidths

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

Test Plan:
- yarn test
- yarn lint

Reviewed By: NickGerleman

Differential Revision: D76145887

Pulled By: jorge-cab

fbshipit-source-id: 3716e84799b44d2ff0994cc673a2172ee85bd9e6
2025-06-12 15:31:46 -07:00
generatedunixname89002005287564 46eab9c509 Fix CQS signal modernize-concat-nested-namespaces in xplat/js/react-native-github/packages/react-native/ReactCommon/react/renderer/graphics/platform/ios/react/renderer/graphics (#51983)
Summary: Pull Request resolved: https://github.com/facebook/react-native/pull/51983

Reviewed By: dtolnay

Differential Revision: D76494516

fbshipit-source-id: 399311ad4e1eadf6741926a19ce1919e73a1bdaa
2025-06-12 15:06:56 -07:00
Aswin Andro 50667eceb1 Publish top-level Flow types for react-native (#51908)
Summary:
FIXED Add index.js.flow to npm package files for Flow support

Currently, the distributed npm package for react-native does not include the index.js.flow file, which causes all exports to be typed as any when using Flow. This commit adds index.js.flow to the "files" array in package.json, ensuring Flow users receive proper type definitions out of the box. This addresses issues where type checking with Flow fails in React Native projects.

## Changelog:

[General][Added] Publish top-level Flow types for `react-native`

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

Reviewed By: huntie, necolas

Differential Revision: D76292301

Pulled By: robhogan

fbshipit-source-id: e56360d3f35af30ef160470181349aac1812e7c1
2025-06-12 14:28:14 -07:00
Nick Gerleman e82a677c79 Convert TextLayoutManager to Kotlin and Make Internal (#51966)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/51966

This starts off mechanically, but needed a couple changes:

1. Some null handling changes to `TextTransform` internals
2. We type MapBuffer keys as `Int` instead of `Short`, because Kotlin does not allow the implicit widening cast that Java does. I also made these internal
3. Some shifts around casting
4. Mark TextLayoutManager internal, and remove usages of `UnstableReactNativeAPI`

I verified that there were no usages of the Java side of TextLayoutManager throughout `react-native-libraries`, so marking TextLayoutManager internal is unlikely to break 3p libraries.

Changelog:
[Android][Breaking] - Make Java Side TextLayoutManager Internal

Reviewed By: javache

Differential Revision: D76444163

fbshipit-source-id: aabb1c498c731598559f0df5c12e0ecdc266339f
2025-06-12 13:50:13 -07:00
Oskar Kwaśniewski 42ca46b95c fix: add ImageSource type to TypeScript (#51969)
Summary:
This PR adds ImageSource type to ImageSource.d.ts which is defined in Flow:

https://github.com/facebook/react-native/blob/d6f29c8afd14b2cc835649db3c59ed2f0e685331/packages/react-native/Libraries/Image/ImageSource.js#L87-L90

But not in the TypeScript file.

## Changelog:

[GENERAL] [FIXED] - add ImageSource type to TypeScript

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

Test Plan: CI Green

Reviewed By: fabriziocucci

Differential Revision: D76532377

Pulled By: Abbondanzo

fbshipit-source-id: f1bbcd3b3fc07bb0f7e82f81ebaffedf9bc06148
2025-06-12 13:39:45 -07:00
87 changed files with 2602 additions and 2207 deletions
+17 -4
View File
@@ -23,8 +23,8 @@ jobs:
id: restore-ios-slice
uses: actions/cache/restore@v4
with:
path: packages/react-native/third-party/
key: v2-ios-core-${{ matrix.slice }}-${{ matrix.flavor }}-${{ hashFiles('packages/react-native/Package.swift') }}-${{ hashFiles('packages/react-native/scripts/ios-prebuild/setup.js') }}
key: v3-ios-core-${{ matrix.slice }}-${{ matrix.flavor }}-${{ hashFiles('packages/react-native/Package.swift') }}-${{ hashFiles('packages/react-native/scripts/ios-prebuild/setup.js') }}
path: packages/react-native/
- name: Setup node.js
if: steps.restore-ios-slice.outputs.cache-hit != 'true'
uses: ./.github/actions/setup-node
@@ -101,6 +101,12 @@ jobs:
# This is going to be replaced by a CLI script
cd packages/react-native
node scripts/ios-prebuild -b -f "${{ matrix.flavor }}" -p "${{ matrix.slice }}"
- name: Upload headers
uses: actions/upload-artifact@v4
with:
name: prebuild-ios-core-headers-${{ matrix.flavor }}-${{ matrix.slice }}
path:
packages/react-native/.build/headers
- name: Upload artifacts
uses: actions/upload-artifact@v4.3.4
with:
@@ -111,10 +117,10 @@ jobs:
uses: actions/cache/save@v4
if: ${{ github.ref == 'refs/heads/main' }} # To avoid that the cache explode
with:
key: v2-ios-core-${{ matrix.slice }}-${{ matrix.flavor }}-${{ hashFiles('packages/react-native/Package.swift') }}-${{ hashFiles('packages/react-native/scripts/ios-prebuild/setup.js') }}
enableCrossOsArchive: true
key: v3-ios-core-${{ matrix.slice }}-${{ matrix.flavor }}-${{ hashFiles('packages/react-native/Package.swift') }}-${{ hashFiles('packages/react-native/scripts/ios-prebuild/setup.js') }}
path: |
packages/react-native/.build/output/spm/${{ matrix.flavor }}/Build/Products
packages/react-native/.build/headers
compose-xcframework:
runs-on: macos-14
@@ -153,6 +159,13 @@ jobs:
pattern: prebuild-ios-core-slice-${{ matrix.flavor }}-*
path: packages/react-native/.build/output/spm/${{ matrix.flavor }}/Build/Products
merge-multiple: true
- name: Download headers
if: steps.restore-ios-xcframework.outputs.cache-hit != 'true'
uses: actions/download-artifact@v4
with:
pattern: prebuild-ios-core-headers-${{ matrix.flavor }}-*
path: packages/react-native/.build/headers
merge-multiple: true
- name: Setup Keychain
if: ${{ steps.restore-ios-xcframework.outputs.cache-hit != 'true' && env.REACT_ORG_CODE_SIGNING_P12_CERT != '' }}
uses: apple-actions/import-codesign-certs@v3 # https://github.com/marketplace/actions/import-code-signing-certificates
+2
View File
@@ -55,6 +55,8 @@ nexusPublishing {
sonatype {
username.set(sonatypeUsername)
password.set(sonatypePassword)
nexusUrl.set(uri("https://ossrh-staging-api.central.sonatype.com/service/local/"))
snapshotRepositoryUrl.set(uri("https://central.sonatype.com/repository/maven-snapshots/"))
}
}
}
+1 -1
View File
@@ -74,7 +74,6 @@
"eslint-plugin-jest": "^27.9.0",
"eslint-plugin-jsx-a11y": "^6.6.0",
"eslint-plugin-react": "^7.30.1",
"eslint-plugin-react-hooks": "^5.2.0",
"eslint-plugin-react-native": "^4.0.0",
"eslint-plugin-redundant-undefined": "^0.4.0",
"eslint-plugin-relay": "^1.8.3",
@@ -111,6 +110,7 @@
"ws": "^6.2.3"
},
"resolutions": {
"eslint-plugin-react-hooks": "6.1.0-canary-12bc60f5-20250613",
"react-is": "19.1.0"
}
}
@@ -37,7 +37,7 @@ internal object DependencyUtils {
}
}
// We add the snapshot for users on nightlies.
mavenRepoFromUrl("https://oss.sonatype.org/content/repositories/snapshots/") { repo ->
mavenRepoFromUrl("https://central.sonatype.com/repository/maven-snapshots/") { repo ->
repo.content { it.excludeGroup("org.webkit") }
}
repositories.mavenCentral { repo ->
@@ -45,7 +45,7 @@ class DependencyUtilsTest {
@Test
fun configureRepositories_containsSnapshotRepo() {
val repositoryURI = URI.create("https://oss.sonatype.org/content/repositories/snapshots/")
val repositoryURI = URI.create("https://central.sonatype.com/repository/maven-snapshots/")
val project = createProject()
configureRepositories(project)
@@ -176,7 +176,7 @@ class DependencyUtilsTest {
@Test
fun configureRepositories_snapshotRepoHasHigherPriorityThanMavenCentral() {
val repositoryURI = URI.create("https://oss.sonatype.org/content/repositories/snapshots/")
val repositoryURI = URI.create("https://central.sonatype.com/repository/maven-snapshots/")
val mavenCentralURI = URI.create("https://repo.maven.apache.org/maven2/")
val project = createProject()
+1
View File
@@ -13,6 +13,7 @@
export type PlatformType = 'iOS' | 'android';
export type SchemaType = $ReadOnly<{
libraryName?: string,
modules: $ReadOnly<{
[hasteModuleName: string]: ComponentSchema | NativeModuleSchema,
}>,
@@ -28,6 +28,11 @@ const argv = yargs
alias: 'exclude',
default: null,
})
.option('l', {
describe: 'Library name to use for schema generation',
alias: 'libraryName',
default: null,
})
.parseSync();
const [outfile, ...fileList] = argv._;
@@ -35,10 +40,12 @@ const platform: ?string = argv.platform;
const exclude: string = argv.exclude;
const excludeRegExp: ?RegExp =
exclude != null && exclude !== '' ? new RegExp(exclude) : null;
const libraryName: ?string = argv.libraryName;
combineSchemasInFileListAndWriteToFile(
fileList,
platform != null ? platform.toLowerCase() : platform,
outfile,
excludeRegExp,
libraryName,
);
@@ -21,8 +21,11 @@ const path = require('path');
const flowParser = new FlowParser();
const typescriptParser = new TypeScriptParser();
function combineSchemas(files: Array<string>): SchemaType {
return files.reduce(
function combineSchemas(
files: Array<string>,
libraryName: ?string,
): SchemaType {
const combined = files.reduce(
(merged, filename) => {
const contents = fs.readFileSync(filename, 'utf8');
@@ -46,6 +49,11 @@ function combineSchemas(files: Array<string>): SchemaType {
},
{modules: {}},
);
return {
libraryName: libraryName || '',
modules: combined.modules,
};
}
function expandDirectoriesIntoFiles(
@@ -74,13 +82,14 @@ function combineSchemasInFileList(
fileList: Array<string>,
platform: ?string,
exclude: ?RegExp,
libraryName: ?string,
): SchemaType {
const expandedFileList = expandDirectoriesIntoFiles(
fileList,
platform,
exclude,
);
const combined = combineSchemas(expandedFileList);
const combined = combineSchemas(expandedFileList, libraryName);
if (Object.keys(combined.modules).length === 0) {
console.error(
'No modules to process in combine-js-to-schema-cli. If this is unexpected, please check if you set up your NativeComponent correctly. See combine-js-to-schema.js for how codegen finds modules.',
@@ -94,8 +103,14 @@ function combineSchemasInFileListAndWriteToFile(
platform: ?string,
outfile: string,
exclude: ?RegExp,
libraryName: ?string,
): void {
const combined = combineSchemasInFileList(fileList, platform, exclude);
const combined = combineSchemasInFileList(
fileList,
platform,
exclude,
libraryName,
);
const formattedSchema = JSON.stringify(combined);
fs.writeFileSync(outfile, formattedSchema);
}
@@ -99,8 +99,15 @@ for (const file of schemaFiles) {
}
}
modules[specName] = module;
specNameToFile[specName] = file;
if (
module.type === 'Component' &&
schema.libraryName === 'FBReactNativeSpec'
) {
continue;
} else {
modules[specName] = module;
specNameToFile[specName] = file;
}
}
}
}
+18 -4
View File
@@ -73,6 +73,20 @@ const ALL_GENERATORS = {
generateViewConfigJs: generateViewConfigJs.generate,
};
type FilesOutput = Map<string, string>;
type GenerateFunction = (
libraryName: string,
schema: SchemaType,
packageName?: string,
assumeNonnull: boolean,
headerPrefix?: string,
) => FilesOutput;
type LibraryGeneratorsFunctions = $ReadOnly<{
[string]: Array<GenerateFunction>,
}>;
type LibraryOptions = $ReadOnly<{
libraryName: string,
schema: SchemaType,
@@ -80,6 +94,7 @@ type LibraryOptions = $ReadOnly<{
packageName?: string, // Some platforms have a notion of package, which should be configurable.
assumeNonnull: boolean,
useLocalIncludePaths?: boolean,
libraryGenerators?: LibraryGeneratorsFunctions,
}>;
type SchemasOptions = $ReadOnly<{
@@ -113,7 +128,7 @@ type SchemasConfig = $ReadOnly<{
test?: boolean,
}>;
const LIBRARY_GENERATORS = {
const LIBRARY_GENERATORS: LibraryGeneratorsFunctions = {
descriptors: [
generateComponentDescriptorCpp.generate,
generateComponentDescriptorH.generate,
@@ -231,8 +246,6 @@ function checkOrWriteFiles(
module.exports = {
allGenerators: ALL_GENERATORS,
libraryGenerators: LIBRARY_GENERATORS,
schemaGenerators: SCHEMAS_GENERATORS,
generate(
{
@@ -242,6 +255,7 @@ module.exports = {
packageName,
assumeNonnull,
useLocalIncludePaths,
libraryGenerators = LIBRARY_GENERATORS,
}: LibraryOptions,
{generators, test}: LibraryConfig,
): boolean {
@@ -278,7 +292,7 @@ module.exports = {
const generatedFiles: Array<CodeGenFile> = [];
for (const name of generators) {
for (const generator of LIBRARY_GENERATORS[name]) {
for (const generator of libraryGenerators[name]) {
generator(
libraryName,
schema,
@@ -13,15 +13,13 @@
#import <memory>
#if USE_HERMES
#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
#elif USE_THIRD_PARTY_JSC != 1
#import <React/JSCExecutorFactory.h>
#endif // USE_HERMES
#endif
#import <ReactCommon/RCTTurboModuleManager.h>
#import <jsireact/JSIExecutor.h>
@@ -145,16 +145,10 @@ std::unique_ptr<facebook::react::JSExecutorFactory> RCTAppSetupDefaultJsExecutor
}
[turboModuleManager installJSBindings:runtime];
};
#if USE_HERMES
#if USE_THIRD_PARTY_JSC != 1
return std::make_unique<facebook::react::HermesExecutorFactory>(
facebook::react::RCTJSIExecutorRuntimeInstaller(runtimeInstallerLambda));
#elif USE_THIRD_PARTY_JSC != 1
return std::make_unique<facebook::react::JSCExecutorFactory>(
facebook::react::RCTJSIExecutorRuntimeInstaller(runtimeInstallerLambda));
#else
throw std::runtime_error("No JSExecutorFactory specified.");
return nullptr;
#endif // USE_HERMES
#endif
}
std::unique_ptr<facebook::react::JSExecutorFactory> RCTAppSetupJsExecutorFactoryForOldArch(
@@ -169,14 +163,8 @@ std::unique_ptr<facebook::react::JSExecutorFactory> RCTAppSetupJsExecutorFactory
facebook::react::RuntimeSchedulerBinding::createAndInstallIfNeeded(runtime, runtimeScheduler);
}
};
#if USE_HERMES
#if USE_THIRD_PARTY_JSC != 1
return std::make_unique<facebook::react::HermesExecutorFactory>(
facebook::react::RCTJSIExecutorRuntimeInstaller(runtimeInstallerLambda));
#elif USE_THIRD_PARTY_JSC != 1
return std::make_unique<facebook::react::JSCExecutorFactory>(
facebook::react::RCTJSIExecutorRuntimeInstaller(runtimeInstallerLambda));
#else
throw std::runtime_error("No JSExecutorFactory specified.");
return nullptr;
#endif // USE_HERMES
#endif
}
@@ -9,10 +9,8 @@
#import <ReactCommon/RCTHost.h>
#import "RCTAppSetupUtils.h"
#import "RCTDependencyProvider.h"
#if USE_HERMES
#if USE_THIRD_PARTY_JSC != 1
#import <React/RCTHermesInstanceFactory.h>
#elif USE_THIRD_PARTY_JSC != 1
#import <React/RCTJscInstanceFactory.h>
#endif
#import <react/nativemodule/defaults/DefaultTurboModules.h>
@@ -45,12 +43,8 @@
- (JSRuntimeFactoryRef)createJSRuntimeFactory
{
#if USE_HERMES
#if USE_THIRD_PARTY_JSC != 1
return jsrt_create_hermes_factory();
#elif USE_THIRD_PARTY_JSC != 1
return jsrt_create_jsc_factory();
#else
return nullptr;
#endif
}
@@ -17,12 +17,8 @@ else
end
is_new_arch_enabled = ENV["RCT_NEW_ARCH_ENABLED"] != "0"
use_hermes = ENV['USE_HERMES'] == nil || ENV['USE_HERMES'] == '1'
new_arch_enabled_flag = (is_new_arch_enabled ? " -DRCT_NEW_ARCH_ENABLED=1" : "")
hermes_flag = (use_hermes ? " -DUSE_HERMES=1" : "")
use_third_party_jsc_flag = ENV['USE_THIRD_PARTY_JSC'] == '1' ? " -DUSE_THIRD_PARTY_JSC=1" : ""
other_cflags = "$(inherited) " + new_arch_enabled_flag + hermes_flag + use_third_party_jsc_flag
other_cflags = "$(inherited) " + new_arch_enabled_flag + js_engine_flags()
header_search_paths = [
"$(PODS_TARGET_SRCROOT)/../../ReactCommon",
@@ -31,7 +27,7 @@ header_search_paths = [
"$(PODS_ROOT)/Headers/Public/ReactCommon",
"$(PODS_ROOT)/Headers/Public/React-RCTFabric",
"$(PODS_ROOT)/Headers/Private/Yoga",
].concat(use_hermes ? [
].concat(use_hermes() ? [
"$(PODS_ROOT)/Headers/Public/React-hermes",
"$(PODS_ROOT)/Headers/Public/hermes-engine"
] : [])
@@ -66,7 +62,7 @@ Pod::Spec.new do |s|
s.dependency "React-CoreModules"
s.dependency "React-RCTFBReactNativeSpec"
s.dependency "React-defaultsnativemodule"
if use_hermes
if use_hermes()
s.dependency 'React-hermes'
end
@@ -50,7 +50,7 @@ Pod::Spec.new do |s|
add_dependency(s, "React-jsinspectorcdp", :framework_name => 'jsinspector_moderncdp')
add_dependency(s, "ReactCommon", :subspec => "turbomodule/core", :additional_framework_paths => ["react/nativemodule/core"])
if ENV["USE_HERMES"] == nil || ENV["USE_HERMES"] == "1"
if use_hermes()
s.dependency "hermes-engine"
end
@@ -72,3 +72,8 @@ export interface ImageURISource {
}
export type ImageRequireSource = number;
export type ImageSource =
| ImageRequireSource
| ImageURISource
| ReadonlyArray<ImageURISource>;
+4 -1
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
+6 -15
View File
@@ -16,10 +16,6 @@ else
source[:tag] = "v#{version}"
end
use_hermes = ENV['USE_HERMES'] == nil || ENV['USE_HERMES'] == '1'
use_hermes_flag = use_hermes ? "-DUSE_HERMES=1" : ""
use_third_party_jsc_flag = ENV['USE_THIRD_PARTY_JSC'] == '1' ? "-DUSE_THIRD_PARTY_JSC=1" : ""
header_subspecs = {
'CoreModulesHeaders' => 'React/CoreModules/**/*.h',
'RCTActionSheetHeaders' => 'Libraries/ActionSheetIOS/*.h',
@@ -35,7 +31,7 @@ header_subspecs = {
}
frameworks_search_paths = []
frameworks_search_paths << "\"$(PODS_CONFIGURATION_BUILD_DIR)/React-hermes\"" if use_hermes
frameworks_search_paths << "\"$(PODS_CONFIGURATION_BUILD_DIR)/React-hermes\"" if use_hermes()
header_search_paths = [
"$(PODS_TARGET_SRCROOT)/ReactCommon",
@@ -56,7 +52,7 @@ Pod::Spec.new do |s|
s.platforms = min_supported_versions
s.source = source
s.resource_bundle = { "RCTI18nStrings" => ["React/I18n/strings/*.lproj"]}
s.compiler_flags = use_hermes_flag + ' ' + use_third_party_jsc_flag
s.compiler_flags = js_engine_flags()
s.header_dir = "React"
s.weak_framework = "JavaScriptCore"
s.pod_target_xcconfig = {
@@ -80,13 +76,9 @@ Pod::Spec.new do |s|
"React/Inspector/**/*",
"React/Runtime/**/*",
]
# If we are using Hermes (the default is use hermes, so USE_HERMES can be nil), we don't have jsc installed
# So we have to exclude the JSCExecutorFactory
if use_hermes
exclude_files = exclude_files.append("React/CxxBridge/JSCExecutorFactory.{h,mm}")
elsif ENV['USE_THIRD_PARTY_JSC'] == '1'
exclude_files = exclude_files.append("React/CxxBridge/JSCExecutorFactory.{h,mm}")
end
# The default is use hermes, we don't have jsc installed
exclude_files = exclude_files.append("React/CxxBridge/JSCExecutorFactory.{h,mm}")
ss.exclude_files = exclude_files
ss.private_header_files = "React/Cxx*/*.h"
@@ -123,7 +115,7 @@ Pod::Spec.new do |s|
s.dependency "React-runtimescheduler"
s.dependency "Yoga"
if use_hermes
if use_hermes()
s.dependency "React-hermes"
end
@@ -136,7 +128,6 @@ Pod::Spec.new do |s|
add_dependency(s, "React-utils", :additional_framework_paths => ["react/utils/platform/ios"])
add_dependency(s, "RCTDeprecation")
depend_on_js_engine(s)
add_rn_third_party_dependencies(s)
end
+15 -2
View File
@@ -19,9 +19,12 @@
#import <CommonCrypto/CommonCrypto.h>
#import <React/RCTUtilsUIOverride.h>
#import <ReactCommon/RuntimeExecutorSyncUIThreadUtils.h>
#import "RCTAssert.h"
#import "RCTLog.h"
using namespace facebook::react;
NSString *const RCTErrorUnspecified = @"EUNSPECIFIED";
// Returns the Path of Home directory
@@ -314,7 +317,12 @@ void RCTUnsafeExecuteOnMainQueueSyncWithError(dispatch_block_t block, NSString *
return;
}
if (facebook::react::ReactNativeFeatureFlags::disableMainQueueSyncDispatchIOS()) {
if (ReactNativeFeatureFlags::enableMainQueueCoordinatorOnIOS()) {
unsafeExecuteOnMainThreadSync(block);
return;
}
if (ReactNativeFeatureFlags::disableMainQueueSyncDispatchIOS()) {
RCTLogError(@"RCTUnsafeExecuteOnMainQueueSync: %@", context);
}
@@ -341,7 +349,12 @@ static void RCTUnsafeExecuteOnMainQueueOnceSync(dispatch_once_t *onceToken, disp
return;
}
if (facebook::react::ReactNativeFeatureFlags::disableMainQueueSyncDispatchIOS()) {
if (ReactNativeFeatureFlags::enableMainQueueCoordinatorOnIOS()) {
unsafeExecuteOnMainThreadSync(block);
return;
}
if (ReactNativeFeatureFlags::disableMainQueueSyncDispatchIOS()) {
RCTLogError(@"RCTUnsafeExecuteOnMainQueueOnceSync: Sync dispatches to the main queue can deadlock React Native.");
}
@@ -45,10 +45,8 @@
#import <react/utils/FollyConvert.h>
#import <reactperflogger/BridgeNativeModulePerfLogger.h>
#if USE_HERMES
#if !defined(USE_HERMES) || USE_HERMES == 1
#import <reacthermes/HermesExecutorFactory.h>
#elif USE_THIRD_PARTY_JSC != 1
#import "JSCExecutorFactory.h"
#endif
#import "RCTJSIExecutorRuntimeInstaller.h"
@@ -471,12 +469,8 @@ struct RCTInstanceCallback : public InstanceCallback {
}
if (!executorFactory) {
auto installBindings = RCTJSIExecutorRuntimeInstaller(nullptr);
#if USE_HERMES
#if !defined(USE_HERMES) || USE_HERMES == 1
executorFactory = std::make_shared<HermesExecutorFactory>(installBindings);
#elif USE_THIRD_PARTY_JSC != 1
executorFactory = std::make_shared<JSCExecutorFactory>(installBindings);
#else
throw std::runtime_error("No JSExecutorFactory specified.");
#endif
}
} else {
@@ -1144,7 +1138,9 @@ RCT_NOT_IMPLEMENTED(-(instancetype)initWithBundleURL
/**
* Prevent super from calling setUp (that'd create another batchedBridge)
*/
- (void)setUp {}
- (void)setUp
{
}
- (Class)executorClass
{
@@ -58,15 +58,14 @@ Pod::Spec.new do |s|
add_dependency(s, "React-RuntimeCore")
add_dependency(s, "React-RuntimeApple")
if ENV["USE_HERMES"] == nil || ENV["USE_HERMES"] == "1"
if use_third_party_jsc()
s.exclude_files = ["RCTHermesInstanceFactory.{mm,h}", "RCTJscInstanceFactory.{mm,h}"]
else
s.dependency "hermes-engine"
add_dependency(s, "React-RuntimeHermes")
s.exclude_files = "RCTJscInstanceFactory.{h,mm}"
elsif ENV['USE_THIRD_PARTY_JSC'] == '1'
s.exclude_files = ["RCTHermesInstanceFactory.{mm,h}", "RCTJscInstanceFactory.{mm,h}"]
else
s.exclude_files = ["RCTHermesInstanceFactory.{mm,h}"]
end
depend_on_js_engine(s)
add_rn_third_party_dependencies(s)
end
@@ -35,6 +35,11 @@ typedef struct {
UIColor *right;
} RCTBorderColors;
/**
* Determine the largest border inset value.
*/
RCT_EXTERN CGFloat RCTMaxBorderInset(UIEdgeInsets borderInsets);
/**
* Determine if the border widths, colors and radii are all equal.
*/
@@ -10,6 +10,11 @@
static const CGFloat RCTViewBorderThreshold = 0.001;
CGFloat RCTMaxBorderInset(UIEdgeInsets borderInsets)
{
return MAX(MAX(borderInsets.top, borderInsets.left), MAX(borderInsets.bottom, borderInsets.right));
}
BOOL RCTBorderInsetsAreEqual(UIEdgeInsets borderInsets)
{
return ABS(borderInsets.left - borderInsets.right) < RCTViewBorderThreshold &&
@@ -415,8 +420,8 @@ static UIImage *RCTGetSolidBorderImage(
return image;
}
// Currently, the dashed / dotted implementation only supports a single colour +
// single width, as that's currently required and supported on Android.
// Currently, the dashed / dotted implementation only supports a single colour,
// as that's currently required and supported on Android.
//
// Supporting individual widths + colours on each side is possible by modifying
// the current implementation. The idea is that we will draw four different lines
@@ -486,12 +491,12 @@ static UIImage *RCTGetDashedOrDottedBorderImage(
{
NSCParameterAssert(borderStyle == RCTBorderStyleDashed || borderStyle == RCTBorderStyleDotted);
if (!RCTBorderColorsAreEqual(borderColors) || !RCTBorderInsetsAreEqual(borderInsets)) {
if (!RCTBorderColorsAreEqual(borderColors)) {
RCTLogWarn(@"Unsupported dashed / dotted border style");
return nil;
}
const CGFloat lineWidth = borderInsets.top;
const CGFloat lineWidth = RCTMaxBorderInset(borderInsets);
if (lineWidth <= 0.0) {
return nil;
}
@@ -519,6 +524,34 @@ static UIImage *RCTGetDashedOrDottedBorderImage(
CGPathRef path =
RCTPathCreateWithRoundedRect(pathRect, RCTGetCornerInsets(cornerRadii, UIEdgeInsetsZero), NULL, NO);
if (!RCTBorderInsetsAreEqual(borderInsets)) {
CGContextSaveGState(context);
{
// Create a path representing the full rect
CGMutablePathRef outerPath = CGPathCreateMutable();
CGPathAddRect(outerPath, NULL, rect);
CGRect insetRect = CGRectMake(
rect.origin.x + borderInsets.left,
rect.origin.y + borderInsets.top,
rect.size.width - borderInsets.left - borderInsets.right,
rect.size.height - borderInsets.top - borderInsets.bottom);
// The padding edge (inner border) radius is the outer border radius minus the corresponding border thickness
CGPathRef innerRoundedRect =
RCTPathCreateWithRoundedRect(insetRect, RCTGetCornerInsets(cornerRadii, borderInsets), NULL, NO);
// Add both paths to outerPath
CGPathAddPath(outerPath, NULL, innerRoundedRect);
// Clip using even-odd
CGContextAddPath(context, outerPath);
CGContextEOClip(context);
CGPathRelease(outerPath);
CGPathRelease(innerRoundedRect);
}
}
CGFloat dashLengths[2];
dashLengths[0] = dashLengths[1] = (borderStyle == RCTBorderStyleDashed ? 3 : 1) * lineWidth;
@@ -950,16 +950,16 @@ public abstract interface class com/facebook/react/bridge/MemoryPressureListener
public abstract fun handleMemoryPressure (I)V
}
public class com/facebook/react/bridge/ModuleHolder {
public final class com/facebook/react/bridge/ModuleHolder {
public fun <init> (Lcom/facebook/react/bridge/NativeModule;)V
public fun <init> (Lcom/facebook/react/module/model/ReactModuleInfo;Ljavax/inject/Provider;)V
public fun destroy ()V
public fun getCanOverrideExistingModule ()Z
public fun getClassName ()Ljava/lang/String;
public fun getModule ()Lcom/facebook/react/bridge/NativeModule;
public fun getName ()Ljava/lang/String;
public fun isCxxModule ()Z
public fun isTurboModule ()Z
public final fun destroy ()V
public final fun getCanOverrideExistingModule ()Z
public final fun getClassName ()Ljava/lang/String;
public final fun getModule ()Lcom/facebook/react/bridge/NativeModule;
public final fun getName ()Ljava/lang/String;
public final fun isCxxModule ()Z
public final fun isTurboModule ()Z
}
public final class com/facebook/react/bridge/ModuleSpec {
@@ -2747,36 +2747,6 @@ public final class com/facebook/react/modules/debug/DevSettingsModule : com/face
public final class com/facebook/react/modules/debug/DevSettingsModule$Companion {
}
public final class com/facebook/react/modules/debug/FpsDebugFrameCallback : android/view/Choreographer$FrameCallback {
public fun <init> (Lcom/facebook/react/bridge/ReactContext;)V
public fun doFrame (J)V
public final fun get4PlusFrameStutters ()I
public final fun getExpectedNumFrames ()I
public final fun getFps ()D
public final fun getFpsInfo (J)Lcom/facebook/react/modules/debug/FpsDebugFrameCallback$FpsInfo;
public final fun getJsFPS ()D
public final fun getNumFrames ()I
public final fun getNumJSFrames ()I
public final fun getTotalTimeMS ()I
public final fun reset ()V
public final fun start ()V
public final fun start (D)V
public static synthetic fun start$default (Lcom/facebook/react/modules/debug/FpsDebugFrameCallback;DILjava/lang/Object;)V
public final fun startAndRecordFpsAtEachFrame ()V
public final fun stop ()V
}
public final class com/facebook/react/modules/debug/FpsDebugFrameCallback$FpsInfo {
public fun <init> (IIIIDDI)V
public final fun getFps ()D
public final fun getJsFps ()D
public final fun getTotal4PlusFrameStutters ()I
public final fun getTotalExpectedFrames ()I
public final fun getTotalFrames ()I
public final fun getTotalJsFrames ()I
public final fun getTotalTimeMs ()I
}
public final class com/facebook/react/modules/debug/SourceCodeModule : com/facebook/fbreact/specs/NativeSourceCodeSpec {
public static final field Companion Lcom/facebook/react/modules/debug/SourceCodeModule$Companion;
public static final field NAME Ljava/lang/String;
@@ -6447,37 +6417,6 @@ public final class com/facebook/react/views/text/TextAttributes {
public fun toString ()Ljava/lang/String;
}
public class com/facebook/react/views/text/TextLayoutManager {
public static final field AS_KEY_BASE_ATTRIBUTES S
public static final field AS_KEY_CACHE_ID S
public static final field AS_KEY_FRAGMENTS S
public static final field AS_KEY_HASH S
public static final field AS_KEY_STRING S
public static final field FR_KEY_HEIGHT S
public static final field FR_KEY_IS_ATTACHMENT S
public static final field FR_KEY_REACT_TAG S
public static final field FR_KEY_STRING S
public static final field FR_KEY_TEXT_ATTRIBUTES S
public static final field FR_KEY_WIDTH S
public static final field PA_KEY_ADJUST_FONT_SIZE_TO_FIT S
public static final field PA_KEY_ELLIPSIZE_MODE S
public static final field PA_KEY_HYPHENATION_FREQUENCY S
public static final field PA_KEY_INCLUDE_FONT_PADDING S
public static final field PA_KEY_MAXIMUM_FONT_SIZE S
public static final field PA_KEY_MAX_NUMBER_OF_LINES S
public static final field PA_KEY_MINIMUM_FONT_SIZE S
public static final field PA_KEY_TEXT_ALIGN_VERTICAL S
public static final field PA_KEY_TEXT_BREAK_STRATEGY S
public fun <init> ()V
public static fun deleteCachedSpannableForTag (I)V
public static fun getOrCreateSpannableForText (Landroid/content/Context;Lcom/facebook/react/common/mapbuffer/MapBuffer;Lcom/facebook/react/views/text/ReactTextViewManagerCallback;)Landroid/text/Spannable;
public static fun getTextGravity (Lcom/facebook/react/common/mapbuffer/MapBuffer;Landroid/text/Spannable;I)I
public static fun isRTL (Lcom/facebook/react/common/mapbuffer/MapBuffer;)Z
public static fun measureLines (Landroid/content/Context;Lcom/facebook/react/common/mapbuffer/MapBuffer;Lcom/facebook/react/common/mapbuffer/MapBuffer;FF)Lcom/facebook/react/bridge/WritableArray;
public static fun measureText (Landroid/content/Context;Lcom/facebook/react/common/mapbuffer/MapBuffer;Lcom/facebook/react/common/mapbuffer/MapBuffer;FLcom/facebook/yoga/YogaMeasureMode;FLcom/facebook/yoga/YogaMeasureMode;Lcom/facebook/react/views/text/ReactTextViewManagerCallback;[F)J
public static fun setCachedSpannableForTag (ILandroid/text/Spannable;)V
}
public abstract interface class com/facebook/react/views/textinput/ContentSizeWatcher {
public abstract fun onLayout ()V
}
@@ -111,7 +111,7 @@ public class NativeAnimatedModule(reactContext: ReactApplicationContext?) :
private var valueMap: Array<BatchExecutionOpCodes>? = null
@JvmStatic
public fun fromId(id: Int): BatchExecutionOpCodes {
fun fromId(id: Int): BatchExecutionOpCodes {
val valueMapNonnull: Array<BatchExecutionOpCodes> =
valueMap ?: BatchExecutionOpCodes.values()
if (valueMap == null) {
@@ -1,413 +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.bridge;
import static com.facebook.infer.annotation.Assertions.assertNotNull;
import static com.facebook.systrace.Systrace.TRACE_TAG_REACT;
import androidx.annotation.Nullable;
import com.facebook.debug.holder.PrinterHolder;
import com.facebook.debug.tags.ReactDebugOverlayTags;
import com.facebook.infer.annotation.Assertions;
import com.facebook.react.common.annotations.internal.LegacyArchitecture;
import com.facebook.react.common.annotations.internal.LegacyArchitectureLogLevel;
import com.facebook.react.common.annotations.internal.LegacyArchitectureLogger;
import com.facebook.systrace.SystraceMessage;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
@LegacyArchitecture(logLevel = LegacyArchitectureLogLevel.ERROR)
class JavaMethodWrapper implements JavaModuleWrapper.NativeMethod {
static {
LegacyArchitectureLogger.assertLegacyArchitecture(
"JavaMethodWrapper", LegacyArchitectureLogLevel.ERROR);
}
private abstract static class ArgumentExtractor<T> {
public int getJSArgumentsNeeded() {
return 1;
}
public abstract @Nullable T extractArgument(
JSInstance jsInstance, ReadableArray jsArguments, int atIndex);
}
private static final ArgumentExtractor<Boolean> ARGUMENT_EXTRACTOR_BOOLEAN =
new ArgumentExtractor<Boolean>() {
@Override
public Boolean extractArgument(
JSInstance jsInstance, ReadableArray jsArguments, int atIndex) {
return jsArguments.getBoolean(atIndex);
}
};
private static final ArgumentExtractor<Double> ARGUMENT_EXTRACTOR_DOUBLE =
new ArgumentExtractor<Double>() {
@Override
public Double extractArgument(
JSInstance jsInstance, ReadableArray jsArguments, int atIndex) {
return jsArguments.getDouble(atIndex);
}
};
private static final ArgumentExtractor<Float> ARGUMENT_EXTRACTOR_FLOAT =
new ArgumentExtractor<Float>() {
@Override
public Float extractArgument(
JSInstance jsInstance, ReadableArray jsArguments, int atIndex) {
return (float) jsArguments.getDouble(atIndex);
}
};
private static final ArgumentExtractor<Integer> ARGUMENT_EXTRACTOR_INTEGER =
new ArgumentExtractor<Integer>() {
@Override
public Integer extractArgument(
JSInstance jsInstance, ReadableArray jsArguments, int atIndex) {
return (int) jsArguments.getDouble(atIndex);
}
};
private static final ArgumentExtractor<String> ARGUMENT_EXTRACTOR_STRING =
new ArgumentExtractor<String>() {
@Override
public String extractArgument(
JSInstance jsInstance, ReadableArray jsArguments, int atIndex) {
return jsArguments.getString(atIndex);
}
};
private static final ArgumentExtractor<ReadableArray> ARGUMENT_EXTRACTOR_ARRAY =
new ArgumentExtractor<ReadableArray>() {
@Override
public ReadableArray extractArgument(
JSInstance jsInstance, ReadableArray jsArguments, int atIndex) {
return jsArguments.getArray(atIndex);
}
};
private static final ArgumentExtractor<Dynamic> ARGUMENT_EXTRACTOR_DYNAMIC =
new ArgumentExtractor<Dynamic>() {
@Override
public Dynamic extractArgument(
JSInstance jsInstance, ReadableArray jsArguments, int atIndex) {
return DynamicFromArray.create(jsArguments, atIndex);
}
};
private static final ArgumentExtractor<ReadableMap> ARGUMENT_EXTRACTOR_MAP =
new ArgumentExtractor<ReadableMap>() {
@Override
public ReadableMap extractArgument(
JSInstance jsInstance, ReadableArray jsArguments, int atIndex) {
return jsArguments.getMap(atIndex);
}
};
private static final ArgumentExtractor<Callback> ARGUMENT_EXTRACTOR_CALLBACK =
new ArgumentExtractor<Callback>() {
@Override
public @Nullable Callback extractArgument(
JSInstance jsInstance, ReadableArray jsArguments, int atIndex) {
if (jsArguments.isNull(atIndex)) {
return null;
} else {
int id = (int) jsArguments.getDouble(atIndex);
return new com.facebook.react.bridge.CallbackImpl(jsInstance, id);
}
}
};
private static final ArgumentExtractor<Promise> ARGUMENT_EXTRACTOR_PROMISE =
new ArgumentExtractor<Promise>() {
@Override
public int getJSArgumentsNeeded() {
return 2;
}
@Override
public Promise extractArgument(
JSInstance jsInstance, ReadableArray jsArguments, int atIndex) {
Callback resolve =
ARGUMENT_EXTRACTOR_CALLBACK.extractArgument(jsInstance, jsArguments, atIndex);
Callback reject =
ARGUMENT_EXTRACTOR_CALLBACK.extractArgument(jsInstance, jsArguments, atIndex + 1);
return new PromiseImpl(resolve, reject);
}
};
private static final boolean DEBUG =
PrinterHolder.getPrinter().shouldDisplayLogMessage(ReactDebugOverlayTags.BRIDGE_CALLS);
private static char paramTypeToChar(Class paramClass) {
char tryCommon = commonTypeToChar(paramClass);
if (tryCommon != '\0') {
return tryCommon;
}
if (paramClass == Callback.class) {
return 'X';
} else if (paramClass == Promise.class) {
return 'P';
} else if (paramClass == ReadableMap.class) {
return 'M';
} else if (paramClass == ReadableArray.class) {
return 'A';
} else if (paramClass == Dynamic.class) {
return 'Y';
} else {
throw new RuntimeException("Got unknown param class: " + paramClass.getSimpleName());
}
}
private static char returnTypeToChar(Class returnClass) {
// Keep this in sync with MethodInvoker
char tryCommon = commonTypeToChar(returnClass);
if (tryCommon != '\0') {
return tryCommon;
}
if (returnClass == void.class) {
return 'v';
} else if (returnClass == WritableMap.class) {
return 'M';
} else if (returnClass == WritableArray.class) {
return 'A';
} else {
throw new RuntimeException("Got unknown return class: " + returnClass.getSimpleName());
}
}
private static char commonTypeToChar(Class typeClass) {
if (typeClass == boolean.class) {
return 'z';
} else if (typeClass == Boolean.class) {
return 'Z';
} else if (typeClass == int.class) {
return 'i';
} else if (typeClass == Integer.class) {
return 'I';
} else if (typeClass == double.class) {
return 'd';
} else if (typeClass == Double.class) {
return 'D';
} else if (typeClass == float.class) {
return 'f';
} else if (typeClass == Float.class) {
return 'F';
} else if (typeClass == String.class) {
return 'S';
} else {
return '\0';
}
}
private final Method mMethod;
private final Class[] mParameterTypes;
private final int mParamLength;
private final JavaModuleWrapper mModuleWrapper;
private String mType = BaseJavaModule.METHOD_TYPE_ASYNC;
private boolean mArgumentsProcessed = false;
private @Nullable ArgumentExtractor[] mArgumentExtractors;
private @Nullable String mSignature;
private @Nullable Object[] mArguments;
private @Nullable int mJSArgumentsNeeded;
public JavaMethodWrapper(JavaModuleWrapper module, Method method, boolean isSync) {
mModuleWrapper = module;
mMethod = method;
mMethod.setAccessible(true);
mParameterTypes = mMethod.getParameterTypes();
mParamLength = mParameterTypes.length;
if (isSync) {
mType = BaseJavaModule.METHOD_TYPE_SYNC;
} else if (mParamLength > 0 && (mParameterTypes[mParamLength - 1] == Promise.class)) {
mType = BaseJavaModule.METHOD_TYPE_PROMISE;
}
}
private void processArguments() {
if (mArgumentsProcessed) {
return;
}
SystraceMessage.beginSection(TRACE_TAG_REACT, "processArguments")
.arg("method", mModuleWrapper.getName() + "." + mMethod.getName())
.flush();
try {
mArgumentsProcessed = true;
mArgumentExtractors = buildArgumentExtractors(mParameterTypes);
mSignature =
buildSignature(mMethod, mParameterTypes, (mType.equals(BaseJavaModule.METHOD_TYPE_SYNC)));
// Since native methods are invoked from a message queue executed on a single thread, it is
// safe to allocate only one arguments object per method that can be reused across calls
mArguments = new Object[mParameterTypes.length];
mJSArgumentsNeeded = calculateJSArgumentsNeeded();
} finally {
SystraceMessage.endSection(TRACE_TAG_REACT).flush();
}
}
public Method getMethod() {
return mMethod;
}
public String getSignature() {
if (!mArgumentsProcessed) {
processArguments();
}
return assertNotNull(mSignature);
}
private String buildSignature(Method method, Class[] paramTypes, boolean isSync) {
StringBuilder builder = new StringBuilder(paramTypes.length + 2);
if (isSync) {
builder.append(returnTypeToChar(method.getReturnType()));
builder.append('.');
} else {
builder.append("v.");
}
for (int i = 0; i < paramTypes.length; i++) {
Class paramClass = paramTypes[i];
if (paramClass == Promise.class) {
Assertions.assertCondition(
i == paramTypes.length - 1, "Promise must be used as last parameter only");
}
builder.append(paramTypeToChar(paramClass));
}
return builder.toString();
}
private ArgumentExtractor[] buildArgumentExtractors(Class[] paramTypes) {
ArgumentExtractor[] argumentExtractors = new ArgumentExtractor[paramTypes.length];
for (int i = 0; i < paramTypes.length; i += argumentExtractors[i].getJSArgumentsNeeded()) {
Class argumentClass = paramTypes[i];
if (argumentClass == Boolean.class || argumentClass == boolean.class) {
argumentExtractors[i] = ARGUMENT_EXTRACTOR_BOOLEAN;
} else if (argumentClass == Integer.class || argumentClass == int.class) {
argumentExtractors[i] = ARGUMENT_EXTRACTOR_INTEGER;
} else if (argumentClass == Double.class || argumentClass == double.class) {
argumentExtractors[i] = ARGUMENT_EXTRACTOR_DOUBLE;
} else if (argumentClass == Float.class || argumentClass == float.class) {
argumentExtractors[i] = ARGUMENT_EXTRACTOR_FLOAT;
} else if (argumentClass == String.class) {
argumentExtractors[i] = ARGUMENT_EXTRACTOR_STRING;
} else if (argumentClass == Callback.class) {
argumentExtractors[i] = ARGUMENT_EXTRACTOR_CALLBACK;
} else if (argumentClass == Promise.class) {
argumentExtractors[i] = ARGUMENT_EXTRACTOR_PROMISE;
Assertions.assertCondition(
i == paramTypes.length - 1, "Promise must be used as last parameter only");
} else if (argumentClass == ReadableMap.class) {
argumentExtractors[i] = ARGUMENT_EXTRACTOR_MAP;
} else if (argumentClass == ReadableArray.class) {
argumentExtractors[i] = ARGUMENT_EXTRACTOR_ARRAY;
} else if (argumentClass == Dynamic.class) {
argumentExtractors[i] = ARGUMENT_EXTRACTOR_DYNAMIC;
} else {
throw new RuntimeException("Got unknown argument class: " + argumentClass.getSimpleName());
}
}
return argumentExtractors;
}
private int calculateJSArgumentsNeeded() {
int n = 0;
for (ArgumentExtractor extractor : assertNotNull(mArgumentExtractors)) {
n += extractor.getJSArgumentsNeeded();
}
return n;
}
private String getAffectedRange(int startIndex, int jsArgumentsNeeded) {
return jsArgumentsNeeded > 1
? "" + startIndex + "-" + (startIndex + jsArgumentsNeeded - 1)
: "" + startIndex;
}
@Override
public void invoke(JSInstance jsInstance, ReadableArray parameters) {
String traceName = mModuleWrapper.getName() + "." + mMethod.getName();
SystraceMessage.beginSection(TRACE_TAG_REACT, "callJavaModuleMethod")
.arg("method", traceName)
.flush();
if (DEBUG) {
PrinterHolder.getPrinter()
.logMessage(
ReactDebugOverlayTags.BRIDGE_CALLS,
"JS->Java: %s.%s()",
mModuleWrapper.getName(),
mMethod.getName());
}
try {
if (!mArgumentsProcessed) {
processArguments();
}
if (mArguments == null || mArgumentExtractors == null) {
throw new Error("processArguments failed");
}
if (mJSArgumentsNeeded != parameters.size()) {
throw new NativeArgumentsParseException(
traceName + " got " + parameters.size() + " arguments, expected " + mJSArgumentsNeeded);
}
int i = 0, jsArgumentsConsumed = 0;
try {
for (; i < mArgumentExtractors.length; i++) {
mArguments[i] =
mArgumentExtractors[i].extractArgument(jsInstance, parameters, jsArgumentsConsumed);
jsArgumentsConsumed += mArgumentExtractors[i].getJSArgumentsNeeded();
}
} catch (UnexpectedNativeTypeException | NullPointerException e) {
throw new NativeArgumentsParseException(
e.getMessage()
+ " (constructing arguments for "
+ traceName
+ " at argument index "
+ getAffectedRange(
jsArgumentsConsumed, mArgumentExtractors[i].getJSArgumentsNeeded())
+ ")",
e);
}
try {
mMethod.invoke(mModuleWrapper.getModule(), mArguments);
} catch (IllegalArgumentException | IllegalAccessException e) {
throw new RuntimeException(createInvokeExceptionMessage(traceName), e);
} catch (InvocationTargetException ite) {
// Exceptions thrown from native module calls end up wrapped in InvocationTargetException
// which just make traces harder to read and bump out useful information
if (ite.getCause() instanceof RuntimeException) {
throw (RuntimeException) ite.getCause();
}
throw new RuntimeException(createInvokeExceptionMessage(traceName), ite);
}
} finally {
SystraceMessage.endSection(TRACE_TAG_REACT).flush();
}
}
/**
* Makes it easier to determine the cause of an error invoking a native method from Javascript
* code by adding the function name.
*/
private static String createInvokeExceptionMessage(String traceName) {
return "Could not invoke " + traceName;
}
/**
* Determines how the method is exported in JavaScript: METHOD_TYPE_ASYNC for regular methods
* METHOD_TYPE_PROMISE for methods that return a promise object to the caller. METHOD_TYPE_SYNC
* for sync methods
*/
@Override
public String getType() {
return mType;
}
}
@@ -0,0 +1,399 @@
/*
* 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.bridge
import com.facebook.debug.holder.PrinterHolder
import com.facebook.debug.tags.ReactDebugOverlayTags
import com.facebook.react.common.annotations.internal.LegacyArchitecture
import com.facebook.react.common.annotations.internal.LegacyArchitectureLogLevel
import com.facebook.react.common.annotations.internal.LegacyArchitectureLogger
import com.facebook.systrace.Systrace.TRACE_TAG_REACT
import com.facebook.systrace.SystraceMessage
import java.lang.reflect.InvocationTargetException
import java.lang.reflect.Method
@LegacyArchitecture(logLevel = LegacyArchitectureLogLevel.ERROR)
internal class JavaMethodWrapper(
private val moduleWrapper: JavaModuleWrapper,
val method: Method,
isSync: Boolean
) : JavaModuleWrapper.NativeMethod {
private abstract class ArgumentExtractor<T> {
open fun getJSArgumentsNeeded(): Int = 1
abstract fun extractArgument(
jsInstance: JSInstance,
jsArguments: ReadableArray,
atIndex: Int
): T?
}
private val parameterTypes: Array<Class<*>>
private val paramLength: Int
/**
* Determines how the method is exported in JavaScript: METHOD_TYPE_ASYNC for regular methods
* METHOD_TYPE_PROMISE for methods that return a promise object to the caller. METHOD_TYPE_SYNC
* for sync methods
*/
override var type: String = BaseJavaModule.METHOD_TYPE_ASYNC
private var argumentsProcessed = false
private var argumentExtractors: Array<ArgumentExtractor<*>>? = null
private var internalSignature: String? = null
private var arguments: Array<Any?>? = null
private var jsArgumentsNeeded = 0
init {
method.isAccessible = true
parameterTypes = method.parameterTypes
paramLength = parameterTypes.size
if (isSync) {
type = BaseJavaModule.METHOD_TYPE_SYNC
} else if (paramLength > 0 && (parameterTypes[paramLength - 1] == Promise::class.java)) {
type = BaseJavaModule.METHOD_TYPE_PROMISE
}
}
private fun processArguments() {
if (argumentsProcessed) {
return
}
SystraceMessage.beginSection(TRACE_TAG_REACT, "processArguments")
.arg("method", moduleWrapper.name + "." + method.name)
.flush()
try {
argumentsProcessed = true
argumentExtractors = buildArgumentExtractors(parameterTypes)
internalSignature =
buildSignature(method, parameterTypes, (type == BaseJavaModule.METHOD_TYPE_SYNC))
// Since native methods are invoked from a message queue executed on a single thread, it is
// safe to allocate only one arguments object per method that can be reused across calls
arguments = arrayOfNulls(parameterTypes.size)
jsArgumentsNeeded = calculateJSArgumentsNeeded()
} finally {
SystraceMessage.endSection(TRACE_TAG_REACT).flush()
}
}
val signature: String?
get() {
if (!argumentsProcessed) {
processArguments()
}
return checkNotNull(internalSignature)
}
private fun buildSignature(method: Method, paramTypes: Array<Class<*>>, isSync: Boolean): String =
buildString(paramTypes.size + 2) {
if (isSync) {
append(returnTypeToChar(method.returnType))
append('.')
} else {
append("v.")
}
for (i in paramTypes.indices) {
val paramClass = paramTypes[i]
if (paramClass == Promise::class.java) {
check(i == paramTypes.size - 1) { "Promise must be used as last parameter only" }
}
append(paramTypeToChar(paramClass))
}
}
private fun buildArgumentExtractors(paramTypes: Array<Class<*>>): Array<ArgumentExtractor<*>> {
val argumentExtractors = arrayOfNulls<ArgumentExtractor<*>>(paramTypes.size)
var i = 0
while (i < paramTypes.size) {
val argumentClass = paramTypes[i]
val extractor: ArgumentExtractor<*> =
when (argumentClass) {
Boolean::class.javaObjectType,
Boolean::class.javaPrimitiveType -> ARGUMENT_EXTRACTOR_BOOLEAN
Int::class.javaObjectType,
Int::class.javaPrimitiveType -> ARGUMENT_EXTRACTOR_INTEGER
Double::class.javaObjectType,
Double::class.javaPrimitiveType -> ARGUMENT_EXTRACTOR_DOUBLE
Float::class.javaObjectType,
Float::class.javaPrimitiveType -> ARGUMENT_EXTRACTOR_FLOAT
String::class.java -> ARGUMENT_EXTRACTOR_STRING
Callback::class.java -> ARGUMENT_EXTRACTOR_CALLBACK
Promise::class.java -> {
check(i == paramTypes.size - 1) { "Promise must be used as last parameter only" }
ARGUMENT_EXTRACTOR_PROMISE
}
ReadableMap::class.java -> ARGUMENT_EXTRACTOR_MAP
ReadableArray::class.java -> ARGUMENT_EXTRACTOR_ARRAY
Dynamic::class.java -> ARGUMENT_EXTRACTOR_DYNAMIC
else ->
throw RuntimeException("Got unknown argument class: ${argumentClass.simpleName}")
}
argumentExtractors[i] = extractor
i += extractor.getJSArgumentsNeeded()
}
return argumentExtractors.requireNoNulls()
}
private fun calculateJSArgumentsNeeded(): Int {
var n = 0
for (extractor in checkNotNull(argumentExtractors)) {
n += extractor.getJSArgumentsNeeded()
}
return n
}
private fun getAffectedRange(startIndex: Int, jsArgumentsNeeded: Int): String =
if (jsArgumentsNeeded > 1) {
"$startIndex-${startIndex + jsArgumentsNeeded - 1}"
} else {
"$startIndex"
}
override fun invoke(jsInstance: JSInstance, parameters: ReadableArray) {
val traceName = moduleWrapper.name + "." + method.name
SystraceMessage.beginSection(TRACE_TAG_REACT, "callJavaModuleMethod")
.arg("method", traceName)
.flush()
if (DEBUG) {
PrinterHolder.printer.logMessage(
ReactDebugOverlayTags.BRIDGE_CALLS, "JS->Java: %s.%s()", moduleWrapper.name, method.name)
}
try {
if (!argumentsProcessed) {
processArguments()
}
val validatedArguments =
requireNotNull(arguments) { "processArguments failed: 'arguments' is null." }
val validatedArgumentExtractors =
requireNotNull(argumentExtractors) {
"processArguments failed: 'argumentExtractors' is null."
}
if (jsArgumentsNeeded != parameters.size()) {
throw NativeArgumentsParseException(
"$traceName got ${parameters.size()} arguments, expected $jsArgumentsNeeded")
}
var i = 0
var jsArgumentsConsumed = 0
try {
while (i < validatedArgumentExtractors.size) {
validatedArguments[i] =
validatedArgumentExtractors[i].extractArgument(
jsInstance, parameters, jsArgumentsConsumed)
jsArgumentsConsumed += validatedArgumentExtractors[i].getJSArgumentsNeeded()
i++
}
} catch (e: UnexpectedNativeTypeException) {
throw NativeArgumentsParseException(
"${e.message} (constructing arguments for $traceName at argument index ${
getAffectedRange(
jsArgumentsConsumed,
validatedArgumentExtractors[i].getJSArgumentsNeeded()
)
})",
e)
} catch (e: NullPointerException) {
throw NativeArgumentsParseException(
"${e.message} (constructing arguments for $traceName at argument index ${
getAffectedRange(
jsArgumentsConsumed,
validatedArgumentExtractors[i].getJSArgumentsNeeded()
)
})",
e)
}
try {
method.invoke(moduleWrapper.module, *validatedArguments)
} catch (e: IllegalArgumentException) {
throw RuntimeException(createInvokeExceptionMessage(traceName), e)
} catch (e: IllegalAccessException) {
throw RuntimeException(createInvokeExceptionMessage(traceName), e)
} catch (e: InvocationTargetException) {
// Exceptions thrown from native module calls end up wrapped in InvocationTargetException
// which just make traces harder to read and bump out useful information
if (e.cause is RuntimeException) {
throw (e.cause as RuntimeException)
}
throw RuntimeException(createInvokeExceptionMessage(traceName), e)
}
} finally {
SystraceMessage.endSection(TRACE_TAG_REACT).flush()
}
}
companion object {
init {
LegacyArchitectureLogger.assertLegacyArchitecture(
"JavaMethodWrapper", LegacyArchitectureLogLevel.ERROR)
}
private val ARGUMENT_EXTRACTOR_BOOLEAN: ArgumentExtractor<Boolean> =
object : ArgumentExtractor<Boolean>() {
override fun extractArgument(
jsInstance: JSInstance,
jsArguments: ReadableArray,
atIndex: Int
): Boolean = jsArguments.getBoolean(atIndex)
}
private val ARGUMENT_EXTRACTOR_DOUBLE: ArgumentExtractor<Double> =
object : ArgumentExtractor<Double>() {
override fun extractArgument(
jsInstance: JSInstance,
jsArguments: ReadableArray,
atIndex: Int
): Double = jsArguments.getDouble(atIndex)
}
private val ARGUMENT_EXTRACTOR_FLOAT: ArgumentExtractor<Float> =
object : ArgumentExtractor<Float>() {
override fun extractArgument(
jsInstance: JSInstance,
jsArguments: ReadableArray,
atIndex: Int
): Float = jsArguments.getDouble(atIndex).toFloat()
}
private val ARGUMENT_EXTRACTOR_INTEGER: ArgumentExtractor<Int> =
object : ArgumentExtractor<Int>() {
override fun extractArgument(
jsInstance: JSInstance,
jsArguments: ReadableArray,
atIndex: Int
): Int = jsArguments.getDouble(atIndex).toInt()
}
private val ARGUMENT_EXTRACTOR_STRING: ArgumentExtractor<String> =
object : ArgumentExtractor<String>() {
override fun extractArgument(
jsInstance: JSInstance,
jsArguments: ReadableArray,
atIndex: Int
): String? = jsArguments.getString(atIndex)
}
private val ARGUMENT_EXTRACTOR_ARRAY: ArgumentExtractor<ReadableArray> =
object : ArgumentExtractor<ReadableArray>() {
override fun extractArgument(
jsInstance: JSInstance,
jsArguments: ReadableArray,
atIndex: Int
): ReadableArray? = jsArguments.getArray(atIndex)
}
private val ARGUMENT_EXTRACTOR_DYNAMIC: ArgumentExtractor<Dynamic> =
object : ArgumentExtractor<Dynamic>() {
override fun extractArgument(
jsInstance: JSInstance,
jsArguments: ReadableArray,
atIndex: Int
): Dynamic = DynamicFromArray.create(jsArguments, atIndex)
}
private val ARGUMENT_EXTRACTOR_MAP: ArgumentExtractor<ReadableMap> =
object : ArgumentExtractor<ReadableMap>() {
override fun extractArgument(
jsInstance: JSInstance,
jsArguments: ReadableArray,
atIndex: Int
): ReadableMap? = jsArguments.getMap(atIndex)
}
private val ARGUMENT_EXTRACTOR_CALLBACK: ArgumentExtractor<Callback> =
object : ArgumentExtractor<Callback>() {
override fun extractArgument(
jsInstance: JSInstance,
jsArguments: ReadableArray,
atIndex: Int
): Callback? =
if (jsArguments.isNull(atIndex)) {
null
} else {
val id = jsArguments.getDouble(atIndex).toInt()
CallbackImpl(jsInstance, id)
}
}
private val ARGUMENT_EXTRACTOR_PROMISE: ArgumentExtractor<Promise> =
object : ArgumentExtractor<Promise>() {
override fun getJSArgumentsNeeded(): Int = 2
override fun extractArgument(
jsInstance: JSInstance,
jsArguments: ReadableArray,
atIndex: Int
): Promise {
val resolve =
ARGUMENT_EXTRACTOR_CALLBACK.extractArgument(jsInstance, jsArguments, atIndex)
val reject =
ARGUMENT_EXTRACTOR_CALLBACK.extractArgument(jsInstance, jsArguments, atIndex + 1)
return PromiseImpl(resolve, reject)
}
}
private val DEBUG =
PrinterHolder.printer.shouldDisplayLogMessage(ReactDebugOverlayTags.BRIDGE_CALLS)
private fun paramTypeToChar(paramClass: Class<*>): Char {
val tryCommon = commonTypeToChar(paramClass)
if (tryCommon != '\u0000') {
return tryCommon
}
return when (paramClass) {
Callback::class.java -> 'X'
Promise::class.java -> 'P'
ReadableMap::class.java -> 'M'
ReadableArray::class.java -> 'A'
Dynamic::class.java -> 'Y'
else -> throw RuntimeException("Got unknown param class: ${paramClass.simpleName}")
}
}
private fun returnTypeToChar(returnClass: Class<*>): Char {
// Keep this in sync with MethodInvoker
val tryCommon = commonTypeToChar(returnClass)
if (tryCommon != '\u0000') {
return tryCommon
}
return when (returnClass) {
Void.TYPE -> 'v'
WritableMap::class.java -> 'M'
WritableArray::class.java -> 'A'
else -> throw RuntimeException("Got unknown return class: ${returnClass.simpleName}")
}
}
private fun commonTypeToChar(typeClass: Class<*>): Char {
return when (typeClass) {
Boolean::class.javaPrimitiveType -> 'z'
Boolean::class.javaObjectType -> 'Z'
Int::class.javaPrimitiveType -> 'i'
Int::class.javaObjectType -> 'I'
Double::class.javaPrimitiveType -> 'd'
Double::class.javaObjectType -> 'D'
Float::class.javaPrimitiveType -> 'f'
Float::class.javaObjectType -> 'F'
String::class.java -> 'S'
else -> '\u0000'
}
}
/**
* Makes it easier to determine the cause of an error invoking a native method from Javascript
* code by adding the function name.
*/
private fun createInvokeExceptionMessage(traceName: String): String =
"Could not invoke $traceName"
}
}
@@ -67,7 +67,7 @@ public class JavaScriptModuleRegistry {
return name ?: getJSModuleName(moduleInterface).also { name = it }
}
public override fun invoke(proxy: Any, method: Method, args: Array<Any?>?): Any? {
override fun invoke(proxy: Any, method: Method, args: Array<Any?>?): Any? {
val jsArgs = if (args != null) Arguments.fromJavaArgs(args) else WritableNativeArray()
catalystInstance.callFunction(getJSModuleName(), method.name, jsArgs)
return null
@@ -1,247 +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.bridge;
import static com.facebook.infer.annotation.Assertions.assertNotNull;
import static com.facebook.react.bridge.ReactMarkerConstants.CREATE_MODULE_END;
import static com.facebook.react.bridge.ReactMarkerConstants.CREATE_MODULE_START;
import static com.facebook.systrace.Systrace.TRACE_TAG_REACT;
import androidx.annotation.GuardedBy;
import androidx.annotation.Nullable;
import com.facebook.common.logging.FLog;
import com.facebook.debug.holder.PrinterHolder;
import com.facebook.debug.tags.ReactDebugOverlayTags;
import com.facebook.infer.annotation.Assertions;
import com.facebook.infer.annotation.Nullsafe;
import com.facebook.proguard.annotations.DoNotStrip;
import com.facebook.react.common.ReactConstants;
import com.facebook.react.module.model.ReactModuleInfo;
import com.facebook.systrace.SystraceMessage;
import java.util.concurrent.atomic.AtomicInteger;
import javax.inject.Provider;
/**
* Holder to enable us to lazy create native modules.
*
* <p>This works by taking a provider instead of an instance, when it is first required we'll create
* and initialize it. Initialization currently always happens on the UI thread but this is due to
* change for performance reasons.
*
* <p>Lifecycle events via a {@link LifecycleEventListener} will still always happen on the UI
* thread.
*/
@Nullsafe(Nullsafe.Mode.LOCAL)
@DoNotStrip
public class ModuleHolder {
private static final AtomicInteger sInstanceKeyCounter = new AtomicInteger(1);
private final int mInstanceKey = sInstanceKeyCounter.getAndIncrement();
private final String mName;
private final ReactModuleInfo mReactModuleInfo;
private @Nullable Provider<? extends NativeModule> mProvider;
// Outside of the constructor, these should only be checked or set when synchronized on this
private @Nullable @GuardedBy("this") NativeModule mModule;
// These are used to communicate phases of creation and initialization across threads
private @GuardedBy("this") boolean mInitializable;
private @GuardedBy("this") boolean mIsCreating;
private @GuardedBy("this") boolean mIsInitializing;
public ModuleHolder(ReactModuleInfo moduleInfo, Provider<? extends NativeModule> provider) {
mName = moduleInfo.name();
mProvider = provider;
mReactModuleInfo = moduleInfo;
if (moduleInfo.needsEagerInit()) {
mModule = create();
}
}
public ModuleHolder(NativeModule nativeModule) {
mName = nativeModule.getName();
mReactModuleInfo =
new ReactModuleInfo(
nativeModule.getName(),
nativeModule.getClass().getSimpleName(),
nativeModule.canOverrideExistingModule(),
true,
CxxModuleWrapper.class.isAssignableFrom(nativeModule.getClass()),
ReactModuleInfo.classIsTurboModule(nativeModule.getClass()));
mModule = nativeModule;
PrinterHolder.getPrinter()
.logMessage(ReactDebugOverlayTags.NATIVE_MODULE, "NativeModule init: %s", mName);
}
/*
* Checks if mModule has been created, and if so tries to initialize the module unless another
* thread is already doing the initialization.
* If mModule has not been created, records that initialization is needed
*/
/* package */ void markInitializable() {
boolean shouldInitializeNow = false;
NativeModule module = null;
synchronized (this) {
mInitializable = true;
if (mModule != null) {
Assertions.assertCondition(!mIsInitializing);
shouldInitializeNow = true;
module = mModule;
}
}
if (shouldInitializeNow) {
Assertions.assertNotNull(module);
doInitialize(module);
}
}
/* package */ synchronized boolean hasInstance() {
return mModule != null;
}
public synchronized void destroy() {
if (mModule != null) {
mModule.invalidate();
}
}
@DoNotStrip
public String getName() {
return mName;
}
public boolean getCanOverrideExistingModule() {
return mReactModuleInfo.canOverrideExistingModule();
}
public boolean isTurboModule() {
return mReactModuleInfo.isTurboModule();
}
public boolean isCxxModule() {
return mReactModuleInfo.isCxxModule();
}
public String getClassName() {
return mReactModuleInfo.className();
}
@DoNotStrip
public NativeModule getModule() {
NativeModule module;
boolean shouldCreate = false;
synchronized (this) {
if (mModule != null) {
return mModule;
// if mModule has not been set, and no one is creating it. Then this thread should call
// create
} else if (!mIsCreating) {
shouldCreate = true;
mIsCreating = true;
} else {
// Wait for mModule to be created by another thread
}
}
if (shouldCreate) {
module = create();
// Once module is built (and initialized if markInitializable has been called), modify mModule
// And signal any waiting threads that it is acceptable to read the field now
synchronized (this) {
mIsCreating = false;
this.notifyAll();
}
return module;
} else {
synchronized (this) {
// Block waiting for another thread to build mModule instance
// Since mIsCreating is true until after creation and instantiation (if needed), we wait
// until the module is ready to use.
while (mModule == null && mIsCreating) {
try {
this.wait();
} catch (InterruptedException e) {
continue;
}
}
return Assertions.assertNotNull(mModule);
}
}
}
private NativeModule create() {
SoftAssertions.assertCondition(mModule == null, "Creating an already created module.");
ReactMarker.logMarker(CREATE_MODULE_START, mName, mInstanceKey);
SystraceMessage.beginSection(TRACE_TAG_REACT, "ModuleHolder.createModule")
.arg("name", mName)
.flush();
PrinterHolder.getPrinter()
.logMessage(ReactDebugOverlayTags.NATIVE_MODULE, "NativeModule init: %s", mName);
NativeModule module;
try {
module = assertNotNull(mProvider).get();
mProvider = null;
boolean shouldInitializeNow = false;
synchronized (this) {
mModule = module;
if (mInitializable && !mIsInitializing) {
shouldInitializeNow = true;
}
}
if (shouldInitializeNow) {
doInitialize(module);
}
} catch (Throwable ex) {
/**
* When NativeModules are created from JavaScript, any exception that occurs in the creation
* process will have its stack trace swallowed before we display a RedBox to the user. Really,
* we should have our HostObjects on Android understand JniExceptions and log the stack trace
* to logcat. For now, logging to Logcat directly when creation fails is sufficient.
*
* @todo(T53311351)
*/
FLog.e(ReactConstants.TAG, ex, "Failed to create NativeModule '%s'", mName);
throw ex;
} finally {
ReactMarker.logMarker(CREATE_MODULE_END, mName, mInstanceKey);
SystraceMessage.endSection(TRACE_TAG_REACT).flush();
}
return module;
}
private void doInitialize(NativeModule module) {
SystraceMessage.beginSection(TRACE_TAG_REACT, "ModuleHolder.initialize")
.arg("name", mName)
.flush();
ReactMarker.logMarker(ReactMarkerConstants.INITIALIZE_MODULE_START, mName, mInstanceKey);
try {
boolean shouldInitialize = false;
// Check to see if another thread is initializing the object, if not claim the responsibility
synchronized (this) {
if (mInitializable && !mIsInitializing) {
shouldInitialize = true;
mIsInitializing = true;
}
}
if (shouldInitialize) {
module.initialize();
// Once finished, set flags accordingly, but we don't expect anyone to wait for this to
// finish
// So no need to notify other threads
synchronized (this) {
mIsInitializing = false;
}
}
} finally {
ReactMarker.logMarker(ReactMarkerConstants.INITIALIZE_MODULE_END, mName, mInstanceKey);
SystraceMessage.endSection(TRACE_TAG_REACT).flush();
}
}
}
@@ -0,0 +1,226 @@
/*
* 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.bridge
import androidx.annotation.GuardedBy
import com.facebook.common.logging.FLog
import com.facebook.debug.holder.PrinterHolder
import com.facebook.debug.tags.ReactDebugOverlayTags
import com.facebook.proguard.annotations.DoNotStrip
import com.facebook.react.common.ReactConstants
import com.facebook.react.module.model.ReactModuleInfo
import com.facebook.systrace.Systrace.TRACE_TAG_REACT
import com.facebook.systrace.SystraceMessage
import java.util.concurrent.atomic.AtomicInteger
import javax.inject.Provider
/**
* Holder to enable us to lazy create native modules.
*
* This works by taking a provider instead of an instance, when it is first required we'll create
* and initialize it. Initialization currently always happens on the UI thread but this is due to
* change for performance reasons.
*
* Lifecycle events via a [LifecycleEventListener] will still always happen on the UI thread.
*/
@DoNotStrip
public class ModuleHolder {
private val instanceKey = instanceKeyCounter.getAndIncrement()
@get:DoNotStrip public val name: String
private val reactModuleInfo: ReactModuleInfo
private var provider: Provider<out NativeModule>? = null
// Outside of the constructor, this should only be checked or set when synchronized on this
@GuardedBy("this") private var internalModule: NativeModule? = null
// This is used to communicate phases of creation and initialization across threads
@GuardedBy("this") private var initializable = false
@GuardedBy("this") private var isCreating = false
@GuardedBy("this") private var isInitializing = false
public constructor(moduleInfo: ReactModuleInfo, provider: Provider<out NativeModule?>) {
name = moduleInfo.name
this.provider = provider
reactModuleInfo = moduleInfo
if (moduleInfo.needsEagerInit) {
internalModule = create()
}
}
public constructor(nativeModule: NativeModule) {
name = nativeModule.name
reactModuleInfo =
ReactModuleInfo(
nativeModule.name,
nativeModule.javaClass.simpleName,
nativeModule.canOverrideExistingModule(),
true,
CxxModuleWrapper::class.java.isAssignableFrom(nativeModule.javaClass),
ReactModuleInfo.classIsTurboModule(nativeModule.javaClass))
internalModule = nativeModule
PrinterHolder.printer.logMessage(
ReactDebugOverlayTags.NATIVE_MODULE, "NativeModule init: %s", name)
}
/*
* Checks if [internalModule] has been created, and if so tries to initialize the module unless another
* thread is already doing the initialization.
* If [internalModule] has not been created, records that initialization is needed.
*/
internal fun markInitializable() {
var shouldInitializeNow = false
var module: NativeModule? = null
synchronized(this) {
initializable = true
if (internalModule != null) {
check(!isInitializing)
shouldInitializeNow = true
module = internalModule
}
}
if (shouldInitializeNow) {
checkNotNull(module)
doInitialize(module)
}
}
@Synchronized internal fun hasInstance(): Boolean = internalModule != null
@Synchronized
public fun destroy() {
internalModule?.invalidate()
}
public val canOverrideExistingModule: Boolean
get() = reactModuleInfo.canOverrideExistingModule
public val isTurboModule: Boolean
get() = reactModuleInfo.isTurboModule
public val isCxxModule: Boolean
get() = reactModuleInfo.isCxxModule
public val className: String
get() = reactModuleInfo.className
@get:DoNotStrip
public val module: NativeModule
get() {
val module: NativeModule
var shouldCreate = false
synchronized(this) {
val safeModule = internalModule
if (safeModule != null) {
return safeModule
// if `internalModule` has not been set, and no one is creating it. Then this thread
// should call
// create
} else if (!isCreating) {
shouldCreate = true
isCreating = true
} else {
// Wait for `internalModule` to be created by another thread
}
}
if (shouldCreate) {
module = create()
// Once module is built (and initialized if markInitializable has been called), modify
// `internalModule`
// And signal any waiting threads that it is acceptable to read the field now
synchronized(this) {
isCreating = false
@Suppress("PLATFORM_CLASS_MAPPED_TO_KOTLIN") (this as Object).notifyAll()
}
return module
} else {
synchronized(this) {
// Block waiting for another thread to build `internalModule` instance
// Since isCreating is true until after creation and instantiation (if needed), we wait
// until the module is ready to use.
while (internalModule == null && isCreating) {
try {
@Suppress("PLATFORM_CLASS_MAPPED_TO_KOTLIN") (this as Object).wait()
} catch (e: InterruptedException) {
continue
}
}
return checkNotNull(internalModule)
}
}
}
private fun create(): NativeModule {
SoftAssertions.assertCondition(internalModule == null, "Creating an already created module.")
ReactMarker.logMarker(ReactMarkerConstants.CREATE_MODULE_START, name, instanceKey)
SystraceMessage.beginSection(TRACE_TAG_REACT, "ModuleHolder.createModule")
.arg("name", name)
.flush()
PrinterHolder.printer.logMessage(
ReactDebugOverlayTags.NATIVE_MODULE, "NativeModule init: %s", name)
val module: NativeModule
try {
module = checkNotNull(provider).get()
provider = null
var shouldInitializeNow = false
synchronized(this) {
internalModule = module
if (initializable && !isInitializing) {
shouldInitializeNow = true
}
}
if (shouldInitializeNow) {
doInitialize(module)
}
} catch (e: Throwable) {
/**
* When NativeModules are created from JavaScript, any exception that occurs in the creation
* process will have its stack trace swallowed before we display a RedBox to the user. Really,
* we should have our HostObjects on Android understand JniExceptions and log the stack trace
* to logcat. For now, logging to Logcat directly when creation fails is sufficient.
*
* @todo(T53311351)
*/
FLog.e(ReactConstants.TAG, e, "Failed to create NativeModule '%s'", name)
throw e
} finally {
ReactMarker.logMarker(ReactMarkerConstants.CREATE_MODULE_END, name, instanceKey)
SystraceMessage.endSection(TRACE_TAG_REACT).flush()
}
return module
}
private fun doInitialize(module: NativeModule?) {
SystraceMessage.beginSection(TRACE_TAG_REACT, "ModuleHolder.initialize")
.arg("name", name)
.flush()
ReactMarker.logMarker(ReactMarkerConstants.INITIALIZE_MODULE_START, name, instanceKey)
try {
var shouldInitialize = false
// Check to see if another thread is initializing the object, if not claim the responsibility
synchronized(this) {
if (initializable && !isInitializing) {
shouldInitialize = true
isInitializing = true
}
}
if (shouldInitialize) {
module?.initialize()
// Once finished, set flags accordingly, but we don't expect anyone to wait for this to
// finish, so no need to notify other threads.
synchronized(this) { isInitializing = false }
}
} finally {
ReactMarker.logMarker(ReactMarkerConstants.INITIALIZE_MODULE_END, name, instanceKey)
SystraceMessage.endSection(TRACE_TAG_REACT).flush()
}
}
private companion object {
private val instanceKeyCounter = AtomicInteger(1)
}
}
@@ -33,7 +33,7 @@ internal class FpsView(reactContext: ReactContext?) : FrameLayout(reactContext!!
textView = findViewById<View>(R.id.fps_text) as TextView
frameCallback = FpsDebugFrameCallback(reactContext!!)
fpsMonitorRunnable = FPSMonitorRunnable()
setCurrentFPS(0.0, 0.0, 0, 0)
setCurrentFPS(0.0, 0.0, 0, 0, frameCallback.isRunningOnFabric)
}
override fun onAttachedToWindow() {
@@ -53,16 +53,21 @@ internal class FpsView(reactContext: ReactContext?) : FrameLayout(reactContext!!
currentFPS: Double,
currentJSFPS: Double,
droppedUIFrames: Int,
total4PlusFrameStutters: Int
total4PlusFrameStutters: Int,
runningOnFabric: Boolean
) {
val fpsString =
var fpsString =
String.format(
Locale.US,
"UI: %.1f fps\n%d dropped so far\n%d stutters (4+) so far\nJS: %.1f fps",
"UI: %.1f fps\n%d dropped so far\n%d stutters (4+) so far",
currentFPS,
droppedUIFrames,
total4PlusFrameStutters,
currentJSFPS)
total4PlusFrameStutters)
if (!runningOnFabric) {
// The JS FPS is only relevant for the legacy architecture, as Fabric we don't use
// BridgeIdleDebugListener to track JS frame drops.
fpsString += String.format(Locale.US, "\nJS: %.1f fps", currentJSFPS)
}
textView.text = fpsString
FLog.d(ReactConstants.TAG, fpsString)
}
@@ -80,7 +85,11 @@ internal class FpsView(reactContext: ReactContext?) : FrameLayout(reactContext!!
totalFramesDropped += frameCallback.expectedNumFrames - frameCallback.numFrames
total4PlusFrameStutters += frameCallback.get4PlusFrameStutters()
setCurrentFPS(
frameCallback.fps, frameCallback.jsFPS, totalFramesDropped, total4PlusFrameStutters)
frameCallback.fps,
frameCallback.jsFPS,
totalFramesDropped,
total4PlusFrameStutters,
frameCallback.isRunningOnFabric)
frameCallback.reset()
postDelayed(this, UPDATE_INTERVAL_MS.toLong())
}
@@ -40,7 +40,7 @@ internal class DevMenuModule(
devSupportManager.setHotModuleReplacementEnabled(enabled)
}
public companion object {
public const val NAME: String = NativeDevMenuSpec.NAME
companion object {
const val NAME: String = NativeDevMenuSpec.NAME
}
}
@@ -8,12 +8,10 @@
package com.facebook.react.modules.debug
import android.view.Choreographer
import com.facebook.infer.annotation.Assertions
import com.facebook.react.bridge.ReactContext
import com.facebook.react.bridge.UiThreadUtil
import com.facebook.react.common.build.ReactBuildConfig
import com.facebook.react.uimanager.UIManagerModule
import java.util.TreeMap
/**
* Each time a frame is drawn, records whether it should have expected any more callbacks since the
@@ -25,17 +23,8 @@ import java.util.TreeMap
* idle and not trying to update the UI. This is different from the FPS above since JS rendering is
* async.
*/
public class FpsDebugFrameCallback(private val reactContext: ReactContext) :
internal class FpsDebugFrameCallback(private val reactContext: ReactContext) :
Choreographer.FrameCallback {
public class FpsInfo(
public val totalFrames: Int,
public val totalJsFrames: Int,
public val totalExpectedFrames: Int,
public val total4PlusFrameStutters: Int,
public val fps: Double,
public val jsFps: Double,
public val totalTimeMs: Int
)
private var choreographer: Choreographer? = null
private val didJSUpdateUiDuringFrameDetector: DidJSUpdateUiDuringFrameDetector =
@@ -46,9 +35,7 @@ public class FpsDebugFrameCallback(private val reactContext: ReactContext) :
private var expectedNumFramesPrev = 0
private var fourPlusFrameStutters = 0
private var numFrameCallbacksWithBatchDispatches = 0
private var isRecordingFpsInfoAtEachFrame = false
private var targetFps = DEFAULT_FPS
private var timeToFps: TreeMap<Long, FpsInfo>? = null
override fun doFrame(l: Long) {
if (firstFrameTime == -1L) {
@@ -65,25 +52,12 @@ public class FpsDebugFrameCallback(private val reactContext: ReactContext) :
if (framesDropped >= 4) {
fourPlusFrameStutters++
}
if (isRecordingFpsInfoAtEachFrame) {
Assertions.assertNotNull(timeToFps)
val info =
FpsInfo(
numFrames,
numJSFrames,
expectedNumFrames,
fourPlusFrameStutters,
fps,
jsFPS,
totalTimeMS)
timeToFps?.put(System.currentTimeMillis(), info)
}
expectedNumFramesPrev = expectedNumFrames
choreographer?.postFrameCallback(this)
}
@JvmOverloads
public fun start(targetFps: Double = this.targetFps) {
fun start(targetFps: Double = this.targetFps) {
// T172641976: re-think if we need to implement addBridgeIdleDebugListener and
// removeBridgeIdleDebugListener for Bridgeless
@Suppress("DEPRECATION")
@@ -91,6 +65,11 @@ public class FpsDebugFrameCallback(private val reactContext: ReactContext) :
val uiManagerModule = reactContext.getNativeModule(UIManagerModule::class.java)
if (!reactContext.isBridgeless) {
reactContext.catalystInstance.addBridgeIdleDebugListener(didJSUpdateUiDuringFrameDetector)
isRunningOnFabric = false
} else {
// T172641976 Consider either implementing a mechanism similar to addBridgeIdleDebugListener
// for Fabric or point users to use RNDT.
isRunningOnFabric = true
}
uiManagerModule?.setViewHierarchyUpdateDebugListener(didJSUpdateUiDuringFrameDetector)
}
@@ -101,13 +80,7 @@ public class FpsDebugFrameCallback(private val reactContext: ReactContext) :
}
}
public fun startAndRecordFpsAtEachFrame() {
timeToFps = TreeMap()
isRecordingFpsInfoAtEachFrame = true
start()
}
public fun stop() {
fun stop() {
@Suppress("DEPRECATION")
if (!ReactBuildConfig.UNSTABLE_ENABLE_MINIFY_LEGACY_ARCHITECTURE) {
val uiManagerModule = reactContext.getNativeModule(UIManagerModule::class.java)
@@ -123,53 +96,48 @@ public class FpsDebugFrameCallback(private val reactContext: ReactContext) :
}
}
public val fps: Double
val fps: Double
get() =
if (lastFrameTime == firstFrameTime) {
0.0
} else numFrames.toDouble() * 1e9 / (lastFrameTime - firstFrameTime)
public val jsFPS: Double
/**
* Please note that this value is not relevant if running on Fabric. That's because we don't
* implement addBridgeIdleDebugListener on Fabric.
*/
val jsFPS: Double
get() =
if (lastFrameTime == firstFrameTime) {
0.0
} else numJSFrames.toDouble() * 1e9 / (lastFrameTime - firstFrameTime)
public val numFrames: Int
val numFrames: Int
get() = numFrameCallbacks - 1
public val numJSFrames: Int
private val numJSFrames: Int
get() = numFrameCallbacksWithBatchDispatches - 1
public val expectedNumFrames: Int
val expectedNumFrames: Int
get() {
val totalTimeMS = totalTimeMS.toDouble()
return (targetFps * totalTimeMS / 1000 + 1).toInt()
}
public fun get4PlusFrameStutters(): Int = fourPlusFrameStutters
var isRunningOnFabric = true
private set
public val totalTimeMS: Int
fun get4PlusFrameStutters(): Int = fourPlusFrameStutters
private val totalTimeMS: Int
get() = ((lastFrameTime.toDouble() - firstFrameTime) / 1000000.0).toInt()
/**
* Returns the FpsInfo as if stop had been called at the given upToTimeMs. Only valid if
* monitoring was started with [startAndRecordFpsAtEachFrame].
*/
public fun getFpsInfo(upToTimeMs: Long): FpsInfo? {
Assertions.assertNotNull(timeToFps, "FPS was not recorded at each frame!")
val (_, value) = timeToFps?.floorEntry(upToTimeMs) ?: return null
return value
}
public fun reset() {
fun reset() {
firstFrameTime = -1
lastFrameTime = -1
numFrameCallbacks = 0
fourPlusFrameStutters = 0
numFrameCallbacksWithBatchDispatches = 0
isRecordingFpsInfoAtEachFrame = false
timeToFps = null
}
private companion object {
@@ -74,7 +74,7 @@ internal class DeviceInfoModule(reactContext: ReactApplicationContext) :
reactApplicationContext.removeLifecycleEventListener(this)
}
public companion object {
public const val NAME: String = NativeDeviceInfoSpec.NAME
companion object {
const val NAME: String = NativeDeviceInfoSpec.NAME
}
}
@@ -84,7 +84,7 @@ internal class BridgelessCatalystInstance(private val reactHost: ReactHostImpl)
throw UnsupportedOperationException("Unimplemented method 'destroy'")
}
public override val isDestroyed: Boolean
override val isDestroyed: Boolean
get() = throw UnsupportedOperationException("Unimplemented method 'isDestroyed'")
@VisibleForTesting
@@ -96,16 +96,16 @@ internal class BridgelessCatalystInstance(private val reactHost: ReactHostImpl)
reactHost.currentReactContext?.getJSModule(jsInterface)
@get:Deprecated("Deprecated in Java")
public override val javaScriptContextHolder: JavaScriptContextHolder
override val javaScriptContextHolder: JavaScriptContextHolder
get() = reactHost.javaScriptContextHolder!!
@Suppress("INAPPLICABLE_JVM_NAME")
@get:Deprecated("Deprecated in Java")
@get:JvmName("getJSCallInvokerHolder") // This is needed to keep backward compatibility
public override val jsCallInvokerHolder: CallInvokerHolder
override val jsCallInvokerHolder: CallInvokerHolder
get() = reactHost.jsCallInvokerHolder!!
public override val nativeMethodCallInvokerHolder: NativeMethodCallInvokerHolder
override val nativeMethodCallInvokerHolder: NativeMethodCallInvokerHolder
get() =
throw UnsupportedOperationException(
"Unimplemented method 'getNativeMethodCallInvokerHolder'")
@@ -119,23 +119,23 @@ internal class BridgelessCatalystInstance(private val reactHost: ReactHostImpl)
override fun getNativeModule(moduleName: String): NativeModule? =
reactHost.getNativeModule(moduleName)
public override val nativeModules: Collection<NativeModule>
override val nativeModules: Collection<NativeModule>
get() = reactHost.nativeModules
public override val reactQueueConfiguration: ReactQueueConfiguration
override val reactQueueConfiguration: ReactQueueConfiguration
get() = reactHost.reactQueueConfiguration!!
public override val runtimeExecutor: RuntimeExecutor?
override val runtimeExecutor: RuntimeExecutor?
get() = reactHost.runtimeExecutor
public override val runtimeScheduler: RuntimeScheduler
override val runtimeScheduler: RuntimeScheduler
get() = throw UnsupportedOperationException("Unimplemented method 'getRuntimeScheduler'")
public override fun extendNativeModules(modules: NativeModuleRegistry) {
override fun extendNativeModules(modules: NativeModuleRegistry) {
throw UnsupportedOperationException("Unimplemented method 'extendNativeModules'")
}
public override val sourceURL: String
override val sourceURL: String
get() = throw UnsupportedOperationException("Unimplemented method 'getSourceURL'")
override fun addBridgeIdleDebugListener(listener: NotThreadSafeBridgeIdleDebugListener) {
@@ -57,7 +57,7 @@ internal class BridgelessReactContext(context: Context, private val reactHost: R
override fun getSourceURL(): String? = sourceURLRef.get()
public fun setSourceURL(sourceURL: String?) {
fun setSourceURL(sourceURL: String?) {
sourceURLRef.set(sourceURL)
}
@@ -59,7 +59,7 @@ internal class FabricEventDispatcher(
eventEmitter.registerFabricEventEmitter(fabricEventEmitter)
}
public override fun dispatchEvent(event: Event<*>) {
override fun dispatchEvent(event: Event<*>) {
for (listener in listeners) {
listener.onEventDispatch(event)
}
@@ -100,7 +100,7 @@ internal class FabricEventDispatcher(
}
}
public override fun dispatchAllEvents() {
override fun dispatchAllEvents() {
scheduleDispatchOfBatchedEvents()
}
@@ -116,46 +116,46 @@ internal class FabricEventDispatcher(
}
/** Add a listener to this EventDispatcher. */
public override fun addListener(listener: EventDispatcherListener) {
override fun addListener(listener: EventDispatcherListener) {
listeners.add(listener)
}
/** Remove a listener from this EventDispatcher. */
public override fun removeListener(listener: EventDispatcherListener) {
override fun removeListener(listener: EventDispatcherListener) {
listeners.remove(listener)
}
public override fun addBatchEventDispatchedListener(listener: BatchEventDispatchedListener) {
override fun addBatchEventDispatchedListener(listener: BatchEventDispatchedListener) {
postEventDispatchListeners.add(listener)
}
public override fun removeBatchEventDispatchedListener(listener: BatchEventDispatchedListener) {
override fun removeBatchEventDispatchedListener(listener: BatchEventDispatchedListener) {
postEventDispatchListeners.remove(listener)
}
public override fun onHostResume() {
override fun onHostResume() {
scheduleDispatchOfBatchedEvents()
if (!ReactNativeFeatureFlags.useOptimizedEventBatchingOnAndroid()) {
currentFrameCallback.resume()
}
}
public override fun onHostPause() {
override fun onHostPause() {
cancelDispatchOfBatchedEvents()
}
public override fun onHostDestroy() {
override fun onHostDestroy() {
cancelDispatchOfBatchedEvents()
}
public fun invalidate() {
fun invalidate() {
eventEmitter.registerFabricEventEmitter(null)
UiThreadUtil.runOnUiThread { cancelDispatchOfBatchedEvents() }
}
@Deprecated("Private API, should only be used when the concrete implementation is known.")
public override fun onCatalystInstanceDestroyed() {
override fun onCatalystInstanceDestroyed() {
invalidate()
}
@@ -21,9 +21,9 @@ import com.facebook.react.common.annotations.internal.LegacyArchitectureLogger
@LegacyArchitecture(logLevel = LegacyArchitectureLogLevel.ERROR)
internal class LayoutUpdateAnimation : AbstractLayoutAnimation() {
internal override fun isValid(): Boolean = durationMs > 0
override fun isValid(): Boolean = durationMs > 0
internal override fun createAnimationImpl(
override fun createAnimationImpl(
view: View,
x: Int,
y: Int,
@@ -26,7 +26,7 @@ internal class ColorStop(var color: Int? = null, val position: LengthPercentage?
internal class ProcessedColorStop(var color: Int? = null, val position: Float? = null)
internal object ColorStopUtils {
public fun getFixedColorStops(
fun getFixedColorStops(
colorStops: List<ColorStop>,
gradientLineLength: Float
): List<ProcessedColorStop> {
@@ -10,5 +10,5 @@ package com.facebook.react.uimanager.style
import android.graphics.Shader
internal interface Gradient {
public fun getShader(width: Float, height: Float): Shader
fun getShader(width: Float, height: Float): Shader
}
@@ -34,7 +34,7 @@ internal class MaintainVisibleScrollPositionHelper<ScrollViewT>(
private val horizontal: Boolean
) : UIManagerListener where ScrollViewT : HasSmoothScroll?, ScrollViewT : ViewGroup? {
public var config: Config? = null
var config: Config? = null
private var firstVisibleViewRef: WeakReference<View>? = null
private var prevFirstVisibleFrame: Rect? = null
private var isListening = false
@@ -16,7 +16,7 @@ import com.facebook.proguard.annotations.DoNotStrip
*/
@DoNotStrip
internal class PreparedLayout(
public val layout: Layout,
public val maximumNumberOfLines: Int,
public val verticalOffset: Float
val layout: Layout,
val maximumNumberOfLines: Int,
val verticalOffset: Float
)
@@ -41,7 +41,7 @@ internal class PreparedLayoutTextView(context: Context) : ViewGroup(context), Re
private var clickableSpans: List<ClickableSpan> = emptyList()
private var selection: TextSelection? = null
public var preparedLayout: PreparedLayout? = null
var preparedLayout: PreparedLayout? = null
set(value) {
if (field != value) {
val lastSelection = selection
@@ -63,7 +63,7 @@ internal class PreparedLayoutTextView(context: Context) : ViewGroup(context), Re
// T221698007: This is closest to existing behavior, but does not align with web. We may want to
// change in the future if not too breaking.
public var overflow: Overflow = Overflow.HIDDEN
var overflow: Overflow = Overflow.HIDDEN
set(value) {
if (field != value) {
field = value
@@ -71,9 +71,9 @@ internal class PreparedLayoutTextView(context: Context) : ViewGroup(context), Re
}
}
public @ColorInt var selectionColor: Int? = null
@ColorInt var selectionColor: Int? = null
public val text: CharSequence?
val text: CharSequence?
get() = preparedLayout?.layout?.text
init {
@@ -88,7 +88,7 @@ internal class PreparedLayoutTextView(context: Context) : ViewGroup(context), Re
preparedLayout = null
}
public fun recycleView(): Unit {
fun recycleView(): Unit {
initView()
BackgroundStyleApplicator.reset(this)
overflow = Overflow.HIDDEN
@@ -122,7 +122,7 @@ internal class PreparedLayoutTextView(context: Context) : ViewGroup(context), Re
// No-op
}
public fun setSelection(start: Int, end: Int) {
fun setSelection(start: Int, end: Int) {
val layout = checkNotNull(preparedLayout).layout
if (start < 0 || end > layout.text.length || start >= end) {
throw IllegalArgumentException(
@@ -143,7 +143,7 @@ internal class PreparedLayoutTextView(context: Context) : ViewGroup(context), Re
invalidate()
}
public fun clearSelection() {
fun clearSelection() {
selection = null
invalidate()
}
@@ -98,23 +98,23 @@ internal class PreparedLayoutTextViewManager :
}
@ReactProp(name = "overflow")
public fun setOverflow(view: PreparedLayoutTextView, overflow: String?): Unit {
fun setOverflow(view: PreparedLayoutTextView, overflow: String?): Unit {
view.overflow = overflow?.let { Overflow.fromString(it) } ?: Overflow.HIDDEN
}
@ReactProp(name = "accessible")
public fun setAccessible(view: PreparedLayoutTextView, accessible: Boolean): Unit {
fun setAccessible(view: PreparedLayoutTextView, accessible: Boolean): Unit {
view.isFocusable = accessible
}
@ReactProp(name = "selectable", defaultBoolean = false)
public fun setSelectable(view: PreparedLayoutTextView, isSelectable: Boolean): Unit {
fun setSelectable(view: PreparedLayoutTextView, isSelectable: Boolean): Unit {
// T222052152: Implement fine-grained text selection for PreparedLayoutTextView
// view.setTextIsSelectable(isSelectable);
}
@ReactProp(name = "selectionColor", customType = "Color")
public fun setSelectionColor(view: PreparedLayoutTextView, color: Int?): Unit {
fun setSelectionColor(view: PreparedLayoutTextView, color: Int?): Unit {
if (color == null) {
view.selectionColor = DefaultStyleValuesUtil.getDefaultTextColorHighlight(view.context)
} else {
@@ -131,7 +131,7 @@ internal class PreparedLayoutTextViewManager :
ViewProps.BORDER_BOTTOM_RIGHT_RADIUS,
ViewProps.BORDER_BOTTOM_LEFT_RADIUS],
defaultFloat = Float.NaN)
public fun setBorderRadius(view: PreparedLayoutTextView, index: Int, borderRadius: Float): Unit {
fun setBorderRadius(view: PreparedLayoutTextView, index: Int, borderRadius: Float): Unit {
val radius =
if (borderRadius.isNaN()) null
else LengthPercentage(borderRadius, LengthPercentageType.POINT)
@@ -139,7 +139,7 @@ internal class PreparedLayoutTextViewManager :
}
@ReactProp(name = "borderStyle")
public fun setBorderStyle(view: PreparedLayoutTextView, borderStyle: String?): Unit {
fun setBorderStyle(view: PreparedLayoutTextView, borderStyle: String?): Unit {
val parsedBorderStyle = if (borderStyle == null) null else BorderStyle.fromString(borderStyle)
BackgroundStyleApplicator.setBorderStyle(view, parsedBorderStyle)
}
@@ -155,7 +155,7 @@ internal class PreparedLayoutTextViewManager :
ViewProps.BORDER_START_WIDTH,
ViewProps.BORDER_END_WIDTH],
defaultFloat = Float.NaN)
public fun setBorderWidth(view: PreparedLayoutTextView, index: Int, width: Float): Unit {
fun setBorderWidth(view: PreparedLayoutTextView, index: Int, width: Float): Unit {
BackgroundStyleApplicator.setBorderWidth(view, LogicalEdge.values()[index], width)
}
@@ -174,12 +174,12 @@ internal class PreparedLayoutTextViewManager :
ViewProps.BORDER_BLOCK_START_COLOR,
],
customType = "Color")
public fun setBorderColor(view: PreparedLayoutTextView, index: Int, color: Int?): Unit {
fun setBorderColor(view: PreparedLayoutTextView, index: Int, color: Int?): Unit {
BackgroundStyleApplicator.setBorderColor(view, LogicalEdge.values()[index], color)
}
@ReactProp(name = "disabled", defaultBoolean = false)
public fun setDisabled(view: PreparedLayoutTextView, disabled: Boolean): Unit {
fun setDisabled(view: PreparedLayoutTextView, disabled: Boolean): Unit {
view.setEnabled(!disabled)
}
@@ -214,7 +214,7 @@ internal class PreparedLayoutTextViewManager :
reactTextViewManagerCallback?.onPostProcessSpannable(text)
}
public companion object {
public const val REACT_CLASS: String = "RCTText"
companion object {
const val REACT_CLASS: String = "RCTText"
}
}
@@ -110,7 +110,7 @@ public abstract class ReactBaseTextShadowNode extends LayoutShadowNode {
if (child instanceof ReactRawTextShadowNode) {
String childText = ((ReactRawTextShadowNode) child).getText();
if (childText != null) {
sb.append(TextTransform.applyNonNull(childText, textAttributes.textTransform));
sb.append(TextTransform.apply(childText, textAttributes.textTransform));
}
} else if (child instanceof ReactBaseTextShadowNode) {
buildSpannedFromShadowNode(
@@ -265,7 +265,7 @@ public abstract class ReactBaseTextShadowNode extends LayoutShadowNode {
if (text != null) {
// Handle text that is provided via a prop (e.g. the `value` and `defaultValue` props on
// TextInput).
sb.append(TextTransform.applyNonNull(text, textShadowNode.mTextAttributes.textTransform));
sb.append(TextTransform.apply(text, textShadowNode.mTextAttributes.textTransform));
}
buildSpannedFromShadowNode(textShadowNode, sb, ops, null, supportsInlineViews, inlineViews, 0);
@@ -134,12 +134,12 @@ public constructor(
view.setSpanned(spanned)
val minimumFontSize: Float =
paragraphAttributes.getDouble(TextLayoutManager.PA_KEY_MINIMUM_FONT_SIZE.toInt()).toFloat()
paragraphAttributes.getDouble(TextLayoutManager.PA_KEY_MINIMUM_FONT_SIZE).toFloat()
view.setMinimumFontSize(minimumFontSize)
val textBreakStrategy =
TextAttributeProps.getTextBreakStrategy(
paragraphAttributes.getString(TextLayoutManager.PA_KEY_TEXT_BREAK_STRATEGY.toInt()))
paragraphAttributes.getString(TextLayoutManager.PA_KEY_TEXT_BREAK_STRATEGY))
val currentJustificationMode =
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) 0 else view.getJustificationMode()
@@ -147,7 +147,7 @@ public constructor(
spanned,
-1, // UNUSED FOR TEXT
false, // TODO add this into local Data
TextLayoutManager.getTextGravity(attributedString, spanned, view.gravityHorizontal),
TextLayoutManager.getTextGravity(attributedString, spanned),
textBreakStrategy,
TextAttributeProps.getJustificationMode(props, currentJustificationMode))
}
@@ -21,11 +21,7 @@ internal enum class TextTransform {
internal companion object {
@JvmStatic
fun apply(text: String?, textTransform: TextTransform?): String? =
text?.applyTextTransform(textTransform)
@JvmStatic
fun applyNonNull(text: String, textTransform: TextTransform?): String =
fun apply(text: String, textTransform: TextTransform?): String =
text.applyTextTransform(textTransform)
}
}
@@ -43,7 +43,7 @@ internal class FrescoBasedReactTextInlineImageShadowNode(
private var tintColor = 0
@ReactProp(name = "src")
public fun setSource(sources: ReadableArray?) {
fun setSource(sources: ReadableArray?) {
val source =
if (sources == null || sources.size() == 0 || sources.getType(0) != ReadableType.Map) null
else checkNotNull(sources.getMap(0)).getString("uri")
@@ -69,12 +69,12 @@ internal class FrescoBasedReactTextInlineImageShadowNode(
}
@ReactProp(name = "headers")
public fun setHeaders(newHeaders: ReadableMap?) {
fun setHeaders(newHeaders: ReadableMap?) {
headers = newHeaders
}
@ReactProp(name = "tintColor", customType = "Color")
public fun setTintColor(newTintColor: Int) {
fun setTintColor(newTintColor: Int) {
tintColor = newTintColor
}
@@ -98,13 +98,13 @@ internal class FrescoBasedReactTextInlineImageShadowNode(
}
@ReactProp(name = ViewProps.RESIZE_MODE)
public fun setResizeMode(newResizeMode: String?) {
fun setResizeMode(newResizeMode: String?) {
resizeMode = newResizeMode
}
public fun getUri(): Uri? = uri
fun getUri(): Uri? = uri
public fun getHeaders(): ReadableMap? = headers
fun getHeaders(): ReadableMap? = headers
override fun isVirtual(): Boolean = true
@@ -124,13 +124,13 @@ internal class FrescoBasedReactTextInlineImageShadowNode(
resizeMode)
}
public fun getDraweeControllerBuilder() = draweeControllerBuilder
fun getDraweeControllerBuilder() = draweeControllerBuilder
public fun getCallerContext(): Any? = callerContext
fun getCallerContext(): Any? = callerContext
// TODO: t9053573 is tracking that this code should be shared
companion object {
public fun getResourceDrawableUri(context: Context, name: String?): Uri? {
fun getResourceDrawableUri(context: Context, name: String?): Uri? {
if (name == null || name.isEmpty()) {
return null
}
@@ -73,23 +73,23 @@ internal class FrescoBasedReactTextInlineImageSpan(
* The ReactTextView that holds this ImageSpan is responsible for passing these methods on so that
* we can do proper lifetime management for Fresco
*/
public override fun onDetachedFromWindow() {
override fun onDetachedFromWindow() {
draweeHolder.onDetach()
}
public override fun onStartTemporaryDetach() {
override fun onStartTemporaryDetach() {
draweeHolder.onDetach()
}
public override fun onAttachedToWindow() {
override fun onAttachedToWindow() {
draweeHolder.onAttach()
}
public override fun onFinishTemporaryDetach() {
override fun onFinishTemporaryDetach() {
draweeHolder.onAttach()
}
public override fun getSize(
override fun getSize(
paint: Paint,
text: CharSequence,
start: Int,
@@ -110,11 +110,11 @@ internal class FrescoBasedReactTextInlineImageSpan(
return _width
}
public override fun setTextView(textView: TextView?) {
override fun setTextView(textView: TextView?) {
this.textView = textView
}
public override fun draw(
override fun draw(
canvas: Canvas,
text: CharSequence,
start: Int,
@@ -16,8 +16,7 @@ import android.text.style.MetricAffectingSpan
* The letter spacing is specified in pixels, which are converted to ems at paint time; this span
* must therefore be applied after any spans affecting font size.
*/
internal class CustomLetterSpacingSpan(public val spacing: Float) :
MetricAffectingSpan(), ReactSpan {
internal class CustomLetterSpacingSpan(val spacing: Float) : MetricAffectingSpan(), ReactSpan {
override fun updateDrawState(paint: TextPaint) {
apply(paint)
}
@@ -71,8 +71,6 @@ import com.facebook.react.views.text.ReactTextViewManagerCallback
import com.facebook.react.views.text.ReactTypefaceUtils.parseFontVariant
import com.facebook.react.views.text.TextAttributeProps
import com.facebook.react.views.text.TextLayoutManager
import com.facebook.react.views.text.TextTransform
import com.facebook.react.views.text.TextTransform.Companion.apply
import com.facebook.react.views.text.internal.span.TextInlineImageSpan.Companion.possiblyUpdateInlineImageSpans
import java.util.LinkedList
@@ -182,7 +180,7 @@ public open class ReactTextInputManager public constructor() :
private fun getReactTextUpdate(text: String?, mostRecentEventCount: Int): ReactTextUpdate {
val sb = SpannableStringBuilder()
sb.append(apply(text, TextTransform.UNSET))
sb.append(text)
return ReactTextUpdate(
sb, mostRecentEventCount, false, 0f, 0f, 0f, 0f, Gravity.NO_GRAVITY, 0, 0)
}
@@ -50,9 +50,9 @@ import org.mockito.stubbing.Answer
import org.robolectric.RobolectricTestRunner
import org.robolectric.Shadows.shadowOf
public object MockCompat {
object MockCompat {
// Same as Mockito's 'eq()', but works for non-nullable types
public fun <T : Any> eq(value: T): T = ArgumentMatchers.eq(value) ?: value
fun <T : Any> eq(value: T): T = ArgumentMatchers.eq(value) ?: value
// Same as Mockito's 'any()', but works for non-nullable types
fun <T> any(): T {
@@ -16,8 +16,6 @@ else
source[:tag] = "v#{version}"
end
using_hermes = ENV['USE_HERMES'] == nil || ENV['USE_HERMES'] == "1"
Pod::Spec.new do |s|
s.name = "ReactCommon"
s.module_name = "ReactCommon"
@@ -48,7 +46,7 @@ Pod::Spec.new do |s|
ss.dependency "React-cxxreact", version
ss.dependency "React-jsi", version
ss.dependency "React-logger", version
if using_hermes
if use_hermes()
ss.dependency "hermes-engine"
end
@@ -58,7 +56,7 @@ Pod::Spec.new do |s|
sss.exclude_files = "react/bridging/tests"
sss.header_dir = "react/bridging"
sss.pod_target_xcconfig = { "HEADER_SEARCH_PATHS" => "\"$(PODS_TARGET_SRCROOT)/ReactCommon\"" }
if using_hermes
if use_hermes()
sss.dependency "hermes-engine"
end
end
@@ -48,7 +48,7 @@ Pod::Spec.new do |s|
s.resource_bundles = {'React-cxxreact_privacy' => 'PrivacyInfo.xcprivacy'}
if ENV['USE_HERMES'] == nil || ENV['USE_HERMES'] == "1"
if use_hermes()
s.dependency 'hermes-engine'
end
@@ -44,7 +44,7 @@ Pod::Spec.new do |s|
add_dependency(s, "React-featureflags")
add_dependency(s, "React-debug")
if ENV['USE_HERMES'] == nil || ENV['USE_HERMES'] == "1"
if use_hermes()
s.dependency 'hermes-engine'
end
@@ -5,10 +5,6 @@
require "json"
js_engine = ENV['USE_HERMES'] == "0" ?
:jsc :
:hermes
package = JSON.parse(File.read(File.join(__dir__, "..", "..", "package.json")))
version = package['version']
@@ -42,7 +38,7 @@ Pod::Spec.new do |s|
"jsi/jsilib-windows.cpp",
"**/test/*"
]
if js_engine == :hermes
if use_hermes()
# JSI is a part of hermes-engine. Including them also in react-native will violate the One Definition Rulle.
files_to_exclude += [ "jsi/jsi.cpp" ]
s.dependency "hermes-engine"
@@ -36,7 +36,7 @@ Pod::Spec.new do |s|
add_dependency(s, "React-jsinspector", :framework_name => 'jsinspector_modern')
add_dependency(s, "React-jsinspectorcdp", :framework_name => 'jsinspector_moderncdp')
add_dependency(s, "React-jsinspectortracing", :framework_name => 'jsinspector_moderntracing')
if ENV['USE_HERMES'] == nil || ENV['USE_HERMES'] == "1"
if use_hermes()
s.dependency 'hermes-engine'
end
@@ -55,7 +55,7 @@ Pod::Spec.new do |s|
add_dependency(s, "React-jsinspectornetwork", :framework_name => 'jsinspector_modernnetwork')
add_dependency(s, "React-jsinspectortracing", :framework_name => 'jsinspector_moderntracing')
s.dependency "React-perflogger", version
if ENV["USE_HERMES"] == nil || ENV["USE_HERMES"] == "1"
if use_hermes()
s.dependency "hermes-engine"
end
@@ -16,6 +16,7 @@
#include <jsinspector-modern/InspectorInterfaces.h>
#include <jsinspector-modern/InspectorPackagerConnection.h>
#include <format>
#include <memory>
#include "FollyDynamicMatchers.h"
@@ -25,7 +26,7 @@
using namespace ::testing;
using namespace std::literals::chrono_literals;
using namespace std::literals::string_literals;
using folly::dynamic, folly::toJson, folly::sformat;
using folly::dynamic, folly::toJson;
namespace facebook::react::jsinspector_modern {
@@ -281,7 +282,7 @@ TEST_F(InspectorPackagerConnectionTest, TestSendReceiveEvents) {
.lazily_make_unique<std::unique_ptr<IRemoteConnection>>());
// Connect to the page.
webSockets_[0]->getDelegate().didReceiveMessage(sformat(
webSockets_[0]->getDelegate().didReceiveMessage(std::format(
R"({{
"event": "connect",
"payload": {{
@@ -321,7 +322,7 @@ TEST_F(InspectorPackagerConnectionTest, TestSendReceiveEvents) {
AtJsonPtr("/params", ElementsAre("arg1", "arg2"))))))
.RetiresOnSaturation();
webSockets_[0]->getDelegate().didReceiveMessage(sformat(
webSockets_[0]->getDelegate().didReceiveMessage(std::format(
R"({{
"event": "wrappedEvent",
"payload": {{
@@ -373,7 +374,7 @@ TEST_F(InspectorPackagerConnectionTest, TestSendReceiveEventsToMultiplePages) {
for (int i = 0; i < kNumPages; ++i) {
// Connect to the i-th page.
webSockets_[0]->getDelegate().didReceiveMessage(sformat(
webSockets_[0]->getDelegate().didReceiveMessage(std::format(
R"({{
"event": "connect",
"payload": {{
@@ -415,7 +416,7 @@ TEST_F(InspectorPackagerConnectionTest, TestSendReceiveEventsToMultiplePages) {
*localConnections_[i],
sendMessage(JsonParsed(AtJsonPtr("/method", Eq(method)))))
.RetiresOnSaturation();
webSockets_[0]->getDelegate().didReceiveMessage(sformat(
webSockets_[0]->getDelegate().didReceiveMessage(std::format(
R"({{
"event": "wrappedEvent",
"payload": {{
@@ -445,7 +446,7 @@ TEST_F(InspectorPackagerConnectionTest, TestSendEventToAllConnections) {
.lazily_make_unique<std::unique_ptr<IRemoteConnection>>());
// Connect to the page.
webSockets_[0]->getDelegate().didReceiveMessage(sformat(
webSockets_[0]->getDelegate().didReceiveMessage(std::format(
R"({{
"event": "connect",
"payload": {{
@@ -486,7 +487,7 @@ TEST_F(InspectorPackagerConnectionTest, TestConnectThenDisconnect) {
.lazily_make_unique<std::unique_ptr<IRemoteConnection>>());
// Connect to the page.
webSockets_[0]->getDelegate().didReceiveMessage(sformat(
webSockets_[0]->getDelegate().didReceiveMessage(std::format(
R"({{
"event": "connect",
"payload": {{
@@ -498,7 +499,7 @@ TEST_F(InspectorPackagerConnectionTest, TestConnectThenDisconnect) {
// Disconnect from the page.
EXPECT_CALL(*localConnections_[0], disconnect()).RetiresOnSaturation();
webSockets_[0]->getDelegate().didReceiveMessage(sformat(
webSockets_[0]->getDelegate().didReceiveMessage(std::format(
R"({{
"event": "disconnect",
"payload": {{
@@ -521,7 +522,7 @@ TEST_F(InspectorPackagerConnectionTest, TestConnectThenCloseSocket) {
.lazily_make_unique<std::unique_ptr<IRemoteConnection>>());
// Connect to the page.
webSockets_[0]->getDelegate().didReceiveMessage(sformat(
webSockets_[0]->getDelegate().didReceiveMessage(std::format(
R"({{
"event": "connect",
"payload": {{
@@ -549,7 +550,7 @@ TEST_F(InspectorPackagerConnectionTest, TestConnectThenSocketFailure) {
.lazily_make_unique<std::unique_ptr<IRemoteConnection>>());
// Connect to the page.
webSockets_[0]->getDelegate().didReceiveMessage(sformat(
webSockets_[0]->getDelegate().didReceiveMessage(std::format(
R"({{
"event": "connect",
"payload": {{
@@ -579,7 +580,7 @@ TEST_F(
.lazily_make_unique<std::unique_ptr<IRemoteConnection>>());
// Connect to the page.
webSockets_[0]->getDelegate().didReceiveMessage(sformat(
webSockets_[0]->getDelegate().didReceiveMessage(std::format(
R"({{
"event": "connect",
"payload": {{
@@ -625,7 +626,7 @@ TEST_F(
.lazily_make_unique<std::unique_ptr<IRemoteConnection>>());
// Connect to the page.
webSockets_[0]->getDelegate().didReceiveMessage(sformat(
webSockets_[0]->getDelegate().didReceiveMessage(std::format(
R"({{
"event": "connect",
"payload": {{
@@ -637,7 +638,7 @@ TEST_F(
// Try connecting to the same page again. This results in a disconnection.
EXPECT_CALL(*localConnections_[0], disconnect()).RetiresOnSaturation();
webSockets_[0]->getDelegate().didReceiveMessage(sformat(
webSockets_[0]->getDelegate().didReceiveMessage(std::format(
R"({{
"event": "connect",
"payload": {{
@@ -660,7 +661,7 @@ TEST_F(InspectorPackagerConnectionTest, TestMultipleDisconnect) {
.lazily_make_unique<std::unique_ptr<IRemoteConnection>>());
// Connect to the page.
webSockets_[0]->getDelegate().didReceiveMessage(sformat(
webSockets_[0]->getDelegate().didReceiveMessage(std::format(
R"({{
"event": "connect",
"payload": {{
@@ -672,7 +673,7 @@ TEST_F(InspectorPackagerConnectionTest, TestMultipleDisconnect) {
// Disconnect from the page.
EXPECT_CALL(*localConnections_[0], disconnect()).RetiresOnSaturation();
webSockets_[0]->getDelegate().didReceiveMessage(sformat(
webSockets_[0]->getDelegate().didReceiveMessage(std::format(
R"({{
"event": "disconnect",
"payload": {{
@@ -683,7 +684,7 @@ TEST_F(InspectorPackagerConnectionTest, TestMultipleDisconnect) {
EXPECT_FALSE(localConnections_[0]);
// Disconnect again. This is a noop.
webSockets_[0]->getDelegate().didReceiveMessage(sformat(
webSockets_[0]->getDelegate().didReceiveMessage(std::format(
R"({{
"event": "disconnect",
"payload": {{
@@ -706,7 +707,7 @@ TEST_F(InspectorPackagerConnectionTest, TestDisconnectThenSendEvent) {
.lazily_make_unique<std::unique_ptr<IRemoteConnection>>());
// Connect to the page.
webSockets_[0]->getDelegate().didReceiveMessage(sformat(
webSockets_[0]->getDelegate().didReceiveMessage(std::format(
R"({{
"event": "connect",
"payload": {{
@@ -718,7 +719,7 @@ TEST_F(InspectorPackagerConnectionTest, TestDisconnectThenSendEvent) {
// Disconnect from the page.
EXPECT_CALL(*localConnections_[0], disconnect()).RetiresOnSaturation();
webSockets_[0]->getDelegate().didReceiveMessage(sformat(
webSockets_[0]->getDelegate().didReceiveMessage(std::format(
R"({{
"event": "disconnect",
"payload": {{
@@ -730,7 +731,7 @@ TEST_F(InspectorPackagerConnectionTest, TestDisconnectThenSendEvent) {
// Send an event from the frontend (remote) to the backend (local). This
// is a noop.
webSockets_[0]->getDelegate().didReceiveMessage(sformat(
webSockets_[0]->getDelegate().didReceiveMessage(std::format(
R"({{
"event": "wrappedEvent",
"payload": {{
@@ -754,7 +755,7 @@ TEST_F(InspectorPackagerConnectionTest, TestSendEventToUnknownPage) {
// Send an event from the frontend (remote) to the backend (local). This
// is a noop (except for logging).
webSockets_[0]->getDelegate().didReceiveMessage(sformat(
webSockets_[0]->getDelegate().didReceiveMessage(std::format(
R"({{
"event": "wrappedEvent",
"payload": {{
@@ -921,7 +922,7 @@ TEST_F(
.lazily_make_unique<std::unique_ptr<IRemoteConnection>>());
// Connect to the page.
retainedWebSocketDelegate->didReceiveMessage(sformat(
retainedWebSocketDelegate->didReceiveMessage(std::format(
R"({{
"event": "connect",
"payload": {{
@@ -941,7 +942,7 @@ TEST_F(
AtJsonPtr("/params", ElementsAre("arg1", "arg2"))))))
.RetiresOnSaturation();
retainedWebSocketDelegate->didReceiveMessage(sformat(
retainedWebSocketDelegate->didReceiveMessage(std::format(
R"({{
"event": "wrappedEvent",
"payload": {{
@@ -975,7 +976,7 @@ TEST_F(InspectorPackagerConnectionTest, TestDestroyConnectionOnPageRemoved) {
.lazily_make_unique<std::unique_ptr<IRemoteConnection>>());
// Connect to the page.
webSockets_[0]->getDelegate().didReceiveMessage(sformat(
webSockets_[0]->getDelegate().didReceiveMessage(std::format(
R"({{
"event": "connect",
"payload": {{
@@ -1005,7 +1006,7 @@ TEST_F(
.lazily_make_unique<std::unique_ptr<IRemoteConnection>>());
// Connect to the page.
webSockets_[0]->getDelegate().didReceiveMessage(sformat(
webSockets_[0]->getDelegate().didReceiveMessage(std::format(
R"({{
"event": "connect",
"payload": {{
@@ -1045,7 +1046,7 @@ TEST_F(
.lazily_make_unique<std::unique_ptr<IRemoteConnection>>());
// Connect to the page.
webSockets_[0]->getDelegate().didReceiveMessage(sformat(
webSockets_[0]->getDelegate().didReceiveMessage(std::format(
R"({{
"event": "connect",
"payload": {{
@@ -1064,7 +1065,7 @@ TEST_F(
// Disconnect from the page.
EXPECT_CALL(*localConnections_[0], disconnect()).RetiresOnSaturation();
webSockets_[0]->getDelegate().didReceiveMessage(sformat(
webSockets_[0]->getDelegate().didReceiveMessage(std::format(
R"({{
"event": "disconnect",
"payload": {{
@@ -1075,7 +1076,7 @@ TEST_F(
EXPECT_FALSE(localConnections_[0]);
// Connect to the same page again.
webSockets_[0]->getDelegate().didReceiveMessage(sformat(
webSockets_[0]->getDelegate().didReceiveMessage(std::format(
R"({{
"event": "connect",
"payload": {{
@@ -1127,7 +1128,7 @@ TEST_F(
.lazily_make_unique<std::unique_ptr<IRemoteConnection>>());
// Connect to the page.
webSockets_[0]->getDelegate().didReceiveMessage(sformat(
webSockets_[0]->getDelegate().didReceiveMessage(std::format(
R"({{
"event": "connect",
"payload": {{
@@ -1151,7 +1152,7 @@ TEST_F(
// Disconnect from the page.
EXPECT_CALL(*localConnections_[0], disconnect()).RetiresOnSaturation();
webSockets_[0]->getDelegate().didReceiveMessage(sformat(
webSockets_[0]->getDelegate().didReceiveMessage(std::format(
R"({{
"event": "disconnect",
"payload": {{
@@ -1162,7 +1163,7 @@ TEST_F(
EXPECT_FALSE(localConnections_[0]);
// Connect to the same page again.
webSockets_[0]->getDelegate().didReceiveMessage(sformat(
webSockets_[0]->getDelegate().didReceiveMessage(std::format(
R"({{
"event": "connect",
"payload": {{
@@ -1256,7 +1257,7 @@ TEST_F(InspectorPackagerConnectionTest, TestRejectedPageConnection) {
AtJsonPtr("/payload/pageId", Eq(std::to_string(pageId)))))))
.RetiresOnSaturation();
webSockets_[0]->getDelegate().didReceiveMessage(sformat(
webSockets_[0]->getDelegate().didReceiveMessage(std::format(
R"({{
"event": "connect",
"payload": {{
@@ -1265,7 +1266,7 @@ TEST_F(InspectorPackagerConnectionTest, TestRejectedPageConnection) {
}})",
toJson(std::to_string(pageId))));
webSockets_[0]->getDelegate().didReceiveMessage(sformat(
webSockets_[0]->getDelegate().didReceiveMessage(std::format(
R"({{
"event": "wrappedEvent",
"payload": {{
@@ -1291,7 +1292,7 @@ TEST_F(InspectorPackagerConnectionTest, TestRejectedPageConnection) {
AtJsonPtr("/payload/pageId", Eq(std::to_string(pageId)))))))
.RetiresOnSaturation();
webSockets_[0]->getDelegate().didReceiveMessage(sformat(
webSockets_[0]->getDelegate().didReceiveMessage(std::format(
R"({{
"event": "connect",
"payload": {{
@@ -1300,7 +1301,7 @@ TEST_F(InspectorPackagerConnectionTest, TestRejectedPageConnection) {
}})",
toJson(std::to_string(pageId))));
webSockets_[0]->getDelegate().didReceiveMessage(sformat(
webSockets_[0]->getDelegate().didReceiveMessage(std::format(
R"({{
"event": "wrappedEvent",
"payload": {{
@@ -1319,7 +1320,7 @@ TEST_F(InspectorPackagerConnectionTest, TestRejectedPageConnection) {
// page.
mockNextConnectionBehavior = Accept;
webSockets_[0]->getDelegate().didReceiveMessage(sformat(
webSockets_[0]->getDelegate().didReceiveMessage(std::format(
R"({{
"event": "connect",
"payload": {{
@@ -1336,7 +1337,7 @@ TEST_F(InspectorPackagerConnectionTest, TestRejectedPageConnection) {
AtJsonPtr("/params", ElementsAre("arg1", "arg2"))))))
.RetiresOnSaturation();
webSockets_[0]->getDelegate().didReceiveMessage(sformat(
webSockets_[0]->getDelegate().didReceiveMessage(std::format(
R"({{
"event": "wrappedEvent",
"payload": {{
@@ -8,13 +8,13 @@
#include <folly/Format.h>
#include <folly/executors/ManualExecutor.h>
#include <folly/executors/QueuedImmediateExecutor.h>
#include <format>
#include "JsiIntegrationTest.h"
#include "engines/JsiIntegrationTestGenericEngineAdapter.h"
#include "engines/JsiIntegrationTestHermesEngineAdapter.h"
using namespace ::testing;
using folly::sformat;
namespace facebook::react::jsinspector_modern {
@@ -486,7 +486,7 @@ TYPED_TEST(JsiIntegrationHermesTest, EvaluateExpressionInExecutionContext) {
}
}
})"));
this->toPage_->sendMessage(sformat(
this->toPage_->sendMessage(std::format(
R"({{
"id": 1,
"method": "Runtime.evaluate",
@@ -508,7 +508,7 @@ TYPED_TEST(JsiIntegrationHermesTest, EvaluateExpressionInExecutionContext) {
// Now the old execution context is stale.
this->expectMessageFromPage(
JsonParsed(AllOf(AtJsonPtr("/id", 3), AtJsonPtr("/error/code", -32600))));
this->toPage_->sendMessage(sformat(
this->toPage_->sendMessage(std::format(
R"({{
"id": 3,
"method": "Runtime.evaluate",
@@ -731,7 +731,7 @@ TYPED_TEST(JsiIntegrationHermesTest, ReleaseRemoteObject) {
// Ensure we can get the properties of the object.
this->expectMessageFromPage(JsonParsed(
AllOf(AtJsonPtr("/id", 2), AtJsonPtr("/result/result", SizeIs(Gt(0))))));
this->toPage_->sendMessage(sformat(
this->toPage_->sendMessage(std::format(
R"({{
"id": 2,
"method": "Runtime.getProperties",
@@ -744,7 +744,7 @@ TYPED_TEST(JsiIntegrationHermesTest, ReleaseRemoteObject) {
"id": 3,
"result": {}
})"));
this->toPage_->sendMessage(sformat(
this->toPage_->sendMessage(std::format(
R"({{
"id": 3,
"method": "Runtime.releaseObject",
@@ -755,7 +755,7 @@ TYPED_TEST(JsiIntegrationHermesTest, ReleaseRemoteObject) {
// Getting properties for a released object results in an error.
this->expectMessageFromPage(
JsonParsed(AllOf(AtJsonPtr("/id", 4), AtJsonPtr("/error/code", -32000))));
this->toPage_->sendMessage(sformat(
this->toPage_->sendMessage(std::format(
R"({{
"id": 4,
"method": "Runtime.getProperties",
@@ -766,7 +766,7 @@ TYPED_TEST(JsiIntegrationHermesTest, ReleaseRemoteObject) {
// Releasing an already released object is an error.
this->expectMessageFromPage(
JsonParsed(AllOf(AtJsonPtr("/id", 5), AtJsonPtr("/error/code", -32000))));
this->toPage_->sendMessage(sformat(
this->toPage_->sendMessage(std::format(
R"({{
"id": 5,
"method": "Runtime.releaseObject",
@@ -797,7 +797,7 @@ TYPED_TEST(JsiIntegrationHermesTest, ReleaseRemoteObjectGroup) {
// Ensure we can get the properties of the object.
this->expectMessageFromPage(JsonParsed(
AllOf(AtJsonPtr("/id", 2), AtJsonPtr("/result/result", SizeIs(Gt(0))))));
this->toPage_->sendMessage(sformat(
this->toPage_->sendMessage(std::format(
R"({{
"id": 2,
"method": "Runtime.getProperties",
@@ -819,7 +819,7 @@ TYPED_TEST(JsiIntegrationHermesTest, ReleaseRemoteObjectGroup) {
// Getting properties for a released object results in an error.
this->expectMessageFromPage(
JsonParsed(AllOf(AtJsonPtr("/id", 4), AtJsonPtr("/error/code", -32000))));
this->toPage_->sendMessage(sformat(
this->toPage_->sendMessage(std::format(
R"({{
"id": 4,
"method": "Runtime.getProperties",
@@ -10,12 +10,10 @@
#import <UIKit/UIKit.h>
#import <vector>
namespace facebook {
namespace react {
namespace facebook::react {
struct ColorComponents;
struct Color;
} // namespace react
} // namespace facebook
} // namespace facebook::react
facebook::react::ColorComponents RCTPlatformColorComponentsFromSemanticItems(
std::vector<std::string>& semanticItems);
@@ -62,15 +62,12 @@ Pod::Spec.new do |s|
add_dependency(s, "React-RCTFBReactNativeSpec")
add_dependency(s, "React-utils", :additional_framework_paths => ["react/utils/platform/ios"])
if ENV["USE_HERMES"] == nil || ENV["USE_HERMES"] == "1"
if use_third_party_jsc()
s.exclude_files = ["ReactCommon/RCTHermesInstance.{mm,h}", "ReactCommon/RCTJscInstance.{mm,h}"]
else
s.dependency "hermes-engine"
add_dependency(s, "React-RuntimeHermes")
s.exclude_files = "ReactCommon/RCTJscInstance.{mm,h}"
elsif ENV['USE_THIRD_PARTY_JSC'] == '1'
s.exclude_files = ["ReactCommon/RCTHermesInstance.{mm,h}", "ReactCommon/RCTJscInstance.{mm,h}"]
else
s.dependency "React-jsc"
s.exclude_files = "ReactCommon/RCTHermesInstance.{mm,h}"
end
add_rn_third_party_dependencies(s)
@@ -44,4 +44,7 @@ Pod::Spec.new do |s|
"DEFINES_MODULE" => "YES" }
s.dependency "React-jsi", version
s.dependency "React-featureflags", version
add_dependency(s, "React-debug")
add_dependency(s, "React-utils", :additional_framework_paths => ["react/utils/platform/ios"])
end
@@ -24,7 +24,7 @@ void executeSynchronouslyOnSameThread_CAN_DEADLOCK(
template <typename DataT>
inline static DataT executeSynchronouslyOnSameThread_CAN_DEADLOCK(
const RuntimeExecutor& runtimeExecutor,
std::function<DataT(jsi::Runtime& runtime)>&& runtimeWork) {
std::function<DataT(jsi::Runtime&)>&& runtimeWork) {
DataT data;
executeSynchronouslyOnSameThread_CAN_DEADLOCK(
@@ -24,7 +24,7 @@ void executeSynchronouslyOnSameThread_CAN_DEADLOCK(
template <typename DataT>
inline static DataT executeSynchronouslyOnSameThread_CAN_DEADLOCK(
const RuntimeExecutor& runtimeExecutor,
std::function<DataT(jsi::Runtime& runtime)>&& runtimeWork) {
std::function<DataT(jsi::Runtime&)>&& runtimeWork) {
DataT data;
executeSynchronouslyOnSameThread_CAN_DEADLOCK(
@@ -33,4 +33,7 @@ inline static DataT executeSynchronouslyOnSameThread_CAN_DEADLOCK(
return data;
}
void unsafeExecuteOnMainThreadSync(std::function<void()> work);
} // namespace facebook::react
@@ -5,11 +5,160 @@
* LICENSE file in the root directory of this source tree.
*/
#import <Foundation/Foundation.h>
#import <ReactCommon/RuntimeExecutorSyncUIThreadUtils.h>
#include <future>
#include <thread>
#import <react/debug/react_native_assert.h>
#import <react/featureflags/ReactNativeFeatureFlags.h>
#import <react/utils/OnScopeExit.h>
#import <algorithm>
#import <functional>
#import <future>
#import <mutex>
#import <optional>
#import <thread>
namespace facebook::react {
namespace {
class UITask {
std::promise<void> _isDone;
std::function<void()> _uiWork;
public:
UITask(UITask &&other) = default;
UITask &operator=(UITask &&other) = default;
UITask(const UITask &) = delete;
UITask &operator=(const UITask &) = delete;
~UITask() = default;
UITask(std::function<void()> &&uiWork) : _uiWork(std::move(uiWork)) {}
void operator()()
{
if (!_uiWork) {
return;
}
OnScopeExit onScopeExit(^{
_uiWork = nullptr;
_isDone.set_value();
});
_uiWork();
}
std::future<void> future()
{
return _isDone.get_future();
}
};
// Protects access to g_uiTask
std::mutex &g_mutex()
{
static std::mutex mutex;
return mutex;
}
std::condition_variable &g_cv()
{
static std::condition_variable cv;
return cv;
}
std::mutex &g_ticket()
{
static std::mutex ticket;
return ticket;
}
std::optional<UITask> &g_uiTask()
{
static std::optional<UITask> uiTaskQueue;
return uiTaskQueue;
}
// Must be called holding g_mutex();
bool hasUITask()
{
return g_uiTask().has_value();
}
// Must be called holding g_mutex();
UITask takeUITask()
{
react_native_assert(hasUITask());
auto uiTask = std::move(*g_uiTask());
g_uiTask() = std::nullopt;
return uiTask;
}
// Must be called holding g_mutex();
UITask &postUITask(std::function<void()> &&uiWork)
{
react_native_assert(!hasUITask());
g_uiTask() = UITask(std::move(uiWork));
g_cv().notify_one();
return *g_uiTask();
}
bool g_isRunningUITask = false;
void runUITask(UITask &uiTask)
{
react_native_assert([[NSThread currentThread] isMainThread]);
g_isRunningUITask = true;
OnScopeExit onScopeExit([]() { g_isRunningUITask = false; });
uiTask();
}
/**
* This method is resilient to multiple javascript threads.
* This can happen when multiple react instances interleave.
*
* The extension from 1 js thread to n: All js threads race to
* get a ticket to post a ui task. The first one to get the ticket
* will post the ui task, and go to sleep. The cooridnator or
* main queue will execute that ui task, waking up the js thread
* and releasing that ticket. Another js thread will get the ticket.
*
* For simplicity, we will just use this algorithm for all bg threads.
* Not just the js thread.
*/
void saferExecuteSynchronouslyOnSameThread_CAN_DEADLOCK(
const RuntimeExecutor &runtimeExecutor,
std::function<void(jsi::Runtime &runtime)> &&runtimeWork)
{
react_native_assert([[NSThread currentThread] isMainThread] && !g_isRunningUITask);
jsi::Runtime *runtime = nullptr;
std::promise<void> runtimeWorkDone;
runtimeExecutor([&runtime, runtimeWorkDoneFuture = runtimeWorkDone.get_future().share()](jsi::Runtime &rt) {
{
std::lock_guard<std::mutex> lock(g_mutex());
runtime = &rt;
g_cv().notify_one();
}
runtimeWorkDoneFuture.wait();
});
while (true) {
std::unique_lock<std::mutex> lock(g_mutex());
g_cv().wait(lock, [&] { return runtime != nullptr || hasUITask(); });
if (runtime != nullptr) {
break;
}
auto uiTask = takeUITask();
lock.unlock();
runUITask(uiTask);
}
OnScopeExit onScopeExit([&]() { runtimeWorkDone.set_value(); });
// Calls into runtime scheduler, which takes care of error handling
runtimeWork(*runtime);
}
/*
* Schedules `runtimeWork` to be executed on the same thread using the
* `RuntimeExecutor`, and blocks on its completion.
@@ -26,7 +175,7 @@ namespace facebook::react {
* - [JS thread] Signal runtime capture block is finished:
* resolve(runtimeCaptureBlockDone);
*/
void executeSynchronouslyOnSameThread_CAN_DEADLOCK(
void legacyExecuteSynchronouslyOnSameThread_CAN_DEADLOCK(
const RuntimeExecutor &runtimeExecutor,
std::function<void(jsi::Runtime &)> &&runtimeWork)
{
@@ -58,4 +207,54 @@ void executeSynchronouslyOnSameThread_CAN_DEADLOCK(
runtimeCaptureBlockDone.get_future().wait();
}
} // namespace
void executeSynchronouslyOnSameThread_CAN_DEADLOCK(
const RuntimeExecutor &runtimeExecutor,
std::function<void(jsi::Runtime &)> &&runtimeWork)
{
if (ReactNativeFeatureFlags::enableMainQueueCoordinatorOnIOS()) {
saferExecuteSynchronouslyOnSameThread_CAN_DEADLOCK(runtimeExecutor, std::move(runtimeWork));
} else {
legacyExecuteSynchronouslyOnSameThread_CAN_DEADLOCK(runtimeExecutor, std::move(runtimeWork));
}
}
/**
* This method is resilient to multiple javascript threads.
* This can happen when multiple react instances interleave.
*
* The extension from 1 js thread to n: All js threads race to
* get a ticket to post a ui task. The first one to get the ticket
* will post the ui task, and go to sleep. The cooridnator or
* main queue will execute that ui task, waking up the js thread
* and releasing that ticket. Another js thread will get the ticket.
*
* For simplicity, we will just use this method for all bg threads.
* Not just the js thread.
*/
void unsafeExecuteOnMainThreadSync(std::function<void()> work)
{
std::lock_guard<std::mutex> ticket(g_ticket());
std::future<void> isDone;
{
std::lock_guard<std::mutex> lock(g_mutex());
isDone = postUITask(std::move(work)).future();
}
dispatch_async(dispatch_get_main_queue(), ^{
std::unique_lock<std::mutex> lock(g_mutex());
if (!hasUITask()) {
return;
}
auto uiTask = takeUITask();
lock.unlock();
runUITask(uiTask);
});
isDone.wait();
}
} // namespace facebook::react
+1
View File
@@ -81,6 +81,7 @@
"gradle.properties",
"gradle/libs.versions.toml",
"index.js",
"index.flow.js",
"interface.js",
"jest-preset.js",
"jest",
@@ -26,53 +26,9 @@ class JSEngineTests < Test::Unit::TestCase
Pod::Config.reset()
Pod::UI.reset()
podSpy_cleanUp()
ENV['USE_HERMES'] = '1'
ENV['CI'] = nil
end
# =============== #
# TEST - setupJsc #
# =============== #
def test_setupJsc_installsPods
# Arrange
fabric_enabled = false
# Act
setup_jsc!(:react_native_path => @react_native_path, :fabric_enabled => fabric_enabled)
# Assert
assert_equal($podInvocationCount, 2)
assert_equal($podInvocation["React-jsi"][:path], "../../ReactCommon/jsi")
assert_equal($podInvocation["React-jsc"][:path], "../../ReactCommon/jsc")
end
def test_setupJsc_installsPods_installsFabricSubspecWhenFabricEnabled
# Arrange
fabric_enabled = true
# Act
setup_jsc!(:react_native_path => @react_native_path, :fabric_enabled => fabric_enabled)
# Assert
assert_equal($podInvocationCount, 3)
assert_equal($podInvocation["React-jsi"][:path], "../../ReactCommon/jsi")
assert_equal($podInvocation["React-jsc"][:path], "../../ReactCommon/jsc")
assert_equal($podInvocation["React-jsc/Fabric"][:path], "../../ReactCommon/jsc")
end
def test_setupJsc_installsPodsWithThirdPartyJSC
# Arrange
ENV['USE_THIRD_PARTY_JSC'] = '1'
fabric_enabled = false
# Act
setup_jsc!(:react_native_path => @react_native_path, :fabric_enabled => fabric_enabled)
# Assert
assert_equal($podInvocationCount, 1)
assert_equal($podInvocation["React-jsi"][:path], "../../ReactCommon/jsi")
end
# ================== #
# TEST - setupHermes #
# ================== #
@@ -34,7 +34,6 @@ class UtilsTests < Test::Unit::TestCase
Xcodeproj::Plist.reset()
XcodebuildMock.reset()
ENV['RCT_NEW_ARCH_ENABLED'] = '0'
ENV['USE_HERMES'] = '1'
ENV['USE_FRAMEWORKS'] = nil
system_reset_commands
$RN_PLATFORMS = nil
@@ -106,21 +105,6 @@ class UtilsTests < Test::Unit::TestCase
})
end
def test_getDefaultFlag_whenOldArchitectureButHermesDisabled()
# Arrange
ENV['RCT_NEW_ARCH_ENABLED'] = '0'
ENV['USE_HERMES'] = '0'
# Act
flags = ReactNativePodsUtils.get_default_flags()
# Assert
assert_equal(flags, {
:fabric_enabled => false,
:hermes_enabled => false,
})
end
def test_getDefaultFlag_whenNewArchitecture()
# Arrange
ENV['RCT_NEW_ARCH_ENABLED'] = '1'
@@ -135,21 +119,6 @@ class UtilsTests < Test::Unit::TestCase
})
end
def test_getDefaultFlag_whenNewArchitectureButHermesDisabled()
# Arrange
ENV['RCT_NEW_ARCH_ENABLED'] = '1'
ENV['USE_HERMES'] = '0'
# Act
flags = ReactNativePodsUtils.get_default_flags()
# Assert
assert_equal(flags, {
:fabric_enabled => true,
:hermes_enabled => false,
})
end
# ============== #
# TEST - has_pod #
# ============== #
@@ -5,19 +5,6 @@
require_relative './utils.rb'
# It sets up the JavaScriptCore.
#
# @parameter react_native_path: relative path to react-native
# @parameter fabric_enabled: whether Fabirc is enabled
def setup_jsc!(react_native_path: "../node_modules/react-native", fabric_enabled: false)
if ENV['USE_THIRD_PARTY_JSC'] != '1'
pod 'React-jsc', :path => "#{react_native_path}/ReactCommon/jsc"
if fabric_enabled
pod 'React-jsc/Fabric', :path => "#{react_native_path}/ReactCommon/jsc"
end
end
end
# It sets up the Hermes.
#
# @parameter react_native_path: relative path to react-native
@@ -32,11 +19,37 @@ def setup_hermes!(react_native_path: "../node_modules/react-native")
pod 'React-hermes', :path => "#{react_native_path}/ReactCommon/hermes"
end
def use_third_party_jsc
return ENV['USE_THIRD_PARTY_JSC'] == '1'
end
# use Hermes is the default. The only other option is the third-party JSC
# if the 3rd party JSC is not true, we always want to use Hermes.
def use_hermes
return !use_third_party_jsc()
end
def use_hermes_flags
return "-DUSE_HERMES=1"
end
def use_third_party_jsc_flags
return "-DUSE_THIRD_PARTY_JSC=1"
end
def js_engine_flags()
if use_hermes()
return use_hermes_flags()
else
return use_third_party_jsc_flags()
end
end
# Utility function to depend on JS engine based on the environment variable.
def depend_on_js_engine(s)
if ENV["USE_HERMES"] == nil || ENV["USE_HERMES"] == "1"
if use_hermes()
s.dependency 'hermes-engine'
elsif ENV['USE_THIRD_PARTY_JSC'] != '1'
elsif use_third_party_jsc()
s.dependency 'React-jsc'
end
end
@@ -142,8 +142,17 @@ class ReactNativeDependenciesUtils
end
def self.nightly_tarball_url(version)
params = "r=snapshots\&g=com.facebook.react\&a=react-native-artifacts\&c=reactnative-dependencies-debug\&e=tar.gz\&v=#{version}-SNAPSHOT"
return resolve_url_redirects("http://oss.sonatype.org/service/local/artifact/maven/redirect\?#{params}")
artefact_coordinate = "react-native-artifacts"
artefact_name = "reactnative-dependencies-debug.tar.gz"
xml_url = "https://central.sonatype.com/repository/maven-snapshots/com/facebook/react/#{artefact_coordinate}/#{version}-SNAPSHOT/maven-metadata.xml"
xml = REXML::Document.new(Net::HTTP.get(URI(xml_url)))
timestamp = xml.elements['metadata/versioning/snapshot/timestamp'].text
build_number = xml.elements['metadata/versioning/snapshot/buildNumber'].text
full_version = "#{version}-#{timestamp}-#{build_number}"
final_url = "https://central.sonatype.com/repository/maven-snapshots/com/facebook/react/#{artefact_coordinate}/#{version}-SNAPSHOT/#{artefact_coordinate}-#{full_version}-#{artefact_name}"
return final_url
end
def self.download_stable_rndeps(react_native_path, version, configuration)
@@ -6,6 +6,7 @@
require 'shellwords'
require_relative "./helpers.rb"
require_relative "./jsengine.rb"
# Utilities class for React Native Cocoapods
class ReactNativePodsUtils
@@ -32,7 +33,7 @@ class ReactNativePodsUtils
flags[:hermes_enabled] = true
end
if ENV['USE_HERMES'] == '0'
if !use_hermes()
flags[:hermes_enabled] = false
end
@@ -64,12 +64,14 @@ def use_react_native! (
fabric_enabled: false,
new_arch_enabled: NewArchitectureHelper.new_arch_enabled,
production: false, # deprecated
hermes_enabled: ENV['USE_HERMES'] && ENV['USE_HERMES'] == '0' ? false : true,
hermes_enabled: true, # deprecated. Hermes is the default engine and JSC has been moved to community support
app_path: '..',
config_file_dir: '',
privacy_file_aggregation_enabled: true
)
error_if_try_to_use_jsc_from_core()
hermes_enabled= true
# Set the app_path as env variable so the podspecs can access it.
ENV['APP_PATH'] = app_path
ENV['REACT_NATIVE_PATH'] = path
@@ -93,7 +95,6 @@ def use_react_native! (
fabric_enabled = fabric_enabled || NewArchitectureHelper.new_arch_enabled
ENV['RCT_FABRIC_ENABLED'] = fabric_enabled ? "1" : "0"
ENV['USE_HERMES'] = hermes_enabled ? "1" : "0"
ENV['RCT_AGGREGATE_PRIVACY_FILES'] = privacy_file_aggregation_enabled ? "1" : "0"
ENV["RCT_NEW_ARCH_ENABLED"] = new_arch_enabled ? "1" : "0"
@@ -142,8 +143,6 @@ def use_react_native! (
if hermes_enabled
setup_hermes!(:react_native_path => prefix)
else
setup_jsc!(:react_native_path => prefix, :fabric_enabled => fabric_enabled)
end
pod 'React-jsiexecutor', :path => "#{prefix}/ReactCommon/jsiexecutor"
@@ -385,8 +384,7 @@ end
def print_jsc_removal_message()
puts ''
puts '=============== JavaScriptCore is being moved ==============='.yellow
puts 'JavaScriptCore has been extracted from react-native core'.yellow
puts 'and will be removed in a future release. It can now be'.yellow
puts 'JavaScriptCore has been removed from React Native. It can now be'.yellow
puts 'installed from `@react-native-community/javascriptcore`'.yellow
puts 'See: https://github.com/react-native-community/javascriptcore'.yellow
puts '============================================================='.yellow
@@ -412,6 +410,19 @@ def print_cocoapods_deprecation_message()
end
def error_if_try_to_use_jsc_from_core()
explicitly_not_use_hermes = ENV['USE_HERMES'] != nil && ENV['USE_HERMES'] == '0'
not_use_3rd_party_jsc = ENV['USE_THIRD_PARTY_JSC'] == nil || ENV['USE_THIRD_PARTY_JSC'] == '0'
if (explicitly_not_use_hermes && not_use_3rd_party_jsc)
message = "Hermes is the default engine and JSC has been moved to community support.\n" +
"Please remove the USE_HERMES=0, as it is not supported anymore.\n" +
"If you want to use JSC, you can install it from `@react-native-community/javascriptcore`.\n" +
"See: https://github.com/react-native-community/javascriptcore"
puts message.red
exit()
end
end
# Function that executes after React Native has been installed to configure some flags and build settings.
#
# Parameters
@@ -429,17 +440,16 @@ def react_native_post_install(
ReactNativePodsUtils.apply_mac_catalyst_patches(installer) if mac_catalyst_enabled
hermes_enabled = ENV['USE_HERMES'] == '1'
privacy_file_aggregation_enabled = ENV['RCT_AGGREGATE_PRIVACY_FILES'] == '1'
if hermes_enabled
if use_hermes()
ReactNativePodsUtils.set_gcc_preprocessor_definition_for_React_hermes(installer)
end
ReactNativePodsUtils.set_gcc_preprocessor_definition_for_debugger(installer)
ReactNativePodsUtils.fix_library_search_paths(installer)
ReactNativePodsUtils.update_search_paths(installer)
ReactNativePodsUtils.set_build_setting(installer, build_setting: "USE_HERMES", value: hermes_enabled)
ReactNativePodsUtils.set_build_setting(installer, build_setting: "USE_HERMES", value: use_hermes())
ReactNativePodsUtils.set_build_setting(installer, build_setting: "REACT_NATIVE_PATH", value: File.join("${PODS_ROOT}", "..", react_native_path))
ReactNativePodsUtils.set_build_setting(installer, build_setting: "SWIFT_ACTIVE_COMPILATION_CONDITIONS", value: ['$(inherited)', 'DEBUG'], config_name: "Debug")
@@ -459,7 +469,7 @@ def react_native_post_install(
NewArchitectureHelper.modify_flags_for_new_architecture(installer, NewArchitectureHelper.new_arch_enabled)
NewArchitectureHelper.set_RCTNewArchEnabled_in_info_plist(installer, NewArchitectureHelper.new_arch_enabled)
if ENV['USE_HERMES'] == '0' && ENV['USE_THIRD_PARTY_JSC'] != '1'
if !use_hermes() && !use_third_party_jsc()
print_jsc_removal_message()
end
@@ -230,8 +230,17 @@ def download_hermes_tarball(react_native_path, tarball_url, version, configurati
end
def nightly_tarball_url(version)
params = "r=snapshots\&g=com.facebook.react\&a=react-native-artifacts\&c=hermes-ios-debug\&e=tar.gz\&v=#{version}-SNAPSHOT"
return resolve_url_redirects("http://oss.sonatype.org/service/local/artifact/maven/redirect\?#{params}")
artefact_coordinate = "react-native-artifacts"
artefact_name = "hermes-ios-debug.tar.gz"
xml_url = "https://central.sonatype.com/repository/maven-snapshots/com/facebook/react/#{artefact_coordinate}/#{version}-SNAPSHOT/maven-metadata.xml"
xml = REXML::Document.new(Net::HTTP.get(URI(xml_url)))
timestamp = xml.elements['metadata/versioning/snapshot/timestamp'].text
build_number = xml.elements['metadata/versioning/snapshot/buildNumber'].text
full_version = "#{version}-#{timestamp}-#{build_number}"
final_url = "https://central.sonatype.com/repository/maven-snapshots/com/facebook/react/#{artefact_coordinate}/#{version}-SNAPSHOT/#{artefact_coordinate}-#{full_version}-#{artefact_name}"
return final_url
end
def resolve_url_redirects(url)
@@ -10,9 +10,17 @@ hermesc_dir_path="$1"; shift
jsi_path="$1"
# This script is supposed to be executed from Xcode "run script" phase.
# Xcode sets up its build environment based on the build target (iphone, iphonesimulator, macodsx).
# Xcode sets up its build environment based on the build target (iphone, iphonesimulator, macosx).
# We want to make sure that hermesc is built for mac.
# So we clean the environment with env -i, and explicitly set SDKROOT to macosx
SDKROOT=$(xcode-select -p)/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk
env -i SDKROOT="$SDKROOT" "$CMAKE_BINARY" -S "${PODS_ROOT}/hermes-engine" -B "$hermesc_dir_path" -DJSI_DIR="$jsi_path"
env -i SDKROOT="$SDKROOT" "$CMAKE_BINARY" --build "$hermesc_dir_path" --target hermesc -j "$(sysctl -n hw.ncpu)"
env -i \
PATH="$PATH" \
SDKROOT="$SDKROOT" \
"$CMAKE_BINARY" -S "${PODS_ROOT}/hermes-engine" -B "$hermesc_dir_path" -DJSI_DIR="$jsi_path"
env -i \
PATH="$PATH" \
SDKROOT="$SDKROOT" \
"$CMAKE_BINARY" --build "$hermesc_dir_path" --target hermesc -j "$(sysctl -n hw.ncpu)"
-6
View File
@@ -37,15 +37,9 @@ def pods(target_name, options = {})
fabric_enabled = true
# Hermes is now enabled by default.
# The following line will only disable Hermes if the USE_HERMES envvar is SET to a value other than 1 (e.g. USE_HERMES=0).
hermes_enabled = !ENV.has_key?('USE_HERMES') || ENV['USE_HERMES'] == '1'
puts "Configuring #{target_name} with Fabric #{fabric_enabled ? "enabled" : "disabled"}.#{hermes_enabled ? " Using Hermes engine." : ""}"
use_react_native!(
path: @prefix_path,
fabric_enabled: fabric_enabled,
hermes_enabled: hermes_enabled,
app_path: "#{Dir.pwd}",
config_file_dir: "#{Dir.pwd}/node_modules",
production: false, #deprecated
+1 -1
View File
@@ -31,7 +31,7 @@ If you are still having a problem after doing the clean up (which can happen if
Both macOS and Xcode are required.
1. `cd packages/rn-tester`
2. Install [Bundler](https://bundler.io/): `gem install bundler`. We use bundler to install the right version of [CocoaPods](https://cocoapods.org/) locally.
3. Install Bundler and CocoaPods dependencies: `bundle install && bundle exec pod install` or `yarn prepare-ios`. In order to use JSC instead of Hermes engine, run: `USE_HERMES=0 bundle exec pod install` or `yarn prepare-ios --arch old --jsvm jsc` instead.
3. Install Bundler and CocoaPods dependencies: `bundle install && bundle exec pod install` or `yarn prepare-ios`.
4. Open the generated `RNTesterPods.xcworkspace`. This is not checked in, as it is generated by CocoaPods. Do not open `RNTesterPods.xcodeproj` directly.
#### Note for Apple Silicon users
@@ -8,6 +8,8 @@
* @format
*/
import type {ReportFullyDrawnViewType} from './ReportFullyDrawnViewNativeComponent';
import {View} from 'react-native';
export default View;
export default View as ReportFullyDrawnViewType;
@@ -7,11 +7,12 @@
package com.facebook.react.uiapp
import android.content.res.Configuration
import android.graphics.Color
import android.graphics.drawable.ColorDrawable
import android.os.Bundle
import android.view.View
import android.widget.FrameLayout
import androidx.core.graphics.drawable.toDrawable
import androidx.core.view.ViewCompat
import androidx.core.view.WindowInsetsCompat
import com.facebook.react.FBRNTesterEndToEndHelper
@@ -43,13 +44,28 @@ internal class RNTesterActivity : ReactActivity() {
if (this::initialProps.isInitialized) initialProps else Bundle()
}
// set background color so it will show below transparent system bars on forced edge-to-edge
private fun maybeUpdateBackgroundColor() {
val isDarkMode =
resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK ==
Configuration.UI_MODE_NIGHT_YES
val color =
if (isDarkMode) {
Color.rgb(11, 6, 0)
} else {
Color.rgb(243, 248, 255)
}
window?.setBackgroundDrawable(color.toDrawable())
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
fullyDrawnReporter.addReporter()
maybeUpdateBackgroundColor()
// set background color so it will show below transparent system bars on forced edge-to-edge
this.window?.setBackgroundDrawable(ColorDrawable(Color.BLACK))
// register insets listener to update margins on the ReactRootView to avoid overlap w/ system
// bars
getReactDelegate()?.getReactRootView()?.let { rootView ->
@@ -69,6 +85,13 @@ internal class RNTesterActivity : ReactActivity() {
}
}
override fun onConfigurationChanged(newConfig: Configuration) {
super.onConfigurationChanged(newConfig)
// update background color on UI mode change
maybeUpdateBackgroundColor()
}
override fun createReactActivityDelegate() = RNTesterActivityDelegate(this, mainComponentName)
override fun getMainComponentName() = "RNTesterApp"
@@ -55,6 +55,7 @@ const config /*: InputConfigT */ = {
// We need to wrap the default transformer so we can run it from source
// using babel-register.
babelTransformerPath: path.resolve(__dirname, 'metro-babel-transformer.js'),
hermesParser: true,
},
serializer: {
// Force an empty list so Metro doesn't inject InitializeCore in tests.
@@ -37,6 +37,7 @@ add_react_third_party_ndk_subdir(folly)
# Common targets
add_react_common_subdir(yoga)
add_react_common_subdir(react/featureflags)
file(GLOB SOURCES "src/*.cpp" "src/*.h")
add_executable(fantom_tester ${SOURCES})
@@ -48,6 +49,7 @@ target_link_libraries(fantom_tester
double-conversion
fast_float
folly_runtime
react_featureflags
yogacore)
target_compile_options(fantom_tester
@@ -15,3 +15,10 @@ cmake -S "$SCRIPT_DIR" -B "$BUILD_DIR" \
-DREACT_COMMON_DIR="${REACT_NATIVE_ROOT_DIR}/ReactCommon"
cmake --build "$BUILD_DIR" --target fantom_tester
while getopts ":r" opt; do
case $opt in
r) "$BUILD_DIR/fantom_tester" ;;
\?) echo "Invalid option: -$OPTARG"; exit 1;;
esac
done
@@ -5,19 +5,40 @@
* LICENSE file in the root directory of this source tree.
*/
#include <fmt/format.h>
#include <glog/logging.h>
#include <react/featureflags/ReactNativeFeatureFlags.h>
#include <react/featureflags/ReactNativeFeatureFlagsDynamicProvider.h>
#include <yoga/YGEnums.h>
#include <yoga/YGValue.h>
#include <format>
#include <iostream>
#include <memory>
using namespace facebook::react;
static void setUpLogging() {
google::InitGoogleLogging("react-native-fantom");
FLAGS_logtostderr = true;
}
static void setUpFeatureFlags() {
folly::dynamic dynamicFeatureFlags = folly::dynamic::object();
dynamicFeatureFlags["enableBridgelessArchitecture"] = true;
dynamicFeatureFlags["cxxNativeAnimatedEnabled"] = true;
ReactNativeFeatureFlags::override(
std::make_unique<ReactNativeFeatureFlagsDynamicProvider>(
dynamicFeatureFlags));
}
int main() {
google::InitGoogleLogging("fantom_tester");
FLAGS_logtostderr = true;
setUpLogging();
setUpFeatureFlags();
LOG(INFO) << "Hello, I am fantom_tester using glog!";
LOG(INFO) << fmt::format(
LOG(INFO) << std::format(
"[Yoga] undefined == zero: {}", YGValueZero == YGValueUndefined);
return 0;
@@ -14,7 +14,6 @@ def use_react_native! (
fabric_enabled: false,
new_arch_enabled: NewArchitectureHelper.new_arch_enabled,
production: false, # deprecated
hermes_enabled: ENV['USE_HERMES'] && ENV['USE_HERMES'] == '0' ? false : true,
flipper_configuration: FlipperConfiguration.disabled,
app_path: '..',
config_file_dir: '',
@@ -27,7 +26,6 @@ def use_react_native! (
path: "../node_modules/react-native",
fabric_enabled: false,
production: false, # deprecated
hermes_enabled: ENV['USE_HERMES'] && ENV['USE_HERMES'] == '0' ? false : true,
flipper_configuration: FlipperConfiguration.disabled,
app_path: '..',
config_file_dir: '',
@@ -39,7 +37,6 @@ const expectedReactNativePodsFile = `
def use_react_native! (
path: "../node_modules/react-native",
production: false, # deprecated
hermes_enabled: ENV['USE_HERMES'] && ENV['USE_HERMES'] == '0' ? false : true,
flipper_configuration: FlipperConfiguration.disabled,
app_path: '..',
config_file_dir: '',
+221 -5
View File
@@ -19,11 +19,25 @@
js-tokens "^4.0.0"
picocolors "^1.0.0"
"@babel/code-frame@^7.27.1":
version "7.27.1"
resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.27.1.tgz#200f715e66d52a23b221a9435534a91cc13ad5be"
integrity sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==
dependencies:
"@babel/helper-validator-identifier" "^7.27.1"
js-tokens "^4.0.0"
picocolors "^1.1.1"
"@babel/compat-data@^7.22.6", "@babel/compat-data@^7.26.5", "@babel/compat-data@^7.26.8":
version "7.26.8"
resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.26.8.tgz#821c1d35641c355284d4a870b8a4a7b0c141e367"
integrity sha512-oH5UPLMWR3L2wEFLnFJ1TZXqHufiTKAiLfqw5zkhS4dKXLJ10yVztfil/twG8EDTA4F/tvVNw9nOl4ZMslB8rQ==
"@babel/compat-data@^7.27.2":
version "7.27.5"
resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.27.5.tgz#7d0658ec1a8420fc866d1df1b03bea0e79934c82"
integrity sha512-KiRAp/VoJaWkkte84TvUd9qjdbZAdiqyvMxrGl1N6vzFogKmaLgoM3L1kgtLicp2HP5fBJS8JrZKLVIZGVJAVg==
"@babel/core@^7.11.6", "@babel/core@^7.12.3", "@babel/core@^7.23.9", "@babel/core@^7.25.2":
version "7.26.9"
resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.26.9.tgz#71838542a4b1e49dfed353d7acbc6eb89f4a76f2"
@@ -45,6 +59,27 @@
json5 "^2.2.3"
semver "^6.3.1"
"@babel/core@^7.24.4":
version "7.27.4"
resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.27.4.tgz#cc1fc55d0ce140a1828d1dd2a2eba285adbfb3ce"
integrity sha512-bXYxrXFubeYdvB0NhD/NBB3Qi6aZeV20GOWVI47t2dkecCEoneR4NPVcb7abpXDEvejgrUfFtG6vG/zxAKmg+g==
dependencies:
"@ampproject/remapping" "^2.2.0"
"@babel/code-frame" "^7.27.1"
"@babel/generator" "^7.27.3"
"@babel/helper-compilation-targets" "^7.27.2"
"@babel/helper-module-transforms" "^7.27.3"
"@babel/helpers" "^7.27.4"
"@babel/parser" "^7.27.4"
"@babel/template" "^7.27.2"
"@babel/traverse" "^7.27.4"
"@babel/types" "^7.27.3"
convert-source-map "^2.0.0"
debug "^4.1.0"
gensync "^1.0.0-beta.2"
json5 "^2.2.3"
semver "^6.3.1"
"@babel/eslint-parser@^7.25.1":
version "7.26.8"
resolved "https://registry.yarnpkg.com/@babel/eslint-parser/-/eslint-parser-7.26.8.tgz#55c4f4aae4970ae127f7a12369182ed6250e6f09"
@@ -65,6 +100,17 @@
"@jridgewell/trace-mapping" "^0.3.25"
jsesc "^3.0.2"
"@babel/generator@^7.27.3":
version "7.27.5"
resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.27.5.tgz#3eb01866b345ba261b04911020cbe22dd4be8c8c"
integrity sha512-ZGhA37l0e/g2s1Cnzdix0O3aLYm66eF8aufiVteOgnwxgnRP8GoyMj7VWsgWnQbVKXyge7hqrFh2K2TQM6t1Hw==
dependencies:
"@babel/parser" "^7.27.5"
"@babel/types" "^7.27.3"
"@jridgewell/gen-mapping" "^0.3.5"
"@jridgewell/trace-mapping" "^0.3.25"
jsesc "^3.0.2"
"@babel/helper-annotate-as-pure@^7.25.9":
version "7.25.9"
resolved "https://registry.yarnpkg.com/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.25.9.tgz#d8eac4d2dc0d7b6e11fa6e535332e0d3184f06b4"
@@ -72,6 +118,13 @@
dependencies:
"@babel/types" "^7.25.9"
"@babel/helper-annotate-as-pure@^7.27.1":
version "7.27.3"
resolved "https://registry.yarnpkg.com/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.27.3.tgz#f31fd86b915fc4daf1f3ac6976c59be7084ed9c5"
integrity sha512-fXSwMQqitTGeHLBC08Eq5yXz2m37E4pJX1qAU1+2cNedz/ifv/bVXft90VeSav5nFO61EcNgwr0aJxbyPaWBPg==
dependencies:
"@babel/types" "^7.27.3"
"@babel/helper-compilation-targets@^7.22.6", "@babel/helper-compilation-targets@^7.25.9", "@babel/helper-compilation-targets@^7.26.5":
version "7.26.5"
resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.26.5.tgz#75d92bb8d8d51301c0d49e52a65c9a7fe94514d8"
@@ -83,6 +136,17 @@
lru-cache "^5.1.1"
semver "^6.3.1"
"@babel/helper-compilation-targets@^7.27.2":
version "7.27.2"
resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.27.2.tgz#46a0f6efab808d51d29ce96858dd10ce8732733d"
integrity sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ==
dependencies:
"@babel/compat-data" "^7.27.2"
"@babel/helper-validator-option" "^7.27.1"
browserslist "^4.24.0"
lru-cache "^5.1.1"
semver "^6.3.1"
"@babel/helper-create-class-features-plugin@^7.25.9":
version "7.26.9"
resolved "https://registry.yarnpkg.com/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.26.9.tgz#d6f83e3039547fbb39967e78043cd3c8b7820c71"
@@ -96,6 +160,19 @@
"@babel/traverse" "^7.26.9"
semver "^6.3.1"
"@babel/helper-create-class-features-plugin@^7.27.1":
version "7.27.1"
resolved "https://registry.yarnpkg.com/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.27.1.tgz#5bee4262a6ea5ddc852d0806199eb17ca3de9281"
integrity sha512-QwGAmuvM17btKU5VqXfb+Giw4JcN0hjuufz3DYnpeVDvZLAObloM77bhMXiqry3Iio+Ai4phVRDwl6WU10+r5A==
dependencies:
"@babel/helper-annotate-as-pure" "^7.27.1"
"@babel/helper-member-expression-to-functions" "^7.27.1"
"@babel/helper-optimise-call-expression" "^7.27.1"
"@babel/helper-replace-supers" "^7.27.1"
"@babel/helper-skip-transparent-expression-wrappers" "^7.27.1"
"@babel/traverse" "^7.27.1"
semver "^6.3.1"
"@babel/helper-create-regexp-features-plugin@^7.18.6", "@babel/helper-create-regexp-features-plugin@^7.25.9":
version "7.26.3"
resolved "https://registry.yarnpkg.com/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.26.3.tgz#5169756ecbe1d95f7866b90bb555b022595302a0"
@@ -124,6 +201,14 @@
"@babel/traverse" "^7.25.9"
"@babel/types" "^7.25.9"
"@babel/helper-member-expression-to-functions@^7.27.1":
version "7.27.1"
resolved "https://registry.yarnpkg.com/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.27.1.tgz#ea1211276be93e798ce19037da6f06fbb994fa44"
integrity sha512-E5chM8eWjTp/aNoVpcbfM7mLxu9XGLWYise2eBKGQomAk/Mb4XoxyqXTZbuTohbsl8EKqdlMhnDI2CCLfcs9wA==
dependencies:
"@babel/traverse" "^7.27.1"
"@babel/types" "^7.27.1"
"@babel/helper-module-imports@^7.25.9":
version "7.25.9"
resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.25.9.tgz#e7f8d20602ebdbf9ebbea0a0751fb0f2a4141715"
@@ -132,6 +217,14 @@
"@babel/traverse" "^7.25.9"
"@babel/types" "^7.25.9"
"@babel/helper-module-imports@^7.27.1":
version "7.27.1"
resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.27.1.tgz#7ef769a323e2655e126673bb6d2d6913bbead204"
integrity sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w==
dependencies:
"@babel/traverse" "^7.27.1"
"@babel/types" "^7.27.1"
"@babel/helper-module-transforms@^7.25.9", "@babel/helper-module-transforms@^7.26.0":
version "7.26.0"
resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.26.0.tgz#8ce54ec9d592695e58d84cd884b7b5c6a2fdeeae"
@@ -141,6 +234,15 @@
"@babel/helper-validator-identifier" "^7.25.9"
"@babel/traverse" "^7.25.9"
"@babel/helper-module-transforms@^7.27.3":
version "7.27.3"
resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.27.3.tgz#db0bbcfba5802f9ef7870705a7ef8788508ede02"
integrity sha512-dSOvYwvyLsWBeIRyOeHXp5vPj5l1I011r52FM1+r1jCERv+aFXYk4whgQccYEGYxK2H3ZAIA8nuPkQ0HaUo3qg==
dependencies:
"@babel/helper-module-imports" "^7.27.1"
"@babel/helper-validator-identifier" "^7.27.1"
"@babel/traverse" "^7.27.3"
"@babel/helper-optimise-call-expression@^7.25.9":
version "7.25.9"
resolved "https://registry.yarnpkg.com/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.25.9.tgz#3324ae50bae7e2ab3c33f60c9a877b6a0146b54e"
@@ -148,11 +250,23 @@
dependencies:
"@babel/types" "^7.25.9"
"@babel/helper-optimise-call-expression@^7.27.1":
version "7.27.1"
resolved "https://registry.yarnpkg.com/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.27.1.tgz#c65221b61a643f3e62705e5dd2b5f115e35f9200"
integrity sha512-URMGH08NzYFhubNSGJrpUEphGKQwMQYBySzat5cAByY1/YgIRkULnIy3tAMeszlL/so2HbeilYloUmSpd7GdVw==
dependencies:
"@babel/types" "^7.27.1"
"@babel/helper-plugin-utils@^7.0.0", "@babel/helper-plugin-utils@^7.10.4", "@babel/helper-plugin-utils@^7.12.13", "@babel/helper-plugin-utils@^7.14.5", "@babel/helper-plugin-utils@^7.18.6", "@babel/helper-plugin-utils@^7.18.9", "@babel/helper-plugin-utils@^7.22.5", "@babel/helper-plugin-utils@^7.25.9", "@babel/helper-plugin-utils@^7.26.5", "@babel/helper-plugin-utils@^7.8.0", "@babel/helper-plugin-utils@^7.8.3":
version "7.26.5"
resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.26.5.tgz#18580d00c9934117ad719392c4f6585c9333cc35"
integrity sha512-RS+jZcRdZdRFzMyr+wcsaqOmld1/EqTghfaBGQQd/WnRdzdlvSZ//kF7U8VQTxf1ynZ4cjUcYgjVGx13ewNPMg==
"@babel/helper-plugin-utils@^7.27.1":
version "7.27.1"
resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.27.1.tgz#ddb2f876534ff8013e6c2b299bf4d39b3c51d44c"
integrity sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw==
"@babel/helper-remap-async-to-generator@^7.25.9":
version "7.25.9"
resolved "https://registry.yarnpkg.com/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.25.9.tgz#e53956ab3d5b9fb88be04b3e2f31b523afd34b92"
@@ -171,6 +285,15 @@
"@babel/helper-optimise-call-expression" "^7.25.9"
"@babel/traverse" "^7.26.5"
"@babel/helper-replace-supers@^7.27.1":
version "7.27.1"
resolved "https://registry.yarnpkg.com/@babel/helper-replace-supers/-/helper-replace-supers-7.27.1.tgz#b1ed2d634ce3bdb730e4b52de30f8cccfd692bc0"
integrity sha512-7EHz6qDZc8RYS5ElPoShMheWvEgERonFCs7IAonWLLUTXW59DP14bCZt89/GKyreYn8g3S83m21FelHKbeDCKA==
dependencies:
"@babel/helper-member-expression-to-functions" "^7.27.1"
"@babel/helper-optimise-call-expression" "^7.27.1"
"@babel/traverse" "^7.27.1"
"@babel/helper-skip-transparent-expression-wrappers@^7.25.9":
version "7.25.9"
resolved "https://registry.yarnpkg.com/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.25.9.tgz#0b2e1b62d560d6b1954893fd2b705dc17c91f0c9"
@@ -179,21 +302,44 @@
"@babel/traverse" "^7.25.9"
"@babel/types" "^7.25.9"
"@babel/helper-skip-transparent-expression-wrappers@^7.27.1":
version "7.27.1"
resolved "https://registry.yarnpkg.com/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.27.1.tgz#62bb91b3abba8c7f1fec0252d9dbea11b3ee7a56"
integrity sha512-Tub4ZKEXqbPjXgWLl2+3JpQAYBJ8+ikpQ2Ocj/q/r0LwE3UhENh7EUabyHjz2kCEsrRY83ew2DQdHluuiDQFzg==
dependencies:
"@babel/traverse" "^7.27.1"
"@babel/types" "^7.27.1"
"@babel/helper-string-parser@^7.25.9":
version "7.25.9"
resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.25.9.tgz#1aabb72ee72ed35789b4bbcad3ca2862ce614e8c"
integrity sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA==
"@babel/helper-string-parser@^7.27.1":
version "7.27.1"
resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz#54da796097ab19ce67ed9f88b47bb2ec49367687"
integrity sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==
"@babel/helper-validator-identifier@^7.25.9":
version "7.25.9"
resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.25.9.tgz#24b64e2c3ec7cd3b3c547729b8d16871f22cbdc7"
integrity sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ==
"@babel/helper-validator-identifier@^7.27.1":
version "7.27.1"
resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.27.1.tgz#a7054dcc145a967dd4dc8fee845a57c1316c9df8"
integrity sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==
"@babel/helper-validator-option@^7.25.9":
version "7.25.9"
resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.25.9.tgz#86e45bd8a49ab7e03f276577f96179653d41da72"
integrity sha512-e/zv1co8pp55dNdEcCynfj9X7nyUKUXoUEwfXqaZt0omVOmDe9oOTdKStH4GmAw6zxMFs50ZayuMfHDKlO7Tfw==
"@babel/helper-validator-option@^7.27.1":
version "7.27.1"
resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz#fa52f5b1e7db1ab049445b421c4471303897702f"
integrity sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==
"@babel/helper-wrap-function@^7.25.9":
version "7.25.9"
resolved "https://registry.yarnpkg.com/@babel/helper-wrap-function/-/helper-wrap-function-7.25.9.tgz#d99dfd595312e6c894bd7d237470025c85eea9d0"
@@ -211,6 +357,14 @@
"@babel/template" "^7.26.9"
"@babel/types" "^7.26.9"
"@babel/helpers@^7.27.4":
version "7.27.6"
resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.27.6.tgz#6456fed15b2cb669d2d1fabe84b66b34991d812c"
integrity sha512-muE8Tt8M22638HU31A3CgfSUciwz1fhATfoVai05aPXGor//CdWDCbnlY1yvBPo07njuVOCNGCSp/GTt12lIug==
dependencies:
"@babel/template" "^7.27.2"
"@babel/types" "^7.27.6"
"@babel/parser@^7.1.0", "@babel/parser@^7.14.7", "@babel/parser@^7.20.7", "@babel/parser@^7.23.9", "@babel/parser@^7.25.3", "@babel/parser@^7.26.9":
version "7.26.9"
resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.26.9.tgz#d9e78bee6dc80f9efd8f2349dcfbbcdace280fd5"
@@ -218,6 +372,13 @@
dependencies:
"@babel/types" "^7.26.9"
"@babel/parser@^7.24.4", "@babel/parser@^7.27.2", "@babel/parser@^7.27.4", "@babel/parser@^7.27.5":
version "7.27.5"
resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.27.5.tgz#ed22f871f110aa285a6fd934a0efed621d118826"
integrity sha512-OsQd175SxWkGlzbny8J3K8TnnDD0N3lrIUtB92xwyRpzaenGZhxDvxN/JgU00U3CDZNj9tPuDJ5H0WS4Nt3vKg==
dependencies:
"@babel/types" "^7.27.3"
"@babel/plugin-bugfix-firefox-class-in-computed-class-key@^7.25.9":
version "7.25.9"
resolved "https://registry.yarnpkg.com/@babel/plugin-bugfix-firefox-class-in-computed-class-key/-/plugin-bugfix-firefox-class-in-computed-class-key-7.25.9.tgz#cc2e53ebf0a0340777fff5ed521943e253b4d8fe"
@@ -720,6 +881,14 @@
dependencies:
"@babel/helper-plugin-utils" "^7.25.9"
"@babel/plugin-transform-private-methods@^7.24.4":
version "7.27.1"
resolved "https://registry.yarnpkg.com/@babel/plugin-transform-private-methods/-/plugin-transform-private-methods-7.27.1.tgz#fdacbab1c5ed81ec70dfdbb8b213d65da148b6af"
integrity sha512-10FVt+X55AjRAYI9BrdISN9/AQWHqldOeZDUoLyif1Kn05a56xVBXb8ZouL8pZ9jem8QpXaOt8TS7RHUIS+GPA==
dependencies:
"@babel/helper-create-class-features-plugin" "^7.27.1"
"@babel/helper-plugin-utils" "^7.27.1"
"@babel/plugin-transform-private-methods@^7.24.7", "@babel/plugin-transform-private-methods@^7.25.9":
version "7.25.9"
resolved "https://registry.yarnpkg.com/@babel/plugin-transform-private-methods/-/plugin-transform-private-methods-7.25.9.tgz#847f4139263577526455d7d3223cd8bda51e3b57"
@@ -1020,6 +1189,15 @@
"@babel/parser" "^7.26.9"
"@babel/types" "^7.26.9"
"@babel/template@^7.27.2":
version "7.27.2"
resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.27.2.tgz#fa78ceed3c4e7b63ebf6cb39e5852fca45f6809d"
integrity sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==
dependencies:
"@babel/code-frame" "^7.27.1"
"@babel/parser" "^7.27.2"
"@babel/types" "^7.27.1"
"@babel/traverse--for-generate-function-map@npm:@babel/traverse@^7.25.3", "@babel/traverse@^7.25.3", "@babel/traverse@^7.25.9", "@babel/traverse@^7.26.5", "@babel/traverse@^7.26.8", "@babel/traverse@^7.26.9":
version "7.26.9"
resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.26.9.tgz#4398f2394ba66d05d988b2ad13c219a2c857461a"
@@ -1033,6 +1211,19 @@
debug "^4.3.1"
globals "^11.1.0"
"@babel/traverse@^7.27.1", "@babel/traverse@^7.27.3", "@babel/traverse@^7.27.4":
version "7.27.4"
resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.27.4.tgz#b0045ac7023c8472c3d35effd7cc9ebd638da6ea"
integrity sha512-oNcu2QbHqts9BtOWJosOVJapWjBDSxGCpFvikNR5TGDYDQf3JwpIoMzIKrvfoti93cLfPJEG4tH9SPVeyCGgdA==
dependencies:
"@babel/code-frame" "^7.27.1"
"@babel/generator" "^7.27.3"
"@babel/parser" "^7.27.4"
"@babel/template" "^7.27.2"
"@babel/types" "^7.27.3"
debug "^4.3.1"
globals "^11.1.0"
"@babel/types@^7.0.0", "@babel/types@^7.20.7", "@babel/types@^7.25.2", "@babel/types@^7.25.9", "@babel/types@^7.26.9", "@babel/types@^7.3.3", "@babel/types@^7.4.4":
version "7.26.9"
resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.26.9.tgz#08b43dec79ee8e682c2ac631c010bdcac54a21ce"
@@ -1041,6 +1232,14 @@
"@babel/helper-string-parser" "^7.25.9"
"@babel/helper-validator-identifier" "^7.25.9"
"@babel/types@^7.27.1", "@babel/types@^7.27.3", "@babel/types@^7.27.6":
version "7.27.6"
resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.27.6.tgz#a434ca7add514d4e646c80f7375c0aa2befc5535"
integrity sha512-ETyHEk2VHHvl9b9jZP5IHPavHYk57EhanlRRuae9XCpb/j5bDCbPPMOBfCWhnl/7EDJz0jEMCi/RhccCE8r1+Q==
dependencies:
"@babel/helper-string-parser" "^7.27.1"
"@babel/helper-validator-identifier" "^7.27.1"
"@bcoe/v8-coverage@^0.2.3":
version "0.2.3"
resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39"
@@ -4027,10 +4226,17 @@ eslint-plugin-jsx-a11y@^6.6.0:
safe-regex-test "^1.0.3"
string.prototype.includes "^2.0.0"
eslint-plugin-react-hooks@^5.2.0:
version "5.2.0"
resolved "https://registry.yarnpkg.com/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-5.2.0.tgz#1be0080901e6ac31ce7971beed3d3ec0a423d9e3"
integrity sha512-+f15FfK64YQwZdJNELETdn5ibXEUQmW1DZL6KXhNnc2heoy/sg9VJJeT7n8TlMWouzWqSWavFkIhHyIbIAEapg==
eslint-plugin-react-hooks@6.1.0-canary-12bc60f5-20250613, eslint-plugin-react-hooks@^5.2.0:
version "6.1.0-canary-12bc60f5-20250613"
resolved "https://registry.yarnpkg.com/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-6.1.0-canary-12bc60f5-20250613.tgz#fac24a3cf8c2b7397b06cc39e016b6b4acad1078"
integrity sha512-K+594rswc9TF1sxVO/Mp5QxxgD6tDsFT/FiwJ+O0/z9+Id6IKUyLn5S7TP24sIij6caUE6aRSEaOqkeIqmzK9A==
dependencies:
"@babel/core" "^7.24.4"
"@babel/parser" "^7.24.4"
"@babel/plugin-transform-private-methods" "^7.24.4"
hermes-parser "^0.25.1"
zod "^3.22.4"
zod-validation-error "^3.0.3"
eslint-plugin-react-native-globals@^0.1.1:
version "0.1.2"
@@ -4796,7 +5002,7 @@ hermes-estree@0.28.1:
resolved "https://registry.yarnpkg.com/hermes-estree/-/hermes-estree-0.28.1.tgz#631e6db146b06e62fc1c630939acf4a3c77d1b24"
integrity sha512-w3nxl/RGM7LBae0v8LH2o36+8VqwOZGv9rX1wyoWT6YaKZLqpJZ0YQ5P0LVr3tuRpf7vCx0iIG4i/VmBJejxTQ==
hermes-parser@0.25.1:
hermes-parser@0.25.1, hermes-parser@^0.25.1:
version "0.25.1"
resolved "https://registry.yarnpkg.com/hermes-parser/-/hermes-parser-0.25.1.tgz#5be0e487b2090886c62bd8a11724cd766d5f54d1"
integrity sha512-6pEjquH3rqaI6cYAXYPcz9MS4rY6R4ngRgrgfDshRptUZIc3lw0MCIJIGDj9++mfySOuPTHB4nrSW99BCvOPIA==
@@ -9030,3 +9236,13 @@ yocto-queue@^0.1.0:
version "0.1.0"
resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b"
integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==
zod-validation-error@^3.0.3:
version "3.5.0"
resolved "https://registry.yarnpkg.com/zod-validation-error/-/zod-validation-error-3.5.0.tgz#13d31d04abbc72f3cc33c5466985f861f54ff96c"
integrity sha512-IWK6O51sRkq0YsnYD2oLDuK2BNsIjYUlR0+1YSd4JyBzm6/892IWroUnLc7oW4FU+b0f6948BHi6H8MDcqpOGw==
zod@^3.22.4:
version "3.25.64"
resolved "https://registry.yarnpkg.com/zod/-/zod-3.25.64.tgz#57b5c7e76dd64e447f7e710285fcdb396b32f803"
integrity sha512-hbP9FpSZf7pkS7hRVUrOjhwKJNyampPgtXKc3AN6DsWtoHsg2Sb4SQaS4Tcay380zSwd2VPo9G9180emBACp5g==